diff --git a/apps/finance/.solcover.js b/apps/finance/.solcover.js index 2c23259eea..eeeff05fb9 100644 --- a/apps/finance/.solcover.js +++ b/apps/finance/.solcover.js @@ -1,7 +1,8 @@ module.exports = { norpc: true, - copyPackages: ['@aragon/os', '@aragon/apps-vault'], + copyPackages: ['@aragon/os', '@aragon/apps-vault', '@aragon/test-helpers'], skipFiles: [ 'test', + '@aragon/test-helpers/contracts/TimeHelpersMock.sol', ] } diff --git a/apps/finance/contracts/test/mocks/FinanceMock.sol b/apps/finance/contracts/test/mocks/FinanceMock.sol index 9a6b029fc9..f44006560c 100644 --- a/apps/finance/contracts/test/mocks/FinanceMock.sol +++ b/apps/finance/contracts/test/mocks/FinanceMock.sol @@ -1,17 +1,15 @@ pragma solidity 0.4.24; import "../../Finance.sol"; +import "@aragon/test-helpers/contracts/TimeHelpersMock.sol"; -contract FinanceMock is Finance { - uint64 mockTime; +contract FinanceMock is Finance, TimeHelpersMock { uint64 mockMaxPeriodTransitions = MAX_UINT64; + function getMaxUint64() public pure returns (uint64) { return MAX_UINT64; } + function mock_setMaxPeriodTransitions(uint64 i) public { mockMaxPeriodTransitions = i; } - function mock_setTimestamp(uint64 i) public { mockTime = i; } function getMaxPeriodTransitions() internal view returns (uint64) { return mockMaxPeriodTransitions; } - function getTimestamp64() internal view returns (uint64) { return mockTime; } - - function getMaxUint64() public pure returns (uint64) { return MAX_UINT64; } } diff --git a/apps/finance/test/finance.js b/apps/finance/test/finance.js index 253968441f..d749276426 100644 --- a/apps/finance/test/finance.js +++ b/apps/finance/test/finance.js @@ -34,7 +34,7 @@ const tokenTestGroups = [ ] contract('Finance App', accounts => { - let daoFact, financeBase, finance, vaultBase, vault, token1, token2, executionTarget, etherToken = {} + let daoFact, financeBase, finance, vaultBase, vault, token1, token2 let ETH, MAX_UINT64, ANY_ENTITY, APP_MANAGER_ROLE let CREATE_PAYMENTS_ROLE, CHANGE_PERIOD_ROLE, CHANGE_BUDGETS_ROLE, EXECUTE_PAYMENTS_ROLE, MANAGE_PAYMENTS_ROLE @@ -42,43 +42,44 @@ contract('Finance App', accounts => { // Error strings const errors = makeErrorMappingProxy({ - // aragonOS errors - APP_AUTH_FAILED: 'APP_AUTH_FAILED', - INIT_ALREADY_INITIALIZED: 'INIT_ALREADY_INITIALIZED', - INIT_NOT_INITIALIZED: 'INIT_NOT_INITIALIZED', - RECOVER_DISALLOWED: 'RECOVER_DISALLOWED', - - // Vault errors - VAULT_TOKEN_TRANSFER_REVERTED: 'VAULT_TOKEN_TRANSFER_REVERTED', - - // Finance errors - FINANCE_BUDGET: 'FINANCE_BUDGET', - FINANCE_COMPLETE_TRANSITION: 'FINANCE_COMPLETE_TRANSITION', - FINANCE_DEPOSIT_AMOUNT_ZERO: 'FINANCE_DEPOSIT_AMOUNT_ZERO', - FINANCE_ETH_VALUE_MISMATCH: 'FINANCE_ETH_VALUE_MISMATCH', - FINANCE_EXECUTE_PAYMENT_NUM: 'FINANCE_EXECUTE_PAYMENT_NUM', - FINANCE_EXECUTE_PAYMENT_TIME: 'FINANCE_EXECUTE_PAYMENT_TIME', - FINANCE_SET_PERIOD_TOO_SHORT: 'FINANCE_SET_PERIOD_TOO_SHORT', - FINANCE_NEW_PAYMENT_AMOUNT_ZERO: 'FINANCE_NEW_PAYMENT_AMOUNT_ZERO', - FINANCE_NEW_PAYMENT_EXECS_ZERO: 'FINANCE_NEW_PAYMENT_EXECS_ZERO', - FINANCE_NEW_PAYMENT_IMMEDIATE: 'FINANCE_NEW_PAYMENT_IMMEDIATE', - FINANCE_NEW_PAYMENT_INTRVL_ZERO: 'FINANCE_NEW_PAYMENT_INTRVL_ZERO', - FINANCE_NO_SCHEDULED_PAYMENT: 'FINANCE_NO_SCHEDULED_PAYMENT', - FINANCE_NO_PERIOD: 'FINANCE_NO_PERIOD', - FINANCE_NO_TRANSACTION: 'FINANCE_NO_TRANSACTION', - FINANCE_PAYMENT_INACTIVE: 'FINANCE_PAYMENT_INACTIVE', - FINANCE_PAYMENT_RECEIVER: 'FINANCE_PAYMENT_RECEIVER', - FINANCE_RECOVER_AMOUNT_ZERO: 'FINANCE_RECOVER_AMOUNT_ZERO', - FINANCE_REMAINING_BUDGET: 'FINANCE_REMAINING_BUDGET', - FINANCE_VAULT_NOT_CONTRACT: 'FINANCE_VAULT_NOT_CONTRACT', + // aragonOS errors + APP_AUTH_FAILED: 'APP_AUTH_FAILED', + INIT_ALREADY_INITIALIZED: 'INIT_ALREADY_INITIALIZED', + INIT_NOT_INITIALIZED: 'INIT_NOT_INITIALIZED', + RECOVER_DISALLOWED: 'RECOVER_DISALLOWED', + + // Vault errors + VAULT_TOKEN_TRANSFER_REVERTED: 'VAULT_TOKEN_TRANSFER_REVERTED', + + // Finance errors + FINANCE_BUDGET: 'FINANCE_BUDGET', + FINANCE_COMPLETE_TRANSITION: 'FINANCE_COMPLETE_TRANSITION', + FINANCE_DEPOSIT_AMOUNT_ZERO: 'FINANCE_DEPOSIT_AMOUNT_ZERO', + FINANCE_ETH_VALUE_MISMATCH: 'FINANCE_ETH_VALUE_MISMATCH', + FINANCE_EXECUTE_PAYMENT_NUM: 'FINANCE_EXECUTE_PAYMENT_NUM', + FINANCE_EXECUTE_PAYMENT_TIME: 'FINANCE_EXECUTE_PAYMENT_TIME', + FINANCE_SET_PERIOD_TOO_SHORT: 'FINANCE_SET_PERIOD_TOO_SHORT', + FINANCE_NEW_PAYMENT_AMOUNT_ZERO: 'FINANCE_NEW_PAYMENT_AMOUNT_ZERO', + FINANCE_NEW_PAYMENT_EXECS_ZERO: 'FINANCE_NEW_PAYMENT_EXECS_ZERO', + FINANCE_NEW_PAYMENT_IMMEDIATE: 'FINANCE_NEW_PAYMENT_IMMEDIATE', + FINANCE_NEW_PAYMENT_INTRVL_ZERO: 'FINANCE_NEW_PAYMENT_INTRVL_ZERO', + FINANCE_NO_SCHEDULED_PAYMENT: 'FINANCE_NO_SCHEDULED_PAYMENT', + FINANCE_NO_PERIOD: 'FINANCE_NO_PERIOD', + FINANCE_NO_TRANSACTION: 'FINANCE_NO_TRANSACTION', + FINANCE_PAYMENT_INACTIVE: 'FINANCE_PAYMENT_INACTIVE', + FINANCE_PAYMENT_RECEIVER: 'FINANCE_PAYMENT_RECEIVER', + FINANCE_RECOVER_AMOUNT_ZERO: 'FINANCE_RECOVER_AMOUNT_ZERO', + FINANCE_REMAINING_BUDGET: 'FINANCE_REMAINING_BUDGET', + FINANCE_VAULT_NOT_CONTRACT: 'FINANCE_VAULT_NOT_CONTRACT', }) const root = accounts[0] const recipient = accounts[1] const n = '0x00' - const START_TIME = 1 - const PERIOD_DURATION = 60 * 60 * 24 // One day in seconds + const NOW = 1 + const ONE_DAY = 60 * 60 * 24 // One day in seconds + const PERIOD_DURATION = ONE_DAY const withdrawAddr = '0x0000000000000000000000000000000000001234' const VAULT_INITIAL_ETH_BALANCE = 400 const VAULT_INITIAL_TOKEN1_BALANCE = 100 @@ -129,7 +130,7 @@ contract('Finance App', accounts => { // finance const receipt2 = await dao.newAppInstance('0x5678', financeBase.address, '0x', false, { from: root }) const financeApp = Finance.at(receipt2.logs.filter(l => l.event == 'NewAppProxy')[0].args.proxy) - await financeApp.mock_setTimestamp(START_TIME) + await financeApp.mockSetTimestamp(NOW) await financeApp.mock_setMaxPeriodTransitions(MAX_UINT64) await acl.createPermission(root, financeApp.address, CREATE_PAYMENTS_ROLE, root, { from: root }) @@ -177,7 +178,7 @@ contract('Finance App', accounts => { it('sets the end of time correctly', async () => { const { financeApp } = await newProxyFinance() - await financeApp.mock_setTimestamp(100) // to make sure it overflows with MAX_UINT64 period length + await financeApp.mockIncreaseTime(100) // to make sure it overflows with MAX_UINT64 period length // initialize with MAX_UINT64 as period duration await financeApp.initialize(vault.address, MAX_UINT64) const [isCurrent, start, end, firstTx, lastTx] = await financeApp.getPeriod(await financeApp.currentPeriodId()) @@ -220,19 +221,16 @@ contract('Finance App', accounts => { }) it('before setting budget allows unlimited spending', async () => { - const time = 22 const amount = 190 - await finance.mock_setTimestamp(time) - await finance.newImmediatePayment(token2.address, recipient, amount, '') assert.equal((await token2.balanceOf(recipient)).valueOf(), amount, 'recipient should have received tokens') }) it('can change period duration', async () => { - const newDuration = 60 * 60 * 24 * 2 // two days - await finance.setPeriodDuration(newDuration) - await finance.mock_setTimestamp(newDuration * 2.5) // Force at least two transitions + const NEW_PERIOD_DURATION = ONE_DAY * 2 // two days + await finance.setPeriodDuration(NEW_PERIOD_DURATION) + await finance.mockSetTimestamp(NEW_PERIOD_DURATION * 2.5) // Force at least two transitions await finance.tryTransitionAccountingPeriod(3) // transition a maximum of 3 accounting periods @@ -240,7 +238,7 @@ contract('Finance App', accounts => { }) it('can transition periods', async () => { - await finance.mock_setTimestamp(PERIOD_DURATION * 2.5) // Force at least two transitions + await finance.mockIncreaseTime(PERIOD_DURATION * 2.5) // Force at least two transitions await finance.tryTransitionAccountingPeriod(3) // transition a maximum of 3 accounting periods @@ -248,7 +246,7 @@ contract('Finance App', accounts => { }) it('only transitions as many periods as allowed', async () => { - await finance.mock_setTimestamp(PERIOD_DURATION * 2.5) // Force at least two transitions + await finance.mockIncreaseTime(PERIOD_DURATION * 2.5) // Force at least two transitions const receipt = await finance.tryTransitionAccountingPeriod(1) // Fail if we only allow a single transition const newPeriodEvents = receipt.logs.filter(log => log.event == 'NewPeriod') @@ -433,27 +431,23 @@ contract('Finance App', accounts => { }) context('setting budget', () => { - const time = START_TIME + 21 - beforeEach(async () => { await finance.setBudget(token1.address, 50) await finance.setBudget(token2.address, 100) await finance.setBudget(ETH, 150) - - await finance.mock_setTimestamp(time) }) it('records payment', async () => { const amount = 10 // executes up to 10 times every 2 seconds - const receipt = await finance.newScheduledPayment(token1.address, recipient, amount, time, 2, 10, 'ref') + const receipt = await finance.newScheduledPayment(token1.address, recipient, amount, NOW, 2, 10, 'ref') const [token, receiver, txAmount, initialTime, interval, maxExecutions, disabled, executions, createdBy] = await finance.getPayment(1) assert.equal(token, token1.address, 'token address should match') assert.equal(receiver, recipient, 'receiver should match') assert.equal(amount, txAmount, 'amount should match') - assert.equal(initialTime, time, 'time should match') + assert.equal(initialTime, NOW, 'time should match') assert.equal(interval, 2, 'interval should match') assert.equal(maxExecutions, 10, 'max executionss should match') assert.equal(getEventData(receipt, 'NewPayment', 'reference'), 'ref', 'ref should match') @@ -465,7 +459,7 @@ contract('Finance App', accounts => { it('fails trying to get payment out of bounds', async () => { const amount = 10 // executes up to 10 times every 2 seconds - await finance.newScheduledPayment(token1.address, recipient, amount, time, 2, 10, 'ref') + await finance.newScheduledPayment(token1.address, recipient, amount, NOW, 2, 10, 'ref') await assertRevert(finance.getPayment(0), errors.FINANCE_NO_SCHEDULED_PAYMENT) await assertRevert(finance.getPayment(2), errors.FINANCE_NO_SCHEDULED_PAYMENT) @@ -474,7 +468,7 @@ contract('Finance App', accounts => { it('fails trying to get transaction out of bounds', async () => { const amount = 10 // executes up to 10 times every 2 seconds - await finance.newScheduledPayment(token1.address, recipient, amount, time, 2, 10, 'ref') + await finance.newScheduledPayment(token1.address, recipient, amount, NOW, 2, 10, 'ref') await assertRevert(finance.getTransaction(2), errors.FINANCE_NO_TRANSACTION) }) @@ -494,7 +488,7 @@ contract('Finance App', accounts => { assert.equal(token, token1.address, 'token address should match') assert.equal(entity, recipient, 'receiver should match') assert.isFalse(isIncoming, 'single payment should be outgoing') - assert.equal(date.valueOf(), time, 'date should be correct') + assert.equal(date.valueOf(), NOW, 'date should be correct') assert.equal(getEventData(receipt, 'NewTransaction', 'reference'), 'ref', 'ref should match') }) @@ -530,22 +524,22 @@ contract('Finance App', accounts => { const amount = 10 // executes up to 10 times every 2 seconds - const firstReceipt = await finance.newScheduledPayment(token1.address, recipient, amount, time, 2, 10, '') - await finance.mock_setTimestamp(time + 4) + const firstReceipt = await finance.newScheduledPayment(token1.address, recipient, amount, NOW, 2, 10, '') + await finance.mockIncreaseTime(4) const secondReceipt = await finance.executePayment(1) assert.equal((await token1.balanceOf(recipient)).valueOf(), amount * 3, 'recipient should have received tokens') - assert.equal(await finance.nextPaymentTime(1), time + 4 + 2, 'payment should be executed again in 2') + assert.equal(await finance.nextPaymentTime(1), NOW + 4 + 2, 'payment should be executed again in 2') return Promise.all([firstReceipt, secondReceipt].map(async (receipt, index) => { - const executionNum = index + 1 + const executionNum = index + 1 - const transactionId = receipt.logs.filter(log => log.event == 'NewTransaction')[0].args.transactionId - const [periodId, txAmount, paymentId, paymentExecutionNumber, token, entity, incoming, date] = await finance.getTransaction(transactionId) + const transactionId = receipt.logs.filter(log => log.event == 'NewTransaction')[0].args.transactionId + const [periodId, txAmount, paymentId, paymentExecutionNumber, token, entity, incoming, date] = await finance.getTransaction(transactionId) - assert.equal(txAmount, amount, 'amount should be correct') - assert.equal(paymentId, 1, 'payment id should be 1') - assert.equal(paymentExecutionNumber.valueOf(), executionNum, `payment execution number should be ${executionNum}`) + assert.equal(txAmount, amount, 'amount should be correct') + assert.equal(paymentId, 1, 'payment id should be 1') + assert.equal(paymentExecutionNumber.valueOf(), executionNum, `payment execution number should be ${executionNum}`) })) }) @@ -553,8 +547,8 @@ contract('Finance App', accounts => { const amount = 10 // executes up to 10 times every 2 seconds - await finance.newScheduledPayment(ETH, withdrawAddr, amount, time, 2, 10, '') - await finance.mock_setTimestamp(time + 4) + await finance.newScheduledPayment(ETH, withdrawAddr, amount, NOW, 2, 10, '') + await finance.mockIncreaseTime(4) await finance.executePayment(1) assert.equal((await getBalance(withdrawAddr)).valueOf(), amount * 3, 'recipient should have received ether') @@ -571,8 +565,8 @@ contract('Finance App', accounts => { // single payment await finance.newImmediatePayment(token1.address, recipient, 10, '') // will spend 10 // executes up to 2 times every 1 seconds - await finance.newScheduledPayment(token2.address, recipient, 5, time + 1, 1, 2, '') // will spend 10 - await finance.mock_setTimestamp(time + 4) + await finance.newScheduledPayment(token2.address, recipient, 5, NOW + 1, 1, 2, '') // will spend 10 + await finance.mockIncreaseTime(4) await finance.executePayment(1) // first create payment doesn't get an id because it is simple immediate tx @@ -592,20 +586,20 @@ contract('Finance App', accounts => { }) it('finishes accounting period correctly', async () => { - await finance.mock_setTimestamp(PERIOD_DURATION + 1) + await finance.mockIncreaseTime(PERIOD_DURATION + 1) await finance.tryTransitionAccountingPeriod(1) const [isCurrent, start, end, firstTx, lastTx] = await finance.getPeriod(0) assert.isFalse(isCurrent, 'shouldnt be current period') - assert.equal(start.valueOf(), START_TIME, 'should have correct start date') - assert.equal(end.valueOf(), START_TIME + PERIOD_DURATION - 1, 'should have correct end date') + assert.equal(start.valueOf(), NOW, 'should have correct start date') + assert.equal(end.valueOf(), NOW + PERIOD_DURATION - 1, 'should have correct end date') assert.equal(firstTx.valueOf(), 1, 'should have correct first tx') assert.equal(lastTx.valueOf(), 4, 'should have correct last tx') }) it('fails trying to access period out of bounds', async () => { - await finance.mock_setTimestamp(PERIOD_DURATION + 1) + await finance.mockIncreaseTime(PERIOD_DURATION + 1) await finance.tryTransitionAccountingPeriod(1) const currentPeriodId = await finance.currentPeriodId() @@ -619,14 +613,14 @@ contract('Finance App', accounts => { beforeEach(async () => { await finance.mock_setMaxPeriodTransitions(maxTransitions) - await finance.mock_setTimestamp(time + (maxTransitions + 2) * PERIOD_DURATION) + await finance.mockIncreaseTime((maxTransitions + 2) * PERIOD_DURATION) }) it('fails when too many period transitions are needed', async () => { // Normal payments await assertRevert( - finance.newImmediatePayment(token1.address, recipient, 10, ''), - errors.FINANCE_COMPLETE_TRANSITION + finance.newImmediatePayment(token1.address, recipient, 10, ''), + errors.FINANCE_COMPLETE_TRANSITION ) // Direct ETH transfers @@ -678,8 +672,8 @@ contract('Finance App', accounts => { it('fails to create a zero-amount single payment', async () => { await assertRevert( - finance.newImmediatePayment(token1.address, recipient, 0, ''), - errors.FINANCE_NEW_PAYMENT_AMOUNT_ZERO + finance.newImmediatePayment(token1.address, recipient, 0, ''), + errors.FINANCE_NEW_PAYMENT_AMOUNT_ZERO ) }) @@ -688,8 +682,8 @@ contract('Finance App', accounts => { await finance.setBudget(token1.address, budget) await assertRevert( - finance.newImmediatePayment(token1.address, recipient, budget + 1, ''), - errors.FINANCE_REMAINING_BUDGET + finance.newImmediatePayment(token1.address, recipient, budget + 1, ''), + errors.FINANCE_REMAINING_BUDGET ) }) @@ -698,8 +692,8 @@ contract('Finance App', accounts => { await finance.removeBudget(token1.address) // clear any budget restrictions await assertRevert( - finance.newImmediatePayment(token1.address, recipient, vaultBalance + 1, ''), - errors.VAULT_TOKEN_TRANSFER_REVERTED + finance.newImmediatePayment(token1.address, recipient, vaultBalance + 1, ''), + errors.VAULT_TOKEN_TRANSFER_REVERTED ) }) }) @@ -708,7 +702,7 @@ contract('Finance App', accounts => { const amount = 10 it('can create a scheduled payment', async () => { - const receipt = await finance.newScheduledPayment(token1.address, recipient, amount, time + 1, 1, 4, '') + const receipt = await finance.newScheduledPayment(token1.address, recipient, amount, NOW + 1, 1, 4, '') assertEvent(receipt, 'NewPayment') }) @@ -716,12 +710,12 @@ contract('Finance App', accounts => { const vaultBalance = await vault.balance(token1.address) await finance.removeBudget(token1.address) // clear any budget restrictions - const receipt = await finance.newScheduledPayment(token1.address, recipient, vaultBalance * 2, time + 1, 1, 4, '') + const receipt = await finance.newScheduledPayment(token1.address, recipient, vaultBalance * 2, NOW + 1, 1, 4, '') assertEvent(receipt, 'NewPayment') }) it('can create a single future payment', async () => { - const receipt = await finance.newScheduledPayment(token1.address, recipient, amount, time + 1, 1, 1, '') + const receipt = await finance.newScheduledPayment(token1.address, recipient, amount, NOW + 1, 1, 1, '') assertEvent(receipt, 'NewPayment') }) @@ -729,28 +723,28 @@ contract('Finance App', accounts => { const vaultBalance = await vault.balance(token1.address) await finance.removeBudget(token1.address) // clear any budget restrictions - const receipt = await finance.newScheduledPayment(token1.address, recipient, vaultBalance * 2, time + 1, 1, 1, '') + const receipt = await finance.newScheduledPayment(token1.address, recipient, vaultBalance * 2, NOW + 1, 1, 1, '') assertEvent(receipt, 'NewPayment') }) it('fails to create a zero-amount payment', async () => { await assertRevert( - finance.newScheduledPayment(token1.address, recipient, 0, time + 1, 1, 2, ''), - errors.FINANCE_NEW_PAYMENT_AMOUNT_ZERO + finance.newScheduledPayment(token1.address, recipient, 0, NOW + 1, 1, 2, ''), + errors.FINANCE_NEW_PAYMENT_AMOUNT_ZERO ) }) it('fails to create a no-interval payment', async () => { await assertRevert( - finance.newScheduledPayment(token1.address, recipient, 1, time + 1, 0, 2, ''), - errors.FINANCE_NEW_PAYMENT_INTRVL_ZERO + finance.newScheduledPayment(token1.address, recipient, 1, NOW + 1, 0, 2, ''), + errors.FINANCE_NEW_PAYMENT_INTRVL_ZERO ) }) it('fails to create a no-executions payment', async () => { await assertRevert( - finance.newScheduledPayment(token1.address, recipient, 1, time + 1, 1, 0, ''), - errors.FINANCE_NEW_PAYMENT_EXECS_ZERO + finance.newScheduledPayment(token1.address, recipient, 1, NOW + 1, 1, 0, ''), + errors.FINANCE_NEW_PAYMENT_EXECS_ZERO ) }) @@ -759,15 +753,15 @@ contract('Finance App', accounts => { await finance.setBudget(token1.address, budget) await assertRevert( - finance.newScheduledPayment(token1.address, recipient, budget + 1, time, 1, 1, ''), - errors.FINANCE_BUDGET + finance.newScheduledPayment(token1.address, recipient, budget + 1, NOW, 1, 1, ''), + errors.FINANCE_BUDGET ) }) it('fails to create an immediate single payment', async () => { await assertRevert( - finance.newScheduledPayment(token1.address, recipient, 1, time - 1, 1, 1, ''), - errors.FINANCE_NEW_PAYMENT_IMMEDIATE + finance.newScheduledPayment(token1.address, recipient, 1, NOW - 1, 1, 1, ''), + errors.FINANCE_NEW_PAYMENT_IMMEDIATE ) }) @@ -776,8 +770,8 @@ contract('Finance App', accounts => { await finance.setBudget(token1.address, budget) await assertRevert( - finance.newScheduledPayment(token1.address, recipient, budget + 1, time, 1, 2, ''), - errors.FINANCE_BUDGET + finance.newScheduledPayment(token1.address, recipient, budget + 1, NOW, 1, 2, ''), + errors.FINANCE_BUDGET ) }) @@ -785,35 +779,35 @@ contract('Finance App', accounts => { const vaultBalance = await vault.balance(token1.address) await finance.removeBudget(token1.address) // clear any budget restrictions - const receipt = await finance.newScheduledPayment(token1.address, recipient, vaultBalance + 1, time, 1, 2, '') + const receipt = await finance.newScheduledPayment(token1.address, recipient, vaultBalance + 1, NOW, 1, 2, '') const newScheduledPaymentId = getEventData(receipt, 'NewPayment', 'paymentId') await assertRevert(finance.executePayment(newScheduledPaymentId), errors.FINANCE_EXECUTE_PAYMENT_NUM) }) - it('fails to execute a payment by receiver without enough funds', async () => { - const vaultBalance = await vault.balance(token1.address) - await finance.removeBudget(token1.address) // clear any budget restrictions + it('fails to execute a payment by receiver without enough funds', async () => { + const vaultBalance = await vault.balance(token1.address) + await finance.removeBudget(token1.address) // clear any budget restrictions - const receipt = await finance.newScheduledPayment(token1.address, recipient, vaultBalance + 1, time, 1, 2, '') - const newScheduledPaymentId = getEventData(receipt, 'NewPayment', 'paymentId') + const receipt = await finance.newScheduledPayment(token1.address, recipient, vaultBalance + 1, NOW, 1, 2, '') + const newScheduledPaymentId = getEventData(receipt, 'NewPayment', 'paymentId') - await assertRevert( - finance.receiverExecutePayment(newScheduledPaymentId, { from: recipient }), - errors.FINANCE_EXECUTE_PAYMENT_NUM - ) - }) + await assertRevert( + finance.receiverExecutePayment(newScheduledPaymentId, { from: recipient }), + errors.FINANCE_EXECUTE_PAYMENT_NUM + ) + }) context('executing scheduled payment', async () => { let paymentId beforeEach(async () => { - const receipt = await finance.newScheduledPayment(token1.address, recipient, amount, time + 1, 1, 4, '') + const receipt = await finance.newScheduledPayment(token1.address, recipient, amount, NOW + 1, 1, 4, '') paymentId = getEventData(receipt, 'NewPayment', 'paymentId') }) it('only executes payment until max executions', async () => { - await finance.mock_setTimestamp(time + 10) + await finance.mockIncreaseTime(10) await finance.executePayment(paymentId) assert.equal((await token1.balanceOf(recipient)).valueOf(), amount * 4, 'recipient should have received tokens') @@ -821,14 +815,14 @@ contract('Finance App', accounts => { }) it('receiver can always execute a payment', async () => { - await finance.mock_setTimestamp(time + 1) + await finance.mockIncreaseTime(1) await finance.receiverExecutePayment(paymentId, { from: recipient }) assert.equal((await token1.balanceOf(recipient)).valueOf(), amount, 'should have received payment') }) it('fails when non-receiver attempts to execute a payment', async () => { - await finance.mock_setTimestamp(time + 1) + await finance.mockIncreaseTime(1) await assertRevert(finance.receiverExecutePayment(paymentId), errors.FINANCE_PAYMENT_RECEIVER) }) @@ -839,21 +833,21 @@ contract('Finance App', accounts => { it('fails when executed by receiver before next available time', async () => { await assertRevert( - finance.receiverExecutePayment(paymentId, { from: recipient }), - errors.FINANCE_EXECUTE_PAYMENT_TIME + finance.receiverExecutePayment(paymentId, { from: recipient }), + errors.FINANCE_EXECUTE_PAYMENT_TIME ) }) it('fails to execute inactive payment', async () => { await finance.setPaymentStatus(paymentId, false) - await finance.mock_setTimestamp(time + 1) + await finance.mockIncreaseTime(1) await assertRevert(finance.executePayment(paymentId), errors.FINANCE_PAYMENT_INACTIVE) }) it('succeeds payment after re-setting payment status to active', async () => { await finance.setPaymentStatus(paymentId, false) - await finance.mock_setTimestamp(time + 1) + await finance.mockIncreaseTime(1) await finance.setPaymentStatus(paymentId, true) @@ -866,7 +860,7 @@ contract('Finance App', accounts => { const vaultBalance = await vault.balance(token1.address) await finance.removeBudget(token1.address) // clear any budget restrictions - const receipt = await finance.newScheduledPayment(token1.address, recipient, vaultBalance + 1, time, 1, 2, '') + const receipt = await finance.newScheduledPayment(token1.address, recipient, vaultBalance + 1, NOW, 1, 2, '') assertEvent(receipt, 'PaymentFailure') // Make sure no transactions were made @@ -882,10 +876,10 @@ contract('Finance App', accounts => { // Create the budget, and use it up for the period await finance.setBudget(token1.address, budget) - await finance.newScheduledPayment(token1.address, recipient, amountPerPayment, time, 1, 2, '') + await finance.newScheduledPayment(token1.address, recipient, amountPerPayment, NOW, 1, 2, '') // No more budget left - const receipt = await finance.newScheduledPayment(token1.address, recipient, amountPerPayment, time, 1, 2, '') + const receipt = await finance.newScheduledPayment(token1.address, recipient, amountPerPayment, NOW, 1, 2, '') assertEvent(receipt, 'PaymentFailure') assert.isFalse(await finance.canMakePayment(token1.address, amountPerPayment)) }) @@ -899,8 +893,8 @@ contract('Finance App', accounts => { assert.isTrue(await finance.canMakePayment(token1.address, amountPerPayment)) // creates a repeating payment that can be executed one more than the vault's funds will allow - await finance.newScheduledPayment(token1.address, recipient, amountPerPayment, time, paidInterval, paidTimes + 1, '') - await finance.mock_setTimestamp(time + paidInterval * (paidTimes + 1)) + await finance.newScheduledPayment(token1.address, recipient, amountPerPayment, NOW, paidInterval, paidTimes + 1, '') + await finance.mockIncreaseTime(paidInterval * (paidTimes + 1)) const receipt = await finance.executePayment(1) assertEvent(receipt, 'PaymentFailure') @@ -922,23 +916,19 @@ contract('Finance App', accounts => { it('fails to create new scheduled payment', async() => { const amount = 1 - const time = 22 - await nonInit.mock_setTimestamp(time) await assertRevert( - nonInit.newScheduledPayment(token1.address, recipient, amount, time, 1, 2, 'ref'), - errors.APP_AUTH_FAILED + nonInit.newScheduledPayment(token1.address, recipient, amount, NOW, 1, 2, 'ref'), + errors.APP_AUTH_FAILED ) }) it('fails to create new single payment transaction', async() => { const amount = 1 - const time = 22 - await nonInit.mock_setTimestamp(time) await assertRevert( - nonInit.newImmediatePayment(token1.address, recipient, amount, 'ref'), - errors.APP_AUTH_FAILED + nonInit.newImmediatePayment(token1.address, recipient, amount, 'ref'), + errors.APP_AUTH_FAILED ) }) diff --git a/apps/survey/.solcover.js b/apps/survey/.solcover.js index 0e27852aec..426fd02ea8 100644 --- a/apps/survey/.solcover.js +++ b/apps/survey/.solcover.js @@ -1,7 +1,8 @@ module.exports = { norpc: true, - copyPackages: ['@aragon/os', '@aragon/apps-shared-minime'], + copyPackages: ['@aragon/os', '@aragon/apps-shared-minime', '@aragon/test-helpers'], skipFiles: [ 'test', + '@aragon/test-helpers/contracts/TimeHelpersMock.sol', ] } diff --git a/apps/survey/contracts/test/mocks/SurveyMock.sol b/apps/survey/contracts/test/mocks/SurveyMock.sol index edf1693d30..1863e46340 100644 --- a/apps/survey/contracts/test/mocks/SurveyMock.sol +++ b/apps/survey/contracts/test/mocks/SurveyMock.sol @@ -1,9 +1,10 @@ pragma solidity 0.4.24; import "../../Survey.sol"; +import "@aragon/test-helpers/contracts/TimeHelpersMock.sol"; -contract SurveyMock is Survey { +contract SurveyMock is Survey, TimeHelpersMock { // Mint a token and create a vote in the same transaction to test snapshot block values are correct function newTokenAndSurvey(address _holder, uint256 _tokenAmount, string _metadata, uint256 _options) external diff --git a/apps/survey/test/survey.js b/apps/survey/test/survey.js index 15552ba749..51830251e8 100644 --- a/apps/survey/test/survey.js +++ b/apps/survey/test/survey.js @@ -1,6 +1,5 @@ const { assertRevert } = require('@aragon/test-helpers/assertThrow') const getBlockNumber = require('@aragon/test-helpers/blockNumber')(web3) -const timeTravel = require('@aragon/test-helpers/timeTravel')(web3) const getContract = name => artifacts.require(name) const pct16 = x => new web3.BigNumber(x).times(new web3.BigNumber(10).toPower(16)) @@ -29,7 +28,7 @@ contract('Survey app', accounts => { const kernelBase = await getContract('Kernel').new(true) // petrify immediately const aclBase = await getContract('ACL').new() daoFact = await getContract('DAOFactory').new(kernelBase.address, aclBase.address, NULL_ADDRESS) - surveyBase = await getContract('Survey').new() + surveyBase = await getContract('SurveyMock').new() // Setup constants ANY_ENTITY = await aclBase.ANY_ENTITY() @@ -47,7 +46,7 @@ contract('Survey app', accounts => { await acl.createPermission(root, dao.address, APP_MANAGER_ROLE, root, { from: root }) const receipt = await dao.newAppInstance(SURVEY_APP_ID, surveyBase.address, '0x', false, { from: root }) - survey = getContract('Survey').at(receipt.logs.filter(l => l.event == 'NewAppProxy')[0].args.proxy) + survey = getContract('SurveyMock').at(receipt.logs.filter(l => l.event == 'NewAppProxy')[0].args.proxy) await acl.createPermission(ANY_ENTITY, survey.address, CREATE_SURVEYS_ROLE, root, { from: root }) await acl.createPermission(ANY_ENTITY, survey.address, MODIFY_PARTICIPATION_ROLE, root, { from: root }) @@ -210,7 +209,7 @@ contract('Survey app', accounts => { it('changing min participation doesnt affect survey min participation', async () => { await survey.changeMinAcceptParticipationPct(pct16(50)) - await timeTravel(surveyTime + 1) + await survey.mockIncreaseTime(surveyTime + 1) const state = await survey.getSurvey(surveyId) assert.deepEqual(state[3], minimumAcceptanceParticipationPct, 'acceptance participation in survey should stay equal') @@ -233,7 +232,7 @@ contract('Survey app', accounts => { }) it('throws when voting after survey closes', async () => { - await timeTravel(surveyTime + 1) + await survey.mockIncreaseTime(surveyTime + 1) await assertRevert(async () => { await survey.voteOption(surveyId, 1, { from: holder31 }) }) diff --git a/apps/token-manager/.solcover.js b/apps/token-manager/.solcover.js index 0e27852aec..426fd02ea8 100644 --- a/apps/token-manager/.solcover.js +++ b/apps/token-manager/.solcover.js @@ -1,7 +1,8 @@ module.exports = { norpc: true, - copyPackages: ['@aragon/os', '@aragon/apps-shared-minime'], + copyPackages: ['@aragon/os', '@aragon/apps-shared-minime', '@aragon/test-helpers'], skipFiles: [ 'test', + '@aragon/test-helpers/contracts/TimeHelpersMock.sol', ] } diff --git a/apps/token-manager/contracts/test/mocks/TokenManagerMock.sol b/apps/token-manager/contracts/test/mocks/TokenManagerMock.sol index c2e0c7a642..3776e48e8f 100644 --- a/apps/token-manager/contracts/test/mocks/TokenManagerMock.sol +++ b/apps/token-manager/contracts/test/mocks/TokenManagerMock.sol @@ -1,11 +1,8 @@ pragma solidity 0.4.24; import "../../TokenManager.sol"; +import "@aragon/test-helpers/contracts/TimeHelpersMock.sol"; -contract TokenManagerMock is TokenManager { - uint256 mockTime; - - function mock_setTimestamp(uint256 i) public { mockTime = i; } - function getTimestamp() internal view returns (uint256) { return mockTime; } -} +/* solium-disable-next-line no-empty-blocks */ +contract TokenManagerMock is TokenManager, TimeHelpersMock {} diff --git a/apps/token-manager/test/tokenmanager.js b/apps/token-manager/test/tokenmanager.js index dcf9889539..8fa3aef6e3 100644 --- a/apps/token-manager/test/tokenmanager.js +++ b/apps/token-manager/test/tokenmanager.js @@ -25,7 +25,7 @@ contract('Token Manager', accounts => { let MINT_ROLE, ISSUE_ROLE, ASSIGN_ROLE, REVOKE_VESTINGS_ROLE, BURN_ROLE let ETH - const NOW_TIME = 1 + const NOW = 1 const root = accounts[0] const holder = accounts[1] @@ -59,7 +59,7 @@ contract('Token Manager', accounts => { const receipt = await dao.newAppInstance('0x1234', tokenManagerBase.address, '0x', false, { from: root }) tokenManager = TokenManager.at(receipt.logs.filter(l => l.event == 'NewAppProxy')[0].args.proxy) - tokenManager.mock_setTimestamp(NOW_TIME) + tokenManager.mockSetTimestamp(NOW) await acl.createPermission(ANY_ADDR, tokenManager.address, MINT_ROLE, root, { from: root }) await acl.createPermission(ANY_ADDR, tokenManager.address, ISSUE_ROLE, root, { from: root }) @@ -348,9 +348,12 @@ contract('Token Manager', accounts => { }) context('assigning vested tokens', () => { - const startDate = NOW_TIME + 1000 - const cliffDate = NOW_TIME + 2000 - const vestingDate = NOW_TIME + 5000 + const CLIFF_DURATION = 2000 + const VESTING_DURATION = 5000 + + const startDate = NOW + 1000 + const cliffDate = NOW + CLIFF_DURATION + const vestingDate = NOW + VESTING_DURATION const totalTokens = 40 const revokable = true @@ -376,22 +379,23 @@ contract('Token Manager', accounts => { }) it('can start transferring on cliff', async () => { - await tokenManager.mock_setTimestamp(cliffDate) + await tokenManager.mockIncreaseTime(CLIFF_DURATION) await token.transfer(holder2, 10, { from: holder }) - assert.equal(await token.balanceOf(holder2), 10, 'should have received tokens') - assert.equal(await tokenManager.spendableBalanceOf(holder), 0, 'should not be able to spend more tokens') + assert.equal((await token.balanceOf(holder2)).toString(), 10, 'should have received tokens') + assert.equal((await tokenManager.spendableBalanceOf(holder)).toString(), 0, 'should not be able to spend more tokens') }) it('can transfer all tokens after vesting', async () => { - await tokenManager.mock_setTimestamp(vestingDate) + await tokenManager.mockIncreaseTime(VESTING_DURATION) await token.transfer(holder2, totalTokens, { from: holder }) assert.equal(await token.balanceOf(holder2), totalTokens, 'should have received tokens') }) it('can transfer half mid vesting', async () => { - await tokenManager.mock_setTimestamp(startDate + (vestingDate - startDate) / 2) + await tokenManager.mockSetTimestamp(startDate) + await tokenManager.mockIncreaseTime((vestingDate - startDate) / 2) await token.transfer(holder2, 20, { from: holder }) @@ -413,7 +417,7 @@ contract('Token Manager', accounts => { }) it('cannot transfer all tokens right before vesting', async () => { - await tokenManager.mock_setTimestamp(vestingDate - 10) + await tokenManager.mockIncreaseTime(VESTING_DURATION - 10) return assertRevert(async () => { await token.transfer(holder2, totalTokens, { from: holder }) @@ -421,14 +425,14 @@ contract('Token Manager', accounts => { }) it('can be revoked and not vested tokens are transfered to token manager', async () => { - await tokenManager.mock_setTimestamp(cliffDate) + await tokenManager.mockIncreaseTime(CLIFF_DURATION) await tokenManager.revokeVesting(holder, 0) await token.transfer(holder2, 5, { from: holder }) - assert.equal(await token.balanceOf(holder), 5, 'should have kept vested tokens') - assert.equal(await token.balanceOf(holder2), 5, 'should have kept vested tokens') - assert.equal(await token.balanceOf(tokenManager.address), totalTokens - 10, 'should have received unvested') + assert.equal((await token.balanceOf(holder)).toString(), 5, 'should have kept vested tokens') + assert.equal((await token.balanceOf(holder2)).toString(), 5, 'should have kept vested tokens') + assert.equal((await token.balanceOf(tokenManager.address)).toString(), totalTokens - 10, 'should have received unvested') }) it('cannot assign a vesting to itself', async () => { @@ -459,7 +463,7 @@ contract('Token Manager', accounts => { }) // Can't create a new vesting even after other vestings have finished - await tokenManager.mock_setTimestamp(vestingDate) + await tokenManager.mockIncreaseTime(VESTING_DURATION) await assertRevert(async () => { await tokenManager.assignVested(holder, 1, startDate, cliffDate, vestingDate, false) }) diff --git a/apps/voting/.solcover.js b/apps/voting/.solcover.js index 0e27852aec..426fd02ea8 100644 --- a/apps/voting/.solcover.js +++ b/apps/voting/.solcover.js @@ -1,7 +1,8 @@ module.exports = { norpc: true, - copyPackages: ['@aragon/os', '@aragon/apps-shared-minime'], + copyPackages: ['@aragon/os', '@aragon/apps-shared-minime', '@aragon/test-helpers'], skipFiles: [ 'test', + '@aragon/test-helpers/contracts/TimeHelpersMock.sol', ] } diff --git a/apps/voting/contracts/test/mocks/VotingMock.sol b/apps/voting/contracts/test/mocks/VotingMock.sol index 78bbc4fd7c..8f54735ab4 100644 --- a/apps/voting/contracts/test/mocks/VotingMock.sol +++ b/apps/voting/contracts/test/mocks/VotingMock.sol @@ -1,14 +1,10 @@ pragma solidity 0.4.24; import "../../Voting.sol"; +import "@aragon/test-helpers/contracts/TimeHelpersMock.sol"; -contract VotingMock is Voting { - uint64 mockTime; - - function mock_setTimestamp(uint64 i) public { mockTime = i; } - function getTimestamp64() internal view returns (uint64) { return mockTime; } - +contract VotingMock is Voting, TimeHelpersMock { /* Ugly hack to work around this issue: * https://github.com/trufflesuite/truffle/issues/569 * https://github.com/trufflesuite/truffle/issues/737 diff --git a/apps/voting/test/voting.js b/apps/voting/test/voting.js index 2cb9481eab..1a09f93b04 100644 --- a/apps/voting/test/voting.js +++ b/apps/voting/test/voting.js @@ -1,5 +1,3 @@ -const sha3 = require('solidity-sha3').default - const { assertRevert } = require('@aragon/test-helpers/assertThrow') const getBlockNumber = require('@aragon/test-helpers/blockNumber')(web3) const { encodeCallScript, EMPTY_SCRIPT } = require('@aragon/test-helpers/evmScript') @@ -35,9 +33,8 @@ contract('Voting App', accounts => { let APP_MANAGER_ROLE let CREATE_VOTES_ROLE, MODIFY_SUPPORT_ROLE, MODIFY_QUORUM_ROLE - const startTime = 1 + const NOW = 1 const votingDuration = 1000 - const votingEnd = startTime + votingDuration + 1 const root = accounts[0] before(async () => { @@ -63,7 +60,7 @@ contract('Voting App', accounts => { const receipt = await dao.newAppInstance('0x1234', votingBase.address, '0x', false, { from: root }) voting = Voting.at(receipt.logs.filter(l => l.event == 'NewAppProxy')[0].args.proxy) - await voting.mock_setTimestamp(startTime) + await voting.mockSetTimestamp(NOW) await acl.createPermission(ANY_ADDR, voting.address, CREATE_VOTES_ROLE, root, { from: root }) await acl.createPermission(ANY_ADDR, voting.address, MODIFY_SUPPORT_ROLE, root, { from: root }) @@ -71,11 +68,6 @@ contract('Voting App', accounts => { }) context('normal token supply, common tests', () => { - const holder20 = accounts[0] - const holder29 = accounts[1] - const holder51 = accounts[2] - const nonHolder = accounts[4] - const neededSupport = pct16(50) const minimumAcceptanceQuorum = pct16(20) @@ -252,7 +244,7 @@ contract('Voting App', accounts => { await voting.vote(voteId, true, false, { from: holder51 }) await voting.vote(voteId, true, false, { from: holder20 }) await voting.vote(voteId, false, false, { from: holder29 }) - await voting.mock_setTimestamp(votingEnd) + await voting.mockIncreaseTime(votingDuration + 1) const state = await voting.getVote(voteId) assert.equal(state[4].toString(), neededSupport.toString(), 'required support in vote should stay equal') @@ -267,7 +259,7 @@ contract('Voting App', accounts => { // it will succeed await voting.vote(voteId, true, true, { from: holder29 }) - await voting.mock_setTimestamp(votingEnd) + await voting.mockIncreaseTime(votingDuration + 1) const state = await voting.getVote(voteId) assert.equal(state[5].toString(), minimumAcceptanceQuorum.toString(), 'acceptance quorum in vote should stay equal') @@ -310,7 +302,7 @@ contract('Voting App', accounts => { }) it('throws when voting after voting closes', async () => { - await voting.mock_setTimestamp(votingEnd) + await voting.mockIncreaseTime(votingDuration + 1) return assertRevert(async () => { await voting.vote(voteId, true, true, { from: holder29 }) }) @@ -319,14 +311,14 @@ contract('Voting App', accounts => { it('can execute if vote is approved with support and quorum', async () => { await voting.vote(voteId, true, true, { from: holder29 }) await voting.vote(voteId, false, true, { from: holder20 }) - await voting.mock_setTimestamp(votingEnd) + await voting.mockIncreaseTime(votingDuration + 1) await voting.executeVote(voteId) assert.equal(await executionTarget.counter(), 2, 'should have executed result') }) it('cannot execute vote if not enough quorum met', async () => { await voting.vote(voteId, true, true, { from: holder20 }) - await voting.mock_setTimestamp(votingEnd) + await voting.mockIncreaseTime(votingDuration + 1) return assertRevert(async () => { await voting.executeVote(voteId) }) @@ -335,7 +327,7 @@ contract('Voting App', accounts => { it('cannot execute vote if not support met', async () => { await voting.vote(voteId, false, true, { from: holder29 }) await voting.vote(voteId, false, true, { from: holder20 }) - await voting.mock_setTimestamp(votingEnd) + await voting.mockIncreaseTime(votingDuration + 1) return assertRevert(async () => { await voting.executeVote(voteId) }) diff --git a/future-apps/payroll/.solcover.js b/future-apps/payroll/.solcover.js index d84efb3538..31486db4e7 100644 --- a/future-apps/payroll/.solcover.js +++ b/future-apps/payroll/.solcover.js @@ -1,7 +1,8 @@ module.exports = { norpc: true, - copyPackages: ['@aragon/os', '@aragon/apps-finance', '@aragon/apps-vault'], + copyPackages: ['@aragon/os', '@aragon/apps-finance', '@aragon/apps-vault', '@aragon/test-helpers'], skipFiles: [ 'test', + '@aragon/test-helpers/contracts/TimeHelpersMock.sol', ] } diff --git a/future-apps/payroll/test/contracts/Payroll_gas_costs.test.js b/future-apps/payroll/test/contracts/Payroll_gas_costs.test.js index ae6f51c99d..786113e2f4 100644 --- a/future-apps/payroll/test/contracts/Payroll_gas_costs.test.js +++ b/future-apps/payroll/test/contracts/Payroll_gas_costs.test.js @@ -33,7 +33,6 @@ contract('Payroll gas costs', ([owner, employee, anotherEmployee]) => { beforeEach('initialize payroll app', async () => { await payroll.initialize(finance.address, denominationToken.address, priceFeed.address, RATE_EXPIRATION_TIME, { from: owner }) - await payroll.mockSetTimestamp(NOW) const startDate = NOW - ONE_MONTH const salary = annualSalaryPerSecond(100000, TOKEN_DECIMALS) diff --git a/shared/test-helpers/contracts/TimeHelpersMock.sol b/shared/test-helpers/contracts/TimeHelpersMock.sol index e413e8ef40..6310e0fd83 100644 --- a/shared/test-helpers/contracts/TimeHelpersMock.sol +++ b/shared/test-helpers/contracts/TimeHelpersMock.sol @@ -27,6 +27,14 @@ contract TimeHelpersMock is TimeHelpers { else mockedTimestamp = block.timestamp.add(_seconds); } + /** + * @dev Decreases the mocked timestamp value, used only for testing purposes + */ + function mockDecreaseTime(uint256 _seconds) public { + if (mockedTimestamp != 0) mockedTimestamp = mockedTimestamp.sub(_seconds); + else mockedTimestamp = block.timestamp.sub(_seconds); + } + /** * @dev Advances the mocked block number value, used only for testing purposes */