Compare commits
43 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8b7e2da8af | |||
| 38893937ec | |||
| f17af2d0ae | |||
|
|
1cfeaec70b
|
||
|
|
44f3128f16
|
||
| c865c154a4 | |||
| a63a37c5d6 | |||
|
20879c4c08
|
|||
| 7ab8e4e52d | |||
|
f11c4f7764
|
|||
|
44cad2d26e
|
|||
| 357698db02 | |||
|
6c80179a3d
|
|||
| c471060cfd | |||
| 7418d33e63 | |||
| 1cfbc09e4a | |||
|
8984ba3802
|
|||
|
2238ccf40e
|
|||
| 0df7930e06 | |||
| eebc0db02b | |||
| b22d16e61e | |||
| 6bc6bcb7f6 | |||
| 9df58b7f9a | |||
| c83a560e3b | |||
| eadca6904a | |||
| 89829ee81f | |||
|
32b212f9cf
|
|||
|
e3c03bf4a0
|
|||
|
7dba0e4501
|
|||
|
e6349f0594
|
|||
|
15b47dbc42
|
|||
|
7ad2515b67
|
|||
|
c63fcc002b
|
|||
|
c4e7e1259e
|
|||
|
17cc44cb98
|
|||
| 2c567fa71a | |||
| 9b4a95f375 | |||
| 687f87f43b | |||
| 7fdeb78617 | |||
| 19f212f283 | |||
| 1f248812a7 | |||
| 3f8407fa02 | |||
| a0b0183beb |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
data
|
||||
build
|
||||
flattened_contracts
|
||||
node_modules
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
language: node_js
|
||||
node_js:
|
||||
- "lts/*"
|
||||
- "12"
|
||||
|
||||
sudo: false
|
||||
dist: xenial
|
||||
|
||||
@@ -34,6 +34,9 @@ Aragon CLI and Truffle need to be installed on your sytem as well:
|
||||
npm install -g @aragon/cli
|
||||
npm install -g truffle
|
||||
|
||||
_Note: `@aragon/cli` currently fails to install on node.js 14. Please use
|
||||
node.js 12 until the issue has been resolved upstream._
|
||||
|
||||
### Local development chain
|
||||
|
||||
For local development it is recommended to use
|
||||
|
||||
@@ -21,8 +21,8 @@ contract Contribution is AragonApp {
|
||||
bytes32 public constant KERNEL_APP_ADDR_NAMESPACE = 0xd6f028ca0e8edb4a8c9757ca4fdccab25fa1e0317da1188108f7d2dee14902fb;
|
||||
|
||||
// ensure alphabetic order
|
||||
enum Apps { Contribution, Contributor, Proposal, Token }
|
||||
bytes32[4] public appIds;
|
||||
enum Apps { Contribution, Contributor, Proposal, Reimbursement, Token }
|
||||
bytes32[5] public appIds;
|
||||
|
||||
struct ContributionData {
|
||||
uint32 contributorId;
|
||||
@@ -54,7 +54,7 @@ contract Contribution is AragonApp {
|
||||
event ContributionClaimed(uint32 id, uint32 indexed contributorId, uint32 amount);
|
||||
event ContributionVetoed(uint32 id, address vetoedByAccount);
|
||||
|
||||
function initialize(bytes32[4] _appIds) public onlyInit {
|
||||
function initialize(bytes32[5] _appIds) public onlyInit {
|
||||
appIds = _appIds;
|
||||
blocksToWait = 40320; // 7 days; 15 seconds block time
|
||||
initialized();
|
||||
@@ -66,13 +66,13 @@ contract Contribution is AragonApp {
|
||||
}
|
||||
|
||||
function getContributorIdByAddress(address contributorAccount) public view returns (uint32) {
|
||||
address contributor = getContract(uint8(Apps.Contributor));
|
||||
return ContributorInterface(contributor).getContributorIdByAddress(contributorAccount);
|
||||
address contributorContract = getContract(uint8(Apps.Contributor));
|
||||
return ContributorInterface(contributorContract).getContributorIdByAddress(contributorAccount);
|
||||
}
|
||||
|
||||
function getContributorAddressById(uint32 contributorId) public view returns (address) {
|
||||
address contributor = getContract(uint8(Apps.Contributor));
|
||||
return ContributorInterface(contributor).getContributorAddressById(contributorId);
|
||||
address contributorContract = getContract(uint8(Apps.Contributor));
|
||||
return ContributorInterface(contributorContract).getContributorAddressById(contributorId);
|
||||
}
|
||||
|
||||
//
|
||||
@@ -164,7 +164,7 @@ contract Contribution is AragonApp {
|
||||
if (contributionId < 10) {
|
||||
c.confirmedAtBlock = block.number;
|
||||
} else {
|
||||
c.confirmedAtBlock = block.number + blocksToWait;
|
||||
c.confirmedAtBlock = block.number + 1 + blocksToWait;
|
||||
}
|
||||
|
||||
contributionsCount++;
|
||||
@@ -193,10 +193,10 @@ contract Contribution is AragonApp {
|
||||
require(block.number >= c.confirmedAtBlock, 'NOT_CLAIMABLE');
|
||||
|
||||
c.claimed = true;
|
||||
address token = getContract(uint8(Apps.Token));
|
||||
address tokenContract = getContract(uint8(Apps.Token));
|
||||
address contributorAccount = getContributorAddressById(c.contributorId);
|
||||
uint256 amount = uint256(c.amount);
|
||||
IToken(token).mintFor(contributorAccount, amount, contributionId);
|
||||
IToken(tokenContract).mintFor(contributorAccount, amount, contributionId);
|
||||
emit ContributionClaimed(contributionId, c.contributorId, c.amount);
|
||||
}
|
||||
|
||||
|
||||
@@ -28,14 +28,14 @@ contract Contributor is AragonApp {
|
||||
uint32 public contributorsCount;
|
||||
|
||||
// ensure alphabetic order
|
||||
enum Apps { Contribution, Contributor, Proposal, Token }
|
||||
bytes32[4] public appIds;
|
||||
enum Apps { Contribution, Contributor, Proposal, Reimbursement, Token }
|
||||
bytes32[5] public appIds;
|
||||
|
||||
event ContributorProfileUpdated(uint32 id, bytes32 oldHashDigest, bytes32 newHashDigest); // what should be logged
|
||||
event ContributorAccountUpdated(uint32 id, address oldAccount, address newAccount);
|
||||
event ContributorAdded(uint32 id, address account);
|
||||
|
||||
function initialize(address root,bytes32[4] _appIds) public onlyInit {
|
||||
function initialize(address root, bytes32[5] _appIds) public onlyInit {
|
||||
appIds = _appIds;
|
||||
|
||||
initialized();
|
||||
|
||||
@@ -20,8 +20,8 @@ contract Proposal is AragonApp {
|
||||
|
||||
bytes32 public constant KERNEL_APP_ADDR_NAMESPACE = 0xd6f028ca0e8edb4a8c9757ca4fdccab25fa1e0317da1188108f7d2dee14902fb;
|
||||
// ensure alphabetic order
|
||||
enum Apps { Contribution, Contributor, Proposal, Token }
|
||||
bytes32[4] public appIds;
|
||||
enum Apps { Contribution, Contributor, Proposal, Reimbursement, Token }
|
||||
bytes32[5] public appIds;
|
||||
|
||||
struct Proposal {
|
||||
address creatorAccount;
|
||||
@@ -46,7 +46,7 @@ contract Proposal is AragonApp {
|
||||
event ProposalVoted(uint32 id, uint32 voterId, uint16 totalVotes);
|
||||
event ProposalExecuted(uint32 id, uint32 contributorId, uint32 amount);
|
||||
|
||||
function initialize(bytes32[4] _appIds) public onlyInit {
|
||||
function initialize(bytes32[5] _appIds) public onlyInit {
|
||||
appIds = _appIds;
|
||||
initialized();
|
||||
}
|
||||
|
||||
1
apps/reimbursement/.gitattributes
vendored
Normal file
1
apps/reimbursement/.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.sol linguist-language=Solidity
|
||||
7
apps/reimbursement/.gitignore
vendored
Normal file
7
apps/reimbursement/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
node_modules
|
||||
artifacts
|
||||
.cache
|
||||
cache
|
||||
dist
|
||||
ipfs.cmd
|
||||
package-lock.json
|
||||
34
apps/reimbursement/arapp.json
Normal file
34
apps/reimbursement/arapp.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"roles": [
|
||||
{
|
||||
"name": "Add reimursements",
|
||||
"id": "ADD_REIMBURSEMENT_ROLE",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"name": "Veto reimbursement",
|
||||
"id": "VETO_REIMBURSEMENT_ROLE",
|
||||
"params": []
|
||||
}
|
||||
],
|
||||
"environments": {
|
||||
"default": {
|
||||
"network": "development",
|
||||
"appName": "kredits-reimbursement.open.aragonpm.eth"
|
||||
},
|
||||
"rinkeby": {
|
||||
"registry": "0x98df287b6c145399aaa709692c8d308357bc085d",
|
||||
"appName": "kredits-reimbursement.open.aragonpm.eth",
|
||||
"wsRPC": "wss://rinkeby.eth.aragon.network/ws",
|
||||
"network": "rinkeby"
|
||||
},
|
||||
"production": {
|
||||
"registry": "0x314159265dd8dbb310642f98f50c066173c1259b",
|
||||
"appName": "kredits-reimbursement.aragonpm.eth",
|
||||
"wsRPC": "wss://mainnet.eth.aragon.network/ws",
|
||||
"network": "mainnet"
|
||||
}
|
||||
},
|
||||
"appName": "kredits-reimbursement.aragonpm.eth",
|
||||
"path": "contracts/Reimbursement.sol"
|
||||
}
|
||||
34
apps/reimbursement/buidler.config.js
Normal file
34
apps/reimbursement/buidler.config.js
Normal file
@@ -0,0 +1,34 @@
|
||||
const { usePlugin } = require('@nomiclabs/buidler/config')
|
||||
const hooks = require('./scripts/buidler-hooks')
|
||||
|
||||
usePlugin('@aragon/buidler-aragon')
|
||||
|
||||
module.exports = {
|
||||
// Default Buidler configurations. Read more about it at https://buidler.dev/config/
|
||||
defaultNetwork: 'localhost',
|
||||
networks: {
|
||||
localhost: {
|
||||
url: 'http://localhost:8545',
|
||||
},
|
||||
},
|
||||
solc: {
|
||||
version: '0.4.24',
|
||||
optimizer: {
|
||||
enabled: true,
|
||||
runs: 10000,
|
||||
},
|
||||
},
|
||||
// Etherscan plugin configuration. Learn more at https://github.com/nomiclabs/buidler/tree/master/packages/buidler-etherscan
|
||||
etherscan: {
|
||||
apiKey: '', // API Key for smart contract verification. Get yours at https://etherscan.io/apis
|
||||
},
|
||||
// Aragon plugin configuration
|
||||
aragon: {
|
||||
appServePort: 8001,
|
||||
clientServePort: 3000,
|
||||
appSrcPath: 'app/',
|
||||
appBuildOutputPath: 'dist/',
|
||||
appName: 'expenses',
|
||||
hooks, // Path to script hooks
|
||||
},
|
||||
}
|
||||
94
apps/reimbursement/contracts/Reimbursement.sol
Normal file
94
apps/reimbursement/contracts/Reimbursement.sol
Normal file
@@ -0,0 +1,94 @@
|
||||
pragma solidity ^0.4.24;
|
||||
|
||||
import "@aragon/os/contracts/apps/AragonApp.sol";
|
||||
import "@aragon/os/contracts/kernel/IKernel.sol";
|
||||
|
||||
contract Reimbursement is AragonApp {
|
||||
bytes32 public constant ADD_REIMBURSEMENT_ROLE = keccak256("ADD_REIMBURSEMENT_ROLE");
|
||||
bytes32 public constant VETO_REIMBURSEMENT_ROLE = keccak256("VETO_REIMBURSEMENT_ROLE");
|
||||
// bytes32 public constant MANAGE_APPS_ROLE = keccak256("MANAGE_APPS_ROLE");
|
||||
|
||||
struct ReimbursementData {
|
||||
uint32 recipientId;
|
||||
uint256 amount;
|
||||
address token;
|
||||
bytes32 hashDigest;
|
||||
uint8 hashFunction;
|
||||
uint8 hashSize;
|
||||
uint256 confirmedAtBlock;
|
||||
bool vetoed;
|
||||
bool exists;
|
||||
}
|
||||
|
||||
mapping(uint32 => ReimbursementData) public reimbursements;
|
||||
uint32 public reimbursementsCount;
|
||||
|
||||
uint32 public blocksToWait;
|
||||
|
||||
event ReimbursementAdded(uint32 id, address indexed addedByAccount, uint256 amount);
|
||||
event ReimbursementVetoed(uint32 id, address vetoedByAccount);
|
||||
|
||||
function initialize() public onlyInit {
|
||||
blocksToWait = 40320; // 7 days; 15 seconds block time
|
||||
initialized();
|
||||
}
|
||||
|
||||
// function setApps() public isInitialized auth(MANAGE_APPS_ROLE) {
|
||||
// }
|
||||
|
||||
function totalAmount(bool confirmedOnly) public view returns (uint256 amount) {
|
||||
for (uint32 i = 1; i <= reimbursementsCount; i++) {
|
||||
ReimbursementData memory r = reimbursements[i];
|
||||
if (!r.vetoed && (block.number >= r.confirmedAtBlock || !confirmedOnly)) {
|
||||
amount += r.amount; // should use safemath
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function get(uint32 reimbursementId) public view returns (uint32 id, uint32 recipientId, uint256 amount, address token, bytes32 hashDigest, uint8 hashFunction, uint8 hashSize, uint256 confirmedAtBlock, bool exists, bool vetoed) {
|
||||
id = reimbursementId;
|
||||
ReimbursementData storage r = reimbursements[id];
|
||||
return (
|
||||
id,
|
||||
r.recipientId,
|
||||
r.amount,
|
||||
r.token,
|
||||
r.hashDigest,
|
||||
r.hashFunction,
|
||||
r.hashSize,
|
||||
r.confirmedAtBlock,
|
||||
r.exists,
|
||||
r.vetoed
|
||||
);
|
||||
}
|
||||
|
||||
function add(uint256 amount, address token, uint32 recipientId, bytes32 hashDigest, uint8 hashFunction, uint8 hashSize) public isInitialized auth(ADD_REIMBURSEMENT_ROLE) {
|
||||
uint32 reimbursementId = reimbursementsCount + 1;
|
||||
ReimbursementData storage r = reimbursements[reimbursementId];
|
||||
r.exists = true;
|
||||
r.amount = amount;
|
||||
r.token = token;
|
||||
r.recipientId = recipientId;
|
||||
r.hashDigest = hashDigest;
|
||||
r.hashFunction = hashFunction;
|
||||
r.hashSize = hashSize;
|
||||
r.confirmedAtBlock = block.number + blocksToWait;
|
||||
|
||||
reimbursementsCount++;
|
||||
|
||||
emit ReimbursementAdded(reimbursementId, msg.sender, amount);
|
||||
}
|
||||
|
||||
function veto(uint32 reimbursementId) public isInitialized auth(VETO_REIMBURSEMENT_ROLE) {
|
||||
ReimbursementData storage r = reimbursements[reimbursementId];
|
||||
require(r.exists, 'NOT_FOUND');
|
||||
require(block.number < r.confirmedAtBlock, 'VETO_PERIOD_ENDED');
|
||||
r.vetoed = true;
|
||||
|
||||
emit ReimbursementVetoed(reimbursementId, msg.sender);
|
||||
}
|
||||
|
||||
function exists(uint32 reimbursementId) public view returns (bool) {
|
||||
return reimbursements[reimbursementId].exists;
|
||||
}
|
||||
}
|
||||
16
apps/reimbursement/manifest.json
Normal file
16
apps/reimbursement/manifest.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "kredits Reimbursement",
|
||||
"author": "Placeholder-author",
|
||||
"description": "An application for Aragon",
|
||||
"details_url": "/meta/details.md",
|
||||
"source_url": "https://<placeholder-repository-url>",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/meta/icon.svg",
|
||||
"sizes": "56x56"
|
||||
}
|
||||
],
|
||||
"screenshots": [{ "src": "/meta/screenshot-1.png" }],
|
||||
"start_url": "/index.html",
|
||||
"script": "/script.js"
|
||||
}
|
||||
30
apps/reimbursement/package.json
Normal file
30
apps/reimbursement/package.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "kredits-reimbursement",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"scripts": {
|
||||
"start": "npm run start:aragon:ipfs",
|
||||
"start:aragon:ipfs": "aragon run",
|
||||
"start:aragon:http": "aragon run --http localhost:8001 --http-served-from ./dist",
|
||||
"start:app": "",
|
||||
"compile": "aragon contracts compile",
|
||||
"sync-assets": "",
|
||||
"build:app": "",
|
||||
"build:script": "",
|
||||
"build": "",
|
||||
"publish:patch": "aragon apm publish patch",
|
||||
"publish:minor": "aragon apm publish minor",
|
||||
"publish:major": "aragon apm publish major",
|
||||
"versions": "aragon apm versions",
|
||||
"test": "truffle test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aragon/os": "^4.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aragon/test-helpers": "^2.1.0",
|
||||
"eth-gas-reporter": "^0.2.17",
|
||||
"ganache-cli": "^6.9.1",
|
||||
"solidity-coverage": "^0.5.11"
|
||||
}
|
||||
}
|
||||
65
apps/reimbursement/test/helpers/dao.js
Normal file
65
apps/reimbursement/test/helpers/dao.js
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
|
||||
const { hash } = require('eth-ens-namehash')
|
||||
const { getEventArgument } = require('@aragon/contract-test-helpers/events')
|
||||
const Kernel = artifacts.require('@aragon/os/build/contracts/kernel/Kernel')
|
||||
const ACL = artifacts.require('@aragon/os/build/contracts/acl/ACL')
|
||||
const EVMScriptRegistryFactory = artifacts.require(
|
||||
'@aragon/os/build/contracts/factory/EVMScriptRegistryFactory'
|
||||
)
|
||||
const DAOFactory = artifacts.require(
|
||||
'@aragon/os/build/contracts/factory/DAOFactory'
|
||||
)
|
||||
|
||||
const newDao = async (rootAccount) => {
|
||||
// Deploy a DAOFactory.
|
||||
const kernelBase = await Kernel.new(true)
|
||||
const aclBase = await ACL.new()
|
||||
const registryFactory = await EVMScriptRegistryFactory.new()
|
||||
const daoFactory = await DAOFactory.new(
|
||||
kernelBase.address,
|
||||
aclBase.address,
|
||||
registryFactory.address
|
||||
)
|
||||
|
||||
// Create a DAO instance.
|
||||
const daoReceipt = await daoFactory.newDAO(rootAccount)
|
||||
const dao = await Kernel.at(getEventArgument(daoReceipt, 'DeployDAO', 'dao'))
|
||||
|
||||
// Grant the rootAccount address permission to install apps in the DAO.
|
||||
const acl = await ACL.at(await dao.acl())
|
||||
const APP_MANAGER_ROLE = await kernelBase.APP_MANAGER_ROLE()
|
||||
await acl.createPermission(
|
||||
rootAccount,
|
||||
dao.address,
|
||||
APP_MANAGER_ROLE,
|
||||
rootAccount,
|
||||
{ from: rootAccount }
|
||||
)
|
||||
|
||||
return { dao, acl }
|
||||
}
|
||||
|
||||
const newApp = async (dao, appName, baseAppAddress, rootAccount) => {
|
||||
const receipt = await dao.newAppInstance(
|
||||
hash(`${appName}.aragonpm.test`), // appId - Unique identifier for each app installed in the DAO; can be any bytes32 string in the tests.
|
||||
baseAppAddress, // appBase - Location of the app's base implementation.
|
||||
'0x', // initializePayload - Used to instantiate and initialize the proxy in the same call (if given a non-empty bytes string).
|
||||
false, // setDefault - Whether the app proxy is the default proxy.
|
||||
{ from: rootAccount }
|
||||
)
|
||||
|
||||
// Find the deployed proxy address in the tx logs.
|
||||
const logs = receipt.logs
|
||||
const log = logs.find((l) => l.event === 'NewAppProxy')
|
||||
const proxyAddress = log.args.proxy
|
||||
|
||||
return proxyAddress
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
newDao,
|
||||
newApp,
|
||||
}
|
||||
|
||||
*/
|
||||
21
apps/reimbursement/test/helpers/permissions.js
Normal file
21
apps/reimbursement/test/helpers/permissions.js
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
|
||||
const ANY_ADDRESS = '0xffffffffffffffffffffffffffffffffffffffff'
|
||||
|
||||
const setOpenPermission = async (acl, appAddress, role, rootAddress) => {
|
||||
// Note: Setting a permission to 0xffffffffffffffffffffffffffffffffffffffff
|
||||
// is interpreted by aragonOS as allowing the role for any address.
|
||||
await acl.createPermission(
|
||||
ANY_ADDRESS, // entity (who?) - The entity or address that will have the permission.
|
||||
appAddress, // app (where?) - The app that holds the role involved in this permission.
|
||||
role, // role (what?) - The particular role that the entity is being assigned to in this permission.
|
||||
rootAddress, // manager - Can grant/revoke further permissions for this role.
|
||||
{ from: rootAddress}
|
||||
)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
setOpenPermission
|
||||
}
|
||||
|
||||
*/
|
||||
1
apps/reimbursement/truffle.js
Normal file
1
apps/reimbursement/truffle.js
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = require("../../truffle.js");
|
||||
8716
apps/reimbursement/yarn.lock
Normal file
8716
apps/reimbursement/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
@@ -7,12 +7,12 @@ contract Token is ERC20Token, AragonApp {
|
||||
bytes32 public constant MINT_TOKEN_ROLE = keccak256("MINT_TOKEN_ROLE");
|
||||
|
||||
// ensure alphabetic order
|
||||
enum Apps { Contribution, Contributor, Proposal, Token }
|
||||
bytes32[4] public appIds;
|
||||
enum Apps { Contribution, Contributor, Proposal, Reimbursement, Token }
|
||||
bytes32[5] public appIds;
|
||||
|
||||
event LogMint(address indexed recipient, uint256 amount, uint32 contributionId);
|
||||
|
||||
function initialize(bytes32[4] _appIds) public onlyInit {
|
||||
function initialize(bytes32[5] _appIds) public onlyInit {
|
||||
appIds = _appIds;
|
||||
name = 'Kredits';
|
||||
symbol = '₭S';
|
||||
|
||||
@@ -31,13 +31,19 @@ const contractCalls = [
|
||||
wiki_username: 'Manuel',
|
||||
}, { gasLimit: 200000 }]],
|
||||
|
||||
['Proposal', 'add', [{ contributorId: 1, contributorIpfsHash: 'QmWKCYGr2rSf6abUPaTYqf98urvoZxGrb7dbspFZA6oyVF', date: '2019-04-09', amount: 500, kind: 'dev', description: '[67P/kredits-contracts] Ran the seeds', url: '' }, { gasLimit: 350000 }]],
|
||||
['Proposal', 'add', [{ contributorId: 2, contributorIpfsHash: 'QmcHzEeAM26HV2zHTf5HnZrCtCtGdEccL5kUtDakAB7ozB', date: '2019-04-10', amount: 500, kind: 'dev', description: '[67P/kredits-contracts] Ran the seeds', url: '' }, { gasLimit: 350000 }]],
|
||||
['Proposal', 'add', [{ contributorId: 2, contributorIpfsHash: 'QmcHzEeAM26HV2zHTf5HnZrCtCtGdEccL5kUtDakAB7ozB', date: '2019-04-11', amount: 500, kind: 'dev', description: '[67P/kredits-contracts] Hacked on kredits', url: '' }, { gasLimit: 350000 }]],
|
||||
['Proposal', 'vote', [1, { gasLimit: 550000 }]],
|
||||
['Contribution', 'add', [{ contributorId: 1, contributorIpfsHash: 'QmWKCYGr2rSf6abUPaTYqf98urvoZxGrb7dbspFZA6oyVF', date: '2019-04-11', amount: 5000, kind: 'dev', description: '[67P/kredits-contracts] Introduce contribution token', url: '' }, { gasLimit: 350000 }]],
|
||||
['Contribution', 'add', [{ contributorId: 1, contributorIpfsHash: 'QmWKCYGr2rSf6abUPaTYqf98urvoZxGrb7dbspFZA6oyVF', date: '2019-04-11', amount: 500, kind: 'dev', description: '[67P/kredits-contracts] Test this thing', url: '' }, { gasLimit: 350000 }]],
|
||||
['Contribution', 'add', [{ contributorId: 2, contributorIpfsHash: 'QmcHzEeAM26HV2zHTf5HnZrCtCtGdEccL5kUtDakAB7ozB', date: '2019-04-11', amount: 1500, kind: 'dev', description: '[67P/kredits-web] Reviewed stuff', url: '' }, { gasLimit: 350000 }]],
|
||||
['Contribution', 'claim', [1, { gasLimit: 300000 }]],
|
||||
['Contribution', 'add', [{ contributorId: 1, contributorIpfsHash: 'QmWKCYGr2rSf6abUPaTYqf98urvoZxGrb7dbspFZA6oyVF', date: '2019-04-11', amount: 1500, kind: 'dev', description: '[67P/kredits-contracts] Add tests', url: '' }, { gasLimit: 350000 }]],
|
||||
['Contribution', 'add', [{ contributorId: 1, contributorIpfsHash: 'QmWKCYGr2rSf6abUPaTYqf98urvoZxGrb7dbspFZA6oyVF', date: '2019-04-11', amount: 1500, kind: 'dev', description: '[67P/kredits-contracts] Introduce contribution token', url: '' }, { gasLimit: 350000 }]],
|
||||
['Contribution', 'add', [{ contributorId: 2, contributorIpfsHash: 'QmcHzEeAM26HV2zHTf5HnZrCtCtGdEccL5kUtDakAB7ozB', date: '2019-04-11', amount: 5000, kind: 'dev', description: '[67P/kredits-web] Expense UI, first draft', url: '' }, { gasLimit: 350000 }]],
|
||||
|
||||
['Reimbursement', 'add', [{amount: 1116000, recipientId: 1, token: '0x2260fac5e5542a773aa44fbcfedf7c193bc2c599', expenses: [
|
||||
{ title: 'Server rent', description: 'Dedicated server: andromeda.kosmos.org, April 2020', amount: 61, currency: 'EUR', date: '2020-05-28' },
|
||||
{ title: 'Server rent', description: 'Dedicated server: centaurus.kosmos.org, April 2020', amount: 32, currency: 'EUR', date: '2020-05-28' }
|
||||
]}, { gasLimit: 300000 }]],
|
||||
['Reimbursement', 'add', [{amount: 166800, recipientId: 2, token: '0x2260fac5e5542a773aa44fbcfedf7c193bc2c599', expenses: [
|
||||
{ title: 'Domain kosmos.chat', description: 'Yearly registration fee for domain kosmos.chat', amount: 13.90, currency: 'EUR', date: '2020-05-30' }
|
||||
]}, { gasLimit: 300000 }]],
|
||||
];
|
||||
|
||||
const funds = [
|
||||
|
||||
@@ -10,17 +10,18 @@ import "../apps/contribution/contracts/Contribution.sol";
|
||||
import "../apps/contributor/contracts/Contributor.sol";
|
||||
import "../apps/token/contracts/Token.sol";
|
||||
import "../apps/proposal/contracts/Proposal.sol";
|
||||
import "../apps/reimbursement/contracts/Reimbursement.sol";
|
||||
|
||||
contract KreditsKit is KitBase {
|
||||
|
||||
// ensure alphabetic order
|
||||
enum Apps { Contribution, Contributor, Proposal, Token }
|
||||
bytes32[4] public appIds;
|
||||
enum Apps { Contribution, Contributor, Proposal, Reimbursement, Token }
|
||||
bytes32[5] public appIds;
|
||||
|
||||
event DeployInstance(address dao);
|
||||
event InstalledApp(address dao, address appProxy, bytes32 appId);
|
||||
|
||||
constructor (DAOFactory _fac, ENS _ens, bytes32[4] _appIds) public KitBase(_fac, _ens) {
|
||||
constructor (DAOFactory _fac, ENS _ens, bytes32[5] _appIds) public KitBase(_fac, _ens) {
|
||||
appIds = _appIds;
|
||||
}
|
||||
|
||||
@@ -41,18 +42,24 @@ contract KreditsKit is KitBase {
|
||||
Contribution contribution = Contribution(_installApp(dao, appIds[uint8(Apps.Contribution)]));
|
||||
contribution.initialize(appIds);
|
||||
|
||||
Proposal proposal = Proposal(_installApp(dao, appIds[uint8(Apps.Proposal)]));
|
||||
proposal.initialize(appIds);
|
||||
|
||||
acl.createPermission(root, contribution, contribution.ADD_CONTRIBUTION_ROLE(), this);
|
||||
acl.createPermission(root, contribution, contribution.VETO_CONTRIBUTION_ROLE(), this);
|
||||
acl.grantPermission(proposal, contribution, contribution.ADD_CONTRIBUTION_ROLE());
|
||||
|
||||
Proposal proposal = Proposal(_installApp(dao, appIds[uint8(Apps.Proposal)]));
|
||||
proposal.initialize(appIds);
|
||||
|
||||
Reimbursement reimbursement = Reimbursement(_installApp(dao, appIds[uint8(Apps.Reimbursement)]));
|
||||
reimbursement.initialize();
|
||||
acl.createPermission(root, reimbursement, reimbursement.ADD_REIMBURSEMENT_ROLE(), this);
|
||||
acl.createPermission(root, reimbursement, reimbursement.VETO_REIMBURSEMENT_ROLE(), this);
|
||||
|
||||
uint256[] memory params = new uint256[](1);
|
||||
params[0] = uint256(203) << 248 | uint256(1) << 240 | uint240(contributor);
|
||||
acl.grantPermissionP(acl.ANY_ENTITY(), contribution, contribution.ADD_CONTRIBUTION_ROLE(), params);
|
||||
acl.grantPermissionP(acl.ANY_ENTITY(), contribution, contribution.VETO_CONTRIBUTION_ROLE(), params);
|
||||
acl.grantPermissionP(acl.ANY_ENTITY(), contributor, contributor.MANAGE_CONTRIBUTORS_ROLE(), params);
|
||||
acl.grantPermissionP(acl.ANY_ENTITY(), reimbursement, reimbursement.ADD_REIMBURSEMENT_ROLE(), params);
|
||||
|
||||
//acl.setPermissionManager(this, proposal, proposal.VOTE_PROPOSAL_ROLE();
|
||||
acl.createPermission(root, proposal, proposal.VOTE_PROPOSAL_ROLE(), this);
|
||||
@@ -67,6 +74,8 @@ contract KreditsKit is KitBase {
|
||||
acl.setPermissionManager(root, contribution, contribution.ADD_CONTRIBUTION_ROLE());
|
||||
acl.setPermissionManager(root, contribution, contribution.VETO_CONTRIBUTION_ROLE());
|
||||
acl.setPermissionManager(root, contributor, contributor.MANAGE_CONTRIBUTORS_ROLE());
|
||||
acl.setPermissionManager(root, reimbursement, reimbursement.ADD_REIMBURSEMENT_ROLE());
|
||||
acl.setPermissionManager(root, reimbursement, reimbursement.VETO_REIMBURSEMENT_ROLE());
|
||||
|
||||
acl.createPermission(root, token, token.MINT_TOKEN_ROLE(), this);
|
||||
acl.grantPermission(contribution, token, token.MINT_TOKEN_ROLE());
|
||||
|
||||
19000
data/contributions.json
Normal file
19000
data/contributions.json
Normal file
File diff suppressed because it is too large
Load Diff
121
data/contributors.json
Normal file
121
data/contributors.json
Normal file
@@ -0,0 +1,121 @@
|
||||
{
|
||||
"1": {
|
||||
"account": "0x7E8f313C56F809188313aa274Fa67EE58c31515d",
|
||||
"hashDigest": "0x99b8afd7b266e19990924a8be9099e81054b70c36b20937228a77a5cf75723b8",
|
||||
"hashFunction": 18,
|
||||
"hashSize": 32,
|
||||
"id": 1
|
||||
},
|
||||
"2": {
|
||||
"account": "0x49575f3DD9a0d60aE661BC992f72D837A77f05Bc",
|
||||
"hashDigest": "0xeb78574922d8606419b714174b31961007afefe313111c9db817aa9d3f82e157",
|
||||
"hashFunction": 18,
|
||||
"hashSize": 32,
|
||||
"id": 2
|
||||
},
|
||||
"3": {
|
||||
"account": "0xF722709ECC3B05c19d02E82a2a4A4021B8F48C62",
|
||||
"hashDigest": "0x7acd73632c7cc8d317fa9e72e925eaef1d700e69f185b84e3f0168471e17cf2d",
|
||||
"hashFunction": 18,
|
||||
"hashSize": 32,
|
||||
"id": 3
|
||||
},
|
||||
"4": {
|
||||
"account": "0xD4a64570B12dA659Ee4BBd41c3509B7b1F9c51AC",
|
||||
"hashDigest": "0x4d394c58e4b0534446be10a9b7f8b50e5478258719c23a3e158a5ea9fca14bdd",
|
||||
"hashFunction": 18,
|
||||
"hashSize": 32,
|
||||
"id": 4
|
||||
},
|
||||
"5": {
|
||||
"account": "0x35d9e68a5F7A935C64b08221d5DC7f161a415184",
|
||||
"hashDigest": "0xd6f726f37ad52998b8341b7021cb5d9f4dcf7ce09af575358505a2a7c2f81edb",
|
||||
"hashFunction": 18,
|
||||
"hashSize": 32,
|
||||
"id": 5
|
||||
},
|
||||
"6": {
|
||||
"account": "0x8345E84D848792bf750A1d032d76f20f00aeC1a7",
|
||||
"hashDigest": "0x1acdc784e20f62c44ec222552531bf89daafaaca1893a68057a9ada920ea0984",
|
||||
"hashFunction": 18,
|
||||
"hashSize": 32,
|
||||
"id": 6
|
||||
},
|
||||
"7": {
|
||||
"account": "0x4D99d767477Fbb2B47EFeb17E2a78970AD22CCc1",
|
||||
"hashDigest": "0xd6d16bde5ee3d3374dd5981dab0296938b24e3c812e72ba4fa3d38011b6fab24",
|
||||
"hashFunction": 18,
|
||||
"hashSize": 32,
|
||||
"id": 7
|
||||
},
|
||||
"8": {
|
||||
"account": "0x6a6cD99ab0335C92E55fbb4403D67A4f4B52AfEc",
|
||||
"hashDigest": "0xec30c91b31a85eef2af7afce0ba66b0636f1d61a75a12d8ca188a2d9e1626cf2",
|
||||
"hashFunction": 18,
|
||||
"hashSize": 32,
|
||||
"id": 8
|
||||
},
|
||||
"9": {
|
||||
"account": "0x21aB0B3527326dcA4467245654Cf881F5F7a8c5e",
|
||||
"hashDigest": "0xd6c8b0f285e614b12f700d6e66e2e67901c16c420308625f844357283bd555ba",
|
||||
"hashFunction": 18,
|
||||
"hashSize": 32,
|
||||
"id": 9
|
||||
},
|
||||
"10": {
|
||||
"account": "0x150A69bAfA216FD55C7CeF8eA2002cd582dc5982",
|
||||
"hashDigest": "0x7490aabadf995751211686b3b9f4b8cfee3354947df23cec043c15bfb519c817",
|
||||
"hashFunction": 18,
|
||||
"hashSize": 32,
|
||||
"id": 10
|
||||
},
|
||||
"11": {
|
||||
"account": "0x2f65679cAf0c3abCbF77fC68fA56759f4D96C73c",
|
||||
"hashDigest": "0xd147d2d26f7eb1cbe1c5da53c345565df2d3c6c33a8a3e3331952d4b878b2bb0",
|
||||
"hashFunction": 18,
|
||||
"hashSize": 32,
|
||||
"id": 11
|
||||
},
|
||||
"12": {
|
||||
"account": "0x13a24319Abb4e00c383D8a80dACb20690699Ac8C",
|
||||
"hashDigest": "0x84c63c2d3f9510f87717e54a2cecb41f88dce6a41ae6392480ebf6561e30670e",
|
||||
"hashFunction": 18,
|
||||
"hashSize": 32,
|
||||
"id": 12
|
||||
},
|
||||
"13": {
|
||||
"account": "0x0e0F02508b80e401C26F584fF02eE80Ab094CdF9",
|
||||
"hashDigest": "0xe4c34e15b87114e9edd599fa1bbd6d4b1bf45c3b0163d0772158fb93503c1fe1",
|
||||
"hashFunction": 18,
|
||||
"hashSize": 32,
|
||||
"id": 13
|
||||
},
|
||||
"14": {
|
||||
"account": "0x49750D74930b20b2937DDF77A6C30d81882d888E",
|
||||
"hashDigest": "0xa1f18c54e77d1e8f0fa106463b3f66a34a6287bcd9b643febea0ab2e97009665",
|
||||
"hashFunction": 18,
|
||||
"hashSize": 32,
|
||||
"id": 14
|
||||
},
|
||||
"15": {
|
||||
"account": "0x608FD4b95116Ea616990Aaeb1d4f1ce07612f261",
|
||||
"hashDigest": "0x1d9de6de5c72eedca6d7a5e8a9159e2f5fe676506aece3000acefcc821723429",
|
||||
"hashFunction": 18,
|
||||
"hashSize": 32,
|
||||
"id": 15
|
||||
},
|
||||
"16": {
|
||||
"account": "0xCaBba4560c96FADe3Dd6C29cF24Cfb16a228bC1c",
|
||||
"hashDigest": "0x0c81914b8a332808879b06b1a68398edccea7e7facb50767e4116c33914d2c64",
|
||||
"hashFunction": 18,
|
||||
"hashSize": 32,
|
||||
"id": 16
|
||||
},
|
||||
"17": {
|
||||
"account": "0x765E88b4F9a59C3a3b300C6eFF9E6E9fDDf9FbD9",
|
||||
"hashDigest": "0xcfbeeadc244dfdc55bbad50d431871439df067970db84c73023956c96a6f5df2",
|
||||
"hashFunction": 18,
|
||||
"hashSize": 32,
|
||||
"id": 17
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,36 @@
|
||||
|
||||
aragon apm publish major --environment=rinkeby"
|
||||
|
||||
## 20212-01-14
|
||||
|
||||
apps/contribution@master » aragon apm publish major --environment=rinkeby
|
||||
eth-provider | Invalid provider preset/location: "local"
|
||||
✔ Start IPFS
|
||||
✔ Applying version bump (major)
|
||||
↓ Building frontend [skipped]
|
||||
→ build script not defined in package.json
|
||||
✔ Deploy contract
|
||||
✔ Determine contract address for version
|
||||
✔ Prepare files for publishing
|
||||
✔ Generate application artifact
|
||||
✔ Publish intent
|
||||
|
||||
⚠ Publishing files from the project's root folder is not recommended. Consider using the distribution folder of your project: "--files <folder>".
|
||||
|
||||
The following information will be published:
|
||||
Contract address: 0x914Da982ef17B56D2e868E3a67E923EbED1aE017
|
||||
Content (ipfs): QmdVrY2R48NFqwLopd8ix1anAK1d6WafDGauou3ZJrB9gf
|
||||
|
||||
? Publish to kredits-contribution.open.aragonpm.eth repo Yes
|
||||
|
||||
✔ Publish kredits-contribution.open.aragonpm.eth
|
||||
|
||||
Successfully published kredits-contribution.open.aragonpm.eth v7.0.0 :
|
||||
|
||||
Transaction hash: 0xb817b2e80e90a6be60b45dd39987498e3132c9962c0501feb7549ad30186c6d5
|
||||
|
||||
|
||||
|
||||
## 2019-04-24 update balances
|
||||
|
||||
✔ Successfully published kredits-contribution.open.aragonpm.eth v6.0.0:
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
# Kredits deployment
|
||||
|
||||
|
||||
## 2021-01-14
|
||||
|
||||
apps/contribution@master » aragon dao upgrade 0xc34edf7d11b7f8433d597f0bb0697acdff55ef14 kredits-contribution.open.aragonpm.eth --environment=rinkeby
|
||||
eth-provider | Invalid provider preset/location: "local"
|
||||
✔ Fetching kredits-contribution.open.aragonpm.eth@latest
|
||||
✔ Fetching kredits-contribution.open.aragonpm.eth@latest
|
||||
✔ Upgrading app
|
||||
|
||||
✔ Successfully executed: "Upgrade 'kredits-contribution.open.aragonpm.eth' app instances to v7.0.0"
|
||||
|
||||
## 2019-04-25 canPerfom fix
|
||||
|
||||
aragon dao upgrade 0xc34edf7d11b7f8433d597f0bb0697acdff55ef14 kredits-contributor.open.aragonpm.eth --environment=rinkeby
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
lib/abis/Reimbursement.json
Normal file
1
lib/abis/Reimbursement.json
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -3,6 +3,7 @@ module.exports = {
|
||||
Contribution: require('./contribution'),
|
||||
Proposal: require('./proposal'),
|
||||
Token: require('./token'),
|
||||
Reimbursement: require('./reimbursement'),
|
||||
Kernel: require('./kernel'),
|
||||
Acl: require('./acl'),
|
||||
};
|
||||
|
||||
65
lib/contracts/reimbursement.js
Normal file
65
lib/contracts/reimbursement.js
Normal file
@@ -0,0 +1,65 @@
|
||||
const Record = require('./record');
|
||||
const ExpenseSerializer = require('../serializers/expense');
|
||||
|
||||
class Reimbursement extends Record {
|
||||
get count () {
|
||||
return this.functions.reimbursementsCount();
|
||||
}
|
||||
|
||||
getById (id) {
|
||||
return this.functions.get(id)
|
||||
.then(data => {
|
||||
return this.ipfs.catAndMerge(data, (ipfsDocument) => {
|
||||
const expenses = JSON.parse(ipfsDocument);
|
||||
return { expenses };
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getData (id) {
|
||||
return this.functions.getReimbursement(id);
|
||||
}
|
||||
|
||||
async add (attrs, callOptions = {}) {
|
||||
const amount = parseInt(attrs.amount);
|
||||
const token = attrs.token;
|
||||
const recipientId = attrs.recipientId;
|
||||
const expenses = attrs.expenses.map( e => new ExpenseSerializer(e) );
|
||||
let errorMessage;
|
||||
|
||||
if (typeof amount !== 'number' || amount <= 0) {
|
||||
errorMessage = 'Invalid data: amount must be a positive number.';
|
||||
}
|
||||
if (!token || token === '') {
|
||||
errorMessage = 'Invalid data: token must be a token address.';
|
||||
}
|
||||
if (!recipientId || recipientId === '') {
|
||||
errorMessage = 'Invalid data: recipientId is required.';
|
||||
}
|
||||
if (expenses.length === 0) {
|
||||
errorMessage = 'Invalid data: at least one expense item is required.';
|
||||
}
|
||||
if (errorMessage) { return Promise.reject(new Error(errorMessage)); }
|
||||
|
||||
return Promise.all(expenses.map(e => e.validate()))
|
||||
.then(() => {
|
||||
const jsonStr = JSON.stringify(expenses.map(e => e.data), null, 2);
|
||||
return this.ipfs
|
||||
.add(jsonStr)
|
||||
.then(ipfsHashAttr => {
|
||||
const reimbursement = [
|
||||
amount,
|
||||
token,
|
||||
parseInt(recipientId),
|
||||
ipfsHashAttr.hashDigest,
|
||||
ipfsHashAttr.hashFunction,
|
||||
ipfsHashAttr.hashSize,
|
||||
];
|
||||
|
||||
return this.functions.add(...reimbursement, callOptions);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Reimbursement;
|
||||
@@ -6,6 +6,7 @@ const deprecate = require('./utils/deprecate');
|
||||
const ABIS = {
|
||||
Contributor: require('./abis/Contributor.json'),
|
||||
Contribution: require('./abis/Contribution.json'),
|
||||
Reimbursement: require('./abis/Reimbursement.json'),
|
||||
Token: require('./abis/Token.json'),
|
||||
Proposal: require('./abis/Proposal.json'),
|
||||
Kernel: require('./abis/Kernel.json'),
|
||||
@@ -16,6 +17,7 @@ const APP_CONTRACTS = [
|
||||
'Contribution',
|
||||
'Token',
|
||||
'Proposal',
|
||||
'Reimbursement',
|
||||
'Acl',
|
||||
];
|
||||
const DaoAddresses = require('./addresses/dao.json');
|
||||
@@ -121,6 +123,10 @@ class Kredits {
|
||||
return this.contractFor('Contribution');
|
||||
}
|
||||
|
||||
get Reimbursement () {
|
||||
return this.contractFor('Reimbursement');
|
||||
}
|
||||
|
||||
get Acl () {
|
||||
return this.contractFor('Acl');
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ class KreditsKit {
|
||||
|
||||
appIdFor (contractName) {
|
||||
// see appIds in KreditsKit.sol for more details
|
||||
const knownContracts = ['Contribution', 'Contributor', 'Proposal', 'Token'];
|
||||
const knownContracts = ['Contribution', 'Contributor', 'Proposal', 'Reimbursement', 'Token'];
|
||||
return this.contract.appIds(knownContracts.indexOf(contractName));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const schemas = require('kosmos-schemas');
|
||||
const schemas = require('@kosmos/schemas');
|
||||
const validator = require('../utils/validator');
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const schemas = require('kosmos-schemas');
|
||||
const schemas = require('@kosmos/schemas');
|
||||
const validator = require('../utils/validator');
|
||||
/**
|
||||
* Handle serialization for JSON-LD object of the contributor, according to
|
||||
|
||||
100
lib/serializers/expense.js
Normal file
100
lib/serializers/expense.js
Normal file
@@ -0,0 +1,100 @@
|
||||
const schemas = require('@kosmos/schemas');
|
||||
const validator = require('../utils/validator');
|
||||
|
||||
/**
|
||||
* Serialization and validation for JSON-LD document of the Expense
|
||||
*
|
||||
* @class
|
||||
* @public
|
||||
*/
|
||||
class ExpenseSerializer {
|
||||
|
||||
constructor (attrs) {
|
||||
Object.keys(attrs).forEach(a => this[a] = attrs[a]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize object to JSON
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
serialize () {
|
||||
// Write it pretty to ipfs
|
||||
return JSON.stringify(this.data, null, 2);
|
||||
}
|
||||
|
||||
get data () {
|
||||
const {
|
||||
title,
|
||||
description,
|
||||
currency,
|
||||
amount,
|
||||
date,
|
||||
url,
|
||||
tags,
|
||||
details,
|
||||
} = this;
|
||||
|
||||
const data = {
|
||||
'@context': 'https://schema.kosmos.org',
|
||||
'@type': 'Expense',
|
||||
title,
|
||||
description,
|
||||
currency,
|
||||
amount,
|
||||
date,
|
||||
'tags': tags || [],
|
||||
'details': details || {},
|
||||
};
|
||||
|
||||
if (url) {
|
||||
data['url'] = url;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate serialized data against schema
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
validate () {
|
||||
const serialized = JSON.parse(this.serialize());
|
||||
const valid = validator.validate(serialized, schemas['expense']);
|
||||
return valid ? Promise.resolve() : Promise.reject(validator.error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize JSON to object
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
static deserialize (serialized) {
|
||||
const {
|
||||
title,
|
||||
description,
|
||||
currency,
|
||||
amount,
|
||||
date,
|
||||
url,
|
||||
tags,
|
||||
details,
|
||||
} = JSON.parse(serialized.toString('utf8'));
|
||||
|
||||
return {
|
||||
title,
|
||||
description,
|
||||
currency,
|
||||
amount,
|
||||
date,
|
||||
url,
|
||||
tags,
|
||||
details,
|
||||
ipfsData: serialized,
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = ExpenseSerializer;
|
||||
33
package-lock.json
generated
33
package-lock.json
generated
@@ -575,6 +575,14 @@
|
||||
"@ethersproject/strings": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"@kosmos/schemas": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@kosmos/schemas/-/schemas-3.0.0.tgz",
|
||||
"integrity": "sha512-7hnirq0ShsDjENBPjNnu6izwzVjkDzEZ4SKvPzQJJfkeJ/NRVHKvfdn9rkE7CRXTi2hMzsZVFOnlF6D/eYXTCA==",
|
||||
"requires": {
|
||||
"brfs-babel": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"@resolver-engine/core": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@resolver-engine/core/-/core-0.2.1.tgz",
|
||||
@@ -3620,6 +3628,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"ethereum-block-by-date": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/ethereum-block-by-date/-/ethereum-block-by-date-1.4.0.tgz",
|
||||
"integrity": "sha512-3D+6esxU9bQDyFev9/9C6+Wqpb+HdSyeKnAhktIZXOQjgPdGRc4zwr/FdrgMfrJlB9Ee4cG9psu7eCgYQ3szaA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"moment": "^2.29.1"
|
||||
}
|
||||
},
|
||||
"ethereum-common": {
|
||||
"version": "0.0.18",
|
||||
"resolved": "https://registry.npmjs.org/ethereum-common/-/ethereum-common-0.0.18.tgz",
|
||||
@@ -5452,14 +5469,6 @@
|
||||
"graceful-fs": "^4.1.9"
|
||||
}
|
||||
},
|
||||
"kosmos-schemas": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/kosmos-schemas/-/kosmos-schemas-2.2.1.tgz",
|
||||
"integrity": "sha512-bZRbwLmJVeaaLqIyJsHbUf7wCjgHP2IhKXH12fD5+1tcw6/bdtZF4nZ41SoVCmdnZnSXh6Fv8nIRT0HksSlj8w==",
|
||||
"requires": {
|
||||
"brfs-babel": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"ky": {
|
||||
"version": "0.15.0",
|
||||
"resolved": "https://registry.npmjs.org/ky/-/ky-0.15.0.tgz",
|
||||
@@ -6040,6 +6049,12 @@
|
||||
"integrity": "sha512-Yp4o3/ZA15wsXqJTT+R+9w2AYIkD1i80Lds47wDbuUhOvQvm+O2EfjFZSz0pMgZZSPHRhGxgcd2+GL4+jZMtdw==",
|
||||
"dev": true
|
||||
},
|
||||
"moment": {
|
||||
"version": "2.29.1",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",
|
||||
"integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==",
|
||||
"dev": true
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
@@ -9470,7 +9485,7 @@
|
||||
"dev": true
|
||||
},
|
||||
"websocket": {
|
||||
"version": "github:web3-js/WebSocket-Node#905deb4812572b344f5801f8c9ce8bb02799d82e",
|
||||
"version": "github:web3-js/WebSocket-Node#ef5ea2f41daf4a2113b80c9223df884b4d56c400",
|
||||
"from": "github:web3-js/WebSocket-Node#polyfill/globalThis",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^4.2.1",
|
||||
"eth-provider": "^0.2.5",
|
||||
"ethereum-block-by-date": "^1.4.0",
|
||||
"homedir": "^0.6.0",
|
||||
"promptly": "^3.0.3",
|
||||
"solc": "^0.6.8",
|
||||
@@ -62,7 +63,7 @@
|
||||
"dependencies": {
|
||||
"ethers": "^5.0.2",
|
||||
"ipfs-http-client": "^41.0.1",
|
||||
"kosmos-schemas": "^2.2.1",
|
||||
"@kosmos/schemas": "^3.0.0",
|
||||
"node-fetch": "^2.6.0",
|
||||
"tv4": "^1.3.0"
|
||||
},
|
||||
|
||||
@@ -11,6 +11,7 @@ const files = [
|
||||
'Kernel',
|
||||
'Proposal',
|
||||
'Token',
|
||||
'Reimbursement',
|
||||
'ACL'
|
||||
];
|
||||
|
||||
|
||||
49
scripts/export/contributions.js
Normal file
49
scripts/export/contributions.js
Normal file
@@ -0,0 +1,49 @@
|
||||
const fs = require('fs');
|
||||
const ethers = require('ethers');
|
||||
const Kredits = require('../../lib/kredits');
|
||||
const provider = new ethers.providers.JsonRpcProvider('https://rpc.ankr.com/eth_rinkeby');
|
||||
|
||||
const arapp = require('../../arapp.json');
|
||||
const apm = arapp.environments['rinkeby'].apm;
|
||||
|
||||
async function main() {
|
||||
const kredits = await new Kredits(provider, null, { apm });
|
||||
//kredits = new Kredits(hre.ethers.provider, hre.ethers.provider.getSigner())
|
||||
await kredits.init();
|
||||
|
||||
console.log(`Using Contribution at: ${kredits.Contribution.contract.address}`);
|
||||
|
||||
const count = await kredits.Contribution.count;
|
||||
const currentBlockHeight = await provider.getBlockNumber();
|
||||
|
||||
const backup = {};
|
||||
const promises = [];
|
||||
for (let i = 1; i <= count; i++) {
|
||||
promises.push(new Promise((resolve, reject) => {
|
||||
setTimeout(async () => {
|
||||
console.log(`Loading contribution #${i}`);
|
||||
await kredits.Contribution.contract.getContribution(i).then(contractData => {
|
||||
backup[i] = {
|
||||
amount: contractData.amount,
|
||||
contributorId: contractData.contributorId,
|
||||
hashDigest: contractData.hashDigest,
|
||||
hashFunction: contractData.hashFunction,
|
||||
hashSize: contractData.hashSize,
|
||||
confirmedAtBlock: contractData.confirmedAtBlock,
|
||||
confirmed: contractData.confirmedAtBlock <= currentBlockHeight,
|
||||
vetoed: contractData.vetoed,
|
||||
id: contractData.id,
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
}, 100 * i);
|
||||
}));
|
||||
}
|
||||
|
||||
await Promise.all(promises).then(() => {
|
||||
fs.writeFileSync("./data/contributions.json", JSON.stringify(backup, null, 2));
|
||||
console.log("Exported");
|
||||
});
|
||||
}
|
||||
|
||||
main();
|
||||
44
scripts/export/contributors.js
Normal file
44
scripts/export/contributors.js
Normal file
@@ -0,0 +1,44 @@
|
||||
const fs = require('fs');
|
||||
const ethers = require('ethers');
|
||||
const Kredits = require('../../lib/kredits');
|
||||
const provider = new ethers.providers.JsonRpcProvider('https://rpc.ankr.com/eth_rinkeby');
|
||||
|
||||
const arapp = require('../../arapp.json');
|
||||
const apm = arapp.environments['rinkeby'].apm;
|
||||
|
||||
async function main() {
|
||||
const kredits = await new Kredits(provider, null, { apm });
|
||||
//kredits = new Kredits(hre.ethers.provider, hre.ethers.provider.getSigner())
|
||||
await kredits.init();
|
||||
|
||||
console.log(`Using Contributor at: ${kredits.Contributor.contract.address}`);
|
||||
|
||||
const count = await kredits.Contributor.count;
|
||||
|
||||
const backup = {};
|
||||
const promises = [];
|
||||
for (let i = 1; i <= count; i++) {
|
||||
promises.push(new Promise((resolve, reject) => {
|
||||
setTimeout(async () => {
|
||||
console.log(`Loading contributor #${i}`);
|
||||
await kredits.Contributor.contract.getContributorById(i).then(contractData => {
|
||||
backup[i] = {
|
||||
account: contractData.account,
|
||||
hashDigest: contractData.hashDigest,
|
||||
hashFunction: contractData.hashFunction,
|
||||
hashSize: contractData.hashSize,
|
||||
id: contractData.id,
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
}, 100 * i);
|
||||
}));
|
||||
}
|
||||
|
||||
await Promise.all(promises).then(() => {
|
||||
fs.writeFileSync("./data/contributors.json", JSON.stringify(backup, null, 2));
|
||||
console.log("Exported");
|
||||
});
|
||||
}
|
||||
|
||||
main();
|
||||
19
scripts/find-block-for-date.js
Normal file
19
scripts/find-block-for-date.js
Normal file
@@ -0,0 +1,19 @@
|
||||
const promptly = require('promptly');
|
||||
const EthDater = require('ethereum-block-by-date');
|
||||
const initKredits = require('./helpers/init_kredits.js');
|
||||
|
||||
module.exports = async function(callback) {
|
||||
let kredits;
|
||||
try { kredits = await initKredits(web3); } catch(e) { callback(e); return; }
|
||||
|
||||
const dater = new EthDater(kredits.provider);
|
||||
const dateStr = await promptly.prompt('Specify a date and time (e.g. 2021-05-07T14:00:40Z): ');
|
||||
const blockData = await dater.getDate(dateStr, true);
|
||||
|
||||
console.log(`
|
||||
The closest block is #${blockData.block}:
|
||||
https://rinkeby.etherscan.io/block/${blockData.block}
|
||||
`);
|
||||
|
||||
callback();
|
||||
}
|
||||
76
scripts/list-contributions-per-contributor.js
Normal file
76
scripts/list-contributions-per-contributor.js
Normal file
@@ -0,0 +1,76 @@
|
||||
const promptly = require('promptly');
|
||||
const Table = require('cli-table');
|
||||
|
||||
const initKredits = require('./helpers/init_kredits.js');
|
||||
|
||||
module.exports = async function(callback) {
|
||||
let kredits;
|
||||
try {
|
||||
kredits = await initKredits(web3);
|
||||
} catch(e) {
|
||||
callback(e);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Using Contribution at: ${kredits.Contribution.contract.address}`);
|
||||
|
||||
const table = new Table({
|
||||
head: ['ID', 'Name', 'Kredits']
|
||||
})
|
||||
|
||||
try {
|
||||
let currentBlockNumber = await kredits.provider.getBlockNumber();
|
||||
console.log(`Current block number: ${currentBlockNumber}`);
|
||||
|
||||
let confirmedBeforeBlock = await promptly.prompt('Before block: ');
|
||||
let confirmedAfterBlock = await promptly.prompt('After block: ');
|
||||
|
||||
let tokens = {};
|
||||
let contributors = await kredits.Contributor.all();
|
||||
contributors.forEach(c => {
|
||||
tokens[c.id] = { amount: 0, contributor: c };
|
||||
});
|
||||
|
||||
let contributionId = await kredits.Contribution.contract.contributionsCount();
|
||||
let nextContribution = true;
|
||||
|
||||
while (nextContribution) {
|
||||
console.log(`Getting contribution: ${contributionId}`);
|
||||
let contribution = await kredits.Contribution.getById(contributionId);
|
||||
contributionId = contributionId - 1;
|
||||
|
||||
// if no conribution is found
|
||||
if (!contribution.exists) {
|
||||
nextContribution = false;
|
||||
break;
|
||||
}
|
||||
// check if the contribution is older
|
||||
// in that case we assume all other contributions now are older
|
||||
if (contribution.confirmedAtBlock < confirmedAfterBlock) {
|
||||
nextContribution = false;
|
||||
}
|
||||
|
||||
// if the contribution is within the range count it
|
||||
if (!contribution.vetoed && contribution.confirmedAtBlock < confirmedBeforeBlock && contribution.confirmedAtBlock > confirmedAfterBlock) {
|
||||
// init
|
||||
tokens[contribution.contributorId].amount = tokens[contribution.contributorId].amount + contribution.amount;
|
||||
}
|
||||
}
|
||||
|
||||
Object.keys(tokens).forEach((contributorId) => {
|
||||
table.push([
|
||||
contributorId,
|
||||
`${tokens[contributorId].contributor.name}`,
|
||||
`${tokens[contributorId].amount}`
|
||||
]);
|
||||
});
|
||||
|
||||
const total = Object.keys(tokens).map(cid => { return tokens[cid].amount}).reduce((a,b) => { return a+b }, 0);
|
||||
console.log(`Total confirmed Kredits: ${total} between block ${confirmedAfterBlock} and ${confirmedBeforeBlock}`);
|
||||
console.log(table.toString());
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
callback();
|
||||
}
|
||||
52
scripts/list-reimbursements.js
Normal file
52
scripts/list-reimbursements.js
Normal file
@@ -0,0 +1,52 @@
|
||||
const promptly = require('promptly');
|
||||
const Table = require('cli-table');
|
||||
|
||||
const initKredits = require('./helpers/init_kredits.js');
|
||||
|
||||
module.exports = async function(callback) {
|
||||
let kredits;
|
||||
try {
|
||||
kredits = await initKredits(web3);
|
||||
} catch(e) {
|
||||
callback(e);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Using Reimbursement at: ${kredits.Reimbursement.contract.address}`);
|
||||
|
||||
const table = new Table({
|
||||
head: ['ID', 'Amount', 'Token', 'recipientId', 'Confirmed?', 'Vetoed?', 'IPFS', 'Expenses']
|
||||
})
|
||||
|
||||
try {
|
||||
let blockNumber = await kredits.provider.getBlockNumber();
|
||||
let reimbursements = await kredits.Reimbursement.all({page: {size: 1000}});
|
||||
|
||||
let kreditsSum = 0;
|
||||
console.log(`Current block number: ${blockNumber}`);
|
||||
reimbursements.forEach(r => {
|
||||
const confirmed = r.confirmedAtBlock <= blockNumber;
|
||||
|
||||
table.push([
|
||||
r.id.toString(),
|
||||
r.amount.toString(),
|
||||
`${r.token}`,
|
||||
`${r.recipientId}`,
|
||||
`${confirmed}`,
|
||||
`${r.vetoed}`,
|
||||
`${r.ipfsHash}`,
|
||||
`${r.expenses.length}`
|
||||
]);
|
||||
});
|
||||
|
||||
console.log(table.toString());
|
||||
|
||||
let totalAmountUnconfirmed = await kredits.Reimbursement.functions.totalAmount(false);
|
||||
let totalAmountConfirmed = await kredits.Reimbursement.functions.totalAmount(true);
|
||||
console.log(`Total: ${totalAmountConfirmed} (confirmed) | ${totalAmountUnconfirmed} (including unconfirmed)`);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
callback();
|
||||
}
|
||||
Reference in New Issue
Block a user