diff --git a/apps/reimbursement/contracts/Reimbursement.sol b/apps/reimbursement/contracts/Reimbursement.sol index f81b173..3b361d5 100644 --- a/apps/reimbursement/contracts/Reimbursement.sol +++ b/apps/reimbursement/contracts/Reimbursement.sol @@ -60,12 +60,13 @@ contract Reimbursement is AragonApp { ); } - function add(uint256 amount, address token, bytes32 hashDigest, uint8 hashFunction, uint8 hashSize) public isInitialized auth(ADD_REIMBURSEMENT_ROLE) { + function add(uint256 amount, address token, address recipient, 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.recipient = recipient; r.hashDigest = hashDigest; r.hashFunction = hashFunction; r.hashSize = hashSize; diff --git a/config/seeds.js b/config/seeds.js index 1030dce..9a41044 100644 --- a/config/seeds.js +++ b/config/seeds.js @@ -39,7 +39,8 @@ const contractCalls = [ ['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 }]], - ['Reimbursement', 'add', [{title: 'Server Hosting', description: 'All the serverz', amount: 1, token: '0xa3048576e296207eb0141f2803590ad044f81928', currency: 'WBTC', date: '2020-05-28'}, { gasLimit: 300000 }]], + ['Reimbursement', 'add', [{amount: 100, recipient: '0x7e8f313c56f809188313aa274fa67ee58c31515d', token: '0xa3048576e296207eb0141f2803590ad044f81928', expenses: [{title: 'Server Hosting', description: 'All the serverz', amount: 100, currency: 'EUR', date: '2020-05-28'}]}, { gasLimit: 300000 }]], + ['Reimbursement', 'add', [{amount: 10, recipient: '0xa502eb4021f3b9ab62f75b57a94e1cfbf81fd827', token: '0xa3048576e296207eb0141f2803590ad044f81928', expenses: [{title: 'Domain', description: 'All the domain', amount: 10, currency: 'EUR', date: '2020-05-28'}]}, { gasLimit: 300000 }]], ]; const funds = [ diff --git a/lib/abis/Reimbursement.json b/lib/abis/Reimbursement.json index 952b3c3..4f89176 100644 --- a/lib/abis/Reimbursement.json +++ b/lib/abis/Reimbursement.json @@ -1 +1 @@ -[{"constant":true,"inputs":[],"name":"hasInitialized","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_script","type":"bytes"}],"name":"getEVMScriptExecutor","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getRecoveryVault","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"reimbursementsCount","outputs":[{"name":"","type":"uint32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"VETO_REIMBURSEMENT_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"token","type":"address"}],"name":"allowRecoverability","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"appId","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"ADD_REIMBURSEMENT_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getInitializationBlock","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"KERNEL_APP_ADDR_NAMESPACE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_token","type":"address"}],"name":"transferToVault","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_sender","type":"address"},{"name":"_role","type":"bytes32"},{"name":"_params","type":"uint256[]"}],"name":"canPerform","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getEVMScriptRegistry","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint32"}],"name":"reimbursements","outputs":[{"name":"recipient","type":"address"},{"name":"amount","type":"uint256"},{"name":"token","type":"address"},{"name":"claimed","type":"bool"},{"name":"hashDigest","type":"bytes32"},{"name":"hashFunction","type":"uint8"},{"name":"hashSize","type":"uint8"},{"name":"confirmedAtBlock","type":"uint256"},{"name":"vetoed","type":"bool"},{"name":"exists","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"kernel","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"blocksToWait","outputs":[{"name":"","type":"uint32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isPetrified","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"appIds","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"id","type":"uint32"},{"indexed":true,"name":"addedByAccont","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"ReimbursementAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"id","type":"uint32"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"ReimbursementClaimed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"id","type":"uint32"},{"indexed":false,"name":"vetoedByAccount","type":"address"}],"name":"ReimbursementVetoed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"executor","type":"address"},{"indexed":false,"name":"script","type":"bytes"},{"indexed":false,"name":"input","type":"bytes"},{"indexed":false,"name":"returnData","type":"bytes"}],"name":"ScriptResult","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"vault","type":"address"},{"indexed":true,"name":"token","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"RecoverToVault","type":"event"},{"constant":false,"inputs":[{"name":"_appIds","type":"bytes32[5]"}],"name":"initialize","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"appId","type":"uint8"}],"name":"getContract","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"confirmedOnly","type":"bool"}],"name":"totalAmount","outputs":[{"name":"amount","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"reimbursementId","type":"uint32"}],"name":"getReimbursement","outputs":[{"name":"id","type":"uint32"},{"name":"recipient","type":"address"},{"name":"amount","type":"uint256"},{"name":"token","type":"address"},{"name":"claimed","type":"bool"},{"name":"hashDigest","type":"bytes32"},{"name":"hashFunction","type":"uint8"},{"name":"hashSize","type":"uint8"},{"name":"confirmedAtBlock","type":"uint256"},{"name":"exists","type":"bool"},{"name":"vetoed","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"amount","type":"uint256"},{"name":"token","type":"address"},{"name":"hashDigest","type":"bytes32"},{"name":"hashFunction","type":"uint8"},{"name":"hashSize","type":"uint8"}],"name":"add","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"reimbursementId","type":"uint32"}],"name":"veto","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"reimbursementId","type":"uint32"}],"name":"claim","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"reimbursementId","type":"uint32"}],"name":"exists","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"}] \ No newline at end of file +[{"constant":true,"inputs":[],"name":"hasInitialized","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_script","type":"bytes"}],"name":"getEVMScriptExecutor","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getRecoveryVault","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"reimbursementsCount","outputs":[{"name":"","type":"uint32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"VETO_REIMBURSEMENT_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"token","type":"address"}],"name":"allowRecoverability","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"appId","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"ADD_REIMBURSEMENT_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getInitializationBlock","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_token","type":"address"}],"name":"transferToVault","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_sender","type":"address"},{"name":"_role","type":"bytes32"},{"name":"_params","type":"uint256[]"}],"name":"canPerform","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getEVMScriptRegistry","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint32"}],"name":"reimbursements","outputs":[{"name":"recipient","type":"address"},{"name":"amount","type":"uint256"},{"name":"token","type":"address"},{"name":"hashDigest","type":"bytes32"},{"name":"hashFunction","type":"uint8"},{"name":"hashSize","type":"uint8"},{"name":"confirmedAtBlock","type":"uint256"},{"name":"vetoed","type":"bool"},{"name":"exists","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"kernel","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"blocksToWait","outputs":[{"name":"","type":"uint32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isPetrified","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"id","type":"uint32"},{"indexed":true,"name":"addedByAccount","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"ReimbursementAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"id","type":"uint32"},{"indexed":false,"name":"vetoedByAccount","type":"address"}],"name":"ReimbursementVetoed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"executor","type":"address"},{"indexed":false,"name":"script","type":"bytes"},{"indexed":false,"name":"input","type":"bytes"},{"indexed":false,"name":"returnData","type":"bytes"}],"name":"ScriptResult","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"vault","type":"address"},{"indexed":true,"name":"token","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"RecoverToVault","type":"event"},{"constant":false,"inputs":[{"name":"_appIds","type":"bytes32[5]"}],"name":"initialize","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"confirmedOnly","type":"bool"}],"name":"totalAmount","outputs":[{"name":"amount","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"reimbursementId","type":"uint32"}],"name":"get","outputs":[{"name":"id","type":"uint32"},{"name":"recipient","type":"address"},{"name":"amount","type":"uint256"},{"name":"token","type":"address"},{"name":"hashDigest","type":"bytes32"},{"name":"hashFunction","type":"uint8"},{"name":"hashSize","type":"uint8"},{"name":"confirmedAtBlock","type":"uint256"},{"name":"exists","type":"bool"},{"name":"vetoed","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"amount","type":"uint256"},{"name":"token","type":"address"},{"name":"recipient","type":"address"},{"name":"hashDigest","type":"bytes32"},{"name":"hashFunction","type":"uint8"},{"name":"hashSize","type":"uint8"}],"name":"add","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"reimbursementId","type":"uint32"}],"name":"veto","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"reimbursementId","type":"uint32"}],"name":"exists","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/lib/contracts/reimbursement.js b/lib/contracts/reimbursement.js index 0d4d12d..f17cd98 100644 --- a/lib/contracts/reimbursement.js +++ b/lib/contracts/reimbursement.js @@ -9,7 +9,10 @@ class Reimbursement extends Record { getById (id) { return this.functions.get(id) .then(data => { - return this.ipfs.catAndMerge(data, ExpenseSerializer.deserialize); + return this.ipfs.catAndMerge(data, (ipfsDocument) => { + const expenses = JSON.parse(ipfsDocument); + return { expenses }; + }); }); } @@ -18,26 +21,32 @@ class Reimbursement extends Record { } async add (attrs, callOptions = {}) { - const reimbursement = new ExpenseSerializer(attrs); + const amount = parseInt(attrs.amount); + const token = attrs.token; + const recipient = attrs.recipient; + const expenses = attrs.expenses.map( e => new ExpenseSerializer(e) ); - try { await reimbursement.validate(); } - catch (error) { return Promise.reject(error); } + if (!amount > 0 || !token || token === '' || !recipient || recipient === '' || !expenses.length > 0) { + return Promise.reject(new Error('Invalid data. amount, token, expenses is required.')); + } - const jsonStr = reimbursement.serialize(); + 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 => { + let reimbursement = [ + amount, + token, + recipient, + ipfsHashAttr.hashDigest, + ipfsHashAttr.hashFunction, + ipfsHashAttr.hashSize, + ]; - return this.ipfs - .add(jsonStr) - .then(ipfsHashAttr => { - let reimbursement = [ - attrs.amount, - attrs.token, - ipfsHashAttr.hashDigest, - ipfsHashAttr.hashFunction, - ipfsHashAttr.hashSize, - ]; - - console.log(reimbursement) - return this.functions.add(...reimbursement, callOptions); + return this.functions.add(...reimbursement, callOptions); + }); }); } } diff --git a/lib/serializers/expense.js b/lib/serializers/expense.js index 0beb898..9309122 100644 --- a/lib/serializers/expense.js +++ b/lib/serializers/expense.js @@ -20,6 +20,11 @@ class Expense { * @public */ serialize () { + // Write it pretty to ipfs + return JSON.stringify(this.data, null, 2); + } + + get data () { let { title, description, @@ -47,8 +52,7 @@ class Expense { data['url'] = url; } - // Write it pretty to ipfs - return JSON.stringify(data, null, 2); + return data; } /** @@ -58,7 +62,6 @@ class Expense { */ validate () { const serialized = JSON.parse(this.serialize()); - console.log(schemas['expense']); const valid = validator.validate(serialized, schemas['expense']); return valid ? Promise.resolve() : Promise.reject(validator.error); } diff --git a/scripts/list-reimbursements.js b/scripts/list-reimbursements.js index 6e350ba..d82046b 100644 --- a/scripts/list-reimbursements.js +++ b/scripts/list-reimbursements.js @@ -15,7 +15,7 @@ module.exports = async function(callback) { console.log(`Using Reimbursement at: ${kredits.Reimbursement.contract.address}`); const table = new Table({ - head: ['ID', 'Title', 'Description', 'Amount', 'Token', 'Recipent', 'Confirmed?', 'Vetoed?', 'Claimed?', 'IPFS'] + head: ['ID', 'Amount', 'Token', 'Recipent', 'Confirmed?', 'Vetoed?', 'IPFS', 'Expenses'] }) try { @@ -24,21 +24,19 @@ module.exports = async function(callback) { let kreditsSum = 0; console.log(`Current block number: ${blockNumber}`); - reimbursements.forEach((r) => { + reimbursements.forEach(r => { const confirmed = r.confirmedAtBlock <= blockNumber; table.push([ r.id.toString(), - `${r.title}`, - `${r.description}`, r.amount.toString(), `${r.token}`, `${r.recipient}`, - `${confirmed} (${r.confirmedAtBlock})`, - r.vetoed, - r.claimed, - r.ipfsHash - ]) + `${confirmed}`, + `${r.vetoed}`, + `${r.ipfsHash}`, + `${r.expenses.length}` + ]); }); console.log(table.toString());