diff --git a/future-apps/payroll/contracts/Payroll.sol b/future-apps/payroll/contracts/Payroll.sol index bc7a2dff89..1e1e036a18 100644 --- a/future-apps/payroll/contracts/Payroll.sol +++ b/future-apps/payroll/contracts/Payroll.sol @@ -670,9 +670,7 @@ contract Payroll is EtherTokenConstant, IForwarder, IsContract, AragonApp { return ONE; } - uint128 xrt; - uint64 when; - (xrt, when) = feed.get(denominationToken, _token); + (uint128 xrt, uint64 when) = feed.get(_token, denominationToken); // Check the price feed is recent enough if (getTimestamp64().sub(when) >= rateExpiryTime) { @@ -690,6 +688,7 @@ contract Payroll is EtherTokenConstant, IForwarder, IsContract, AragonApp { * @return True if there was at least one token transfer */ function _transferTokensAmount(uint256 _employeeId, PaymentType _type, uint256 _totalAmount) internal returns (bool somethingPaid) { + if (_totalAmount == 0) return false; Employee storage employee = employees[_employeeId]; string memory paymentReference = _paymentReferenceFor(_type); for (uint256 i = 0; i < allowedTokensArray.length; i++) { diff --git a/future-apps/payroll/contracts/test/mocks/PriceFeedMock.sol b/future-apps/payroll/contracts/test/mocks/PriceFeedMock.sol index 5a905351db..69d1454e5d 100644 --- a/future-apps/payroll/contracts/test/mocks/PriceFeedMock.sol +++ b/future-apps/payroll/contracts/test/mocks/PriceFeedMock.sol @@ -1,29 +1,36 @@ pragma solidity ^0.4.24; -import "@aragon/ppf-contracts/contracts/IFeed.sol"; +import "@aragon/ppf-contracts/contracts/PPF.sol"; import "@aragon/test-helpers/contracts/TimeHelpersMock.sol"; -contract PriceFeedMock is IFeed, TimeHelpersMock { +contract PriceFeedMock is PPF, TimeHelpersMock { + // Set operator to address(0) so invalid signatures can pass + constructor () PPF(address(0), msg.sender) public { + // solium-disable-previous-line no-empty-blocks + } - event PriceFeedLogSetRate(address sender, address token, uint128 value); + // Overriding function for testing purposes, removing check for zero address operator + function _setOperator(address _operator) internal { + // require(_operator != address(0)); + operator = _operator; + emit SetOperator(_operator); + } - function get(address base, address quote) external view returns (uint128 xrt, uint64 when) { - xrt = toInt(quote); - when = getTimestamp64(); + // Overwrite function using TimeHelpers and allowing to set past rates + function update(address base, address quote, uint128 xrt, uint64 when, bytes sig) public { + bytes32 pair = super.pairId(base, quote); - emit PriceFeedLogSetRate(msg.sender, quote, xrt); - } + // Remove check that ensures a given rate is more recent than the current value + // require(when > feed[pair].when && when <= getTimestamp()); + require(xrt > 0); // Make sure xrt is not 0, as the math would break (Dividing by 0 sucks big time) + require(base != quote); // Assumption that currency units are fungible and xrt should always be 1 - /// Gets the first byte of an address as an integer - function toInt(address x) public pure returns(uint128 i) { - uint256 j = uint256(x); - j = j >> 152; - if (j == 0) - j = 10**15; - else - j = j * 10**18; - i = uint128(j); - } + bytes32 h = super.setHash(base, quote, xrt, when); + require(h.personalRecover(sig) == operator); // Make sure the update was signed by the operator + + feed[pair] = Price(super.pairXRT(base, quote, xrt), when); + emit SetRate(base, quote, xrt, when); + } } diff --git a/future-apps/payroll/package.json b/future-apps/payroll/package.json index f6e0631f10..e8a6fa3c64 100644 --- a/future-apps/payroll/package.json +++ b/future-apps/payroll/package.json @@ -39,9 +39,9 @@ "/test" ], "dependencies": { - "@aragon/apps-finance": "2.1.0", + "@aragon/apps-finance": "3.0.0", "@aragon/os": "4.1.0", - "@aragon/ppf-contracts": "1.0.2" + "@aragon/ppf-contracts": "1.1.0" }, "devDependencies": { "@aragon/apps-shared-migrations": "1.0.0", diff --git a/future-apps/payroll/test/contracts/Payroll_add_employee.test.js b/future-apps/payroll/test/contracts/Payroll_add_employee.test.js index 53a19d963d..f0517da3a0 100644 --- a/future-apps/payroll/test/contracts/Payroll_add_employee.test.js +++ b/future-apps/payroll/test/contracts/Payroll_add_employee.test.js @@ -1,12 +1,12 @@ const { assertRevert } = require('@aragon/test-helpers/assertThrow') const { getEvents, getEventArgument } = require('../helpers/events') const { maxUint64, annualSalaryPerSecond } = require('../helpers/numbers')(web3) -const { deployErc20TokenAndDeposit, deployContracts, createPayrollInstance, mockTimestamps } = require('../helpers/setup.js')(artifacts, web3) +const { deployErc20TokenAndDeposit, deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy.js')(artifacts, web3) const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' contract('Payroll employees addition', ([owner, employee, anotherEmployee, anyone]) => { - let dao, payroll, payrollBase, finance, vault, priceFeed, denominationToken, anotherToken + let dao, payroll, payrollBase, finance, vault, priceFeed, denominationToken const NOW = 1553703809 // random fixed timestamp in seconds const ONE_MONTH = 60 * 60 * 24 * 31 @@ -17,15 +17,13 @@ contract('Payroll employees addition', ([owner, employee, anotherEmployee, anyon const currentTimestamp = async () => payroll.getTimestampPublic() - before('setup base apps and tokens', async () => { - ({ dao, finance, vault, priceFeed, payrollBase } = await deployContracts(owner)) - anotherToken = await deployErc20TokenAndDeposit(owner, finance, vault, 'Another token', TOKEN_DECIMALS) - denominationToken = await deployErc20TokenAndDeposit(owner, finance, vault, 'Denomination Token', TOKEN_DECIMALS) + before('deploy base apps and tokens', async () => { + ({ dao, finance, vault, payrollBase } = await deployContracts(owner)) + denominationToken = await deployErc20TokenAndDeposit(owner, finance, 'Denomination Token', TOKEN_DECIMALS) }) - beforeEach('setup payroll instance', async () => { - payroll = await createPayrollInstance(dao, payrollBase, owner) - await mockTimestamps(payroll, priceFeed, NOW) + beforeEach('create payroll and price feed instance', async () => { + ({ payroll, priceFeed } = await createPayrollAndPriceFeed(dao, payrollBase, owner, NOW)) }) describe('addEmployeeNow', () => { @@ -39,7 +37,6 @@ contract('Payroll employees addition', ([owner, employee, anotherEmployee, anyon context('when the sender has permissions to add employees', () => { const from = owner - let receipt, employeeId context('when the employee has not been added yet', () => { let receipt, employeeId diff --git a/future-apps/payroll/test/contracts/Payroll_allowed_tokens.test.js b/future-apps/payroll/test/contracts/Payroll_allowed_tokens.test.js index 1bbbde4a7c..17c5f412cb 100644 --- a/future-apps/payroll/test/contracts/Payroll_allowed_tokens.test.js +++ b/future-apps/payroll/test/contracts/Payroll_allowed_tokens.test.js @@ -1,13 +1,14 @@ const PAYMENT_TYPES = require('../helpers/payment_types') +const setTokenRates = require('../helpers/set_token_rates')(web3) const { getEvent } = require('../helpers/events') const { assertRevert } = require('@aragon/test-helpers/assertThrow') -const { deployErc20TokenAndDeposit, deployContracts, createPayrollInstance, mockTimestamps } = require('../helpers/setup.js')(artifacts, web3) +const { deployErc20TokenAndDeposit, deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy.js')(artifacts, web3) const MAX_GAS_USED = 6.5e6 const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' -contract('Payroll allowed tokens,', ([owner, employee, anotherEmployee, anyone]) => { - let dao, payroll, payrollBase, finance, vault, priceFeed, denominationToken, anotherToken +contract('Payroll allowed tokens,', ([owner, employee, anyone]) => { + let dao, payroll, payrollBase, finance, vault, priceFeed, denominationToken const NOW = 1553703809 // random fixed timestamp in seconds const ONE_MONTH = 60 * 60 * 24 * 31 @@ -16,15 +17,13 @@ contract('Payroll allowed tokens,', ([owner, employee, anotherEmployee, anyone]) const TOKEN_DECIMALS = 18 - before('setup base apps and tokens', async () => { - ({ dao, finance, vault, priceFeed, payrollBase } = await deployContracts(owner)) - anotherToken = await deployErc20TokenAndDeposit(owner, finance, vault, 'Another token', TOKEN_DECIMALS) - denominationToken = await deployErc20TokenAndDeposit(owner, finance, vault, 'Denomination Token', TOKEN_DECIMALS) + before('deploy base apps and tokens', async () => { + ({ dao, finance, vault, payrollBase } = await deployContracts(owner)) + denominationToken = await deployErc20TokenAndDeposit(owner, finance, 'Denomination Token', TOKEN_DECIMALS) }) - beforeEach('setup payroll instance', async () => { - payroll = await createPayrollInstance(dao, payrollBase, owner) - await mockTimestamps(payroll, priceFeed, NOW) + beforeEach('create payroll and price feed instance', async () => { + ({ payroll, priceFeed } = await createPayrollAndPriceFeed(dao, payrollBase, owner, NOW)) }) describe('addAllowedToken', () => { @@ -58,8 +57,8 @@ contract('Payroll allowed tokens,', ([owner, employee, anotherEmployee, anyone]) }) it('can allow multiple tokens', async () => { - const erc20Token1 = await deployErc20TokenAndDeposit(owner, finance, vault, 'Token 1', 18) - const erc20Token2 = await deployErc20TokenAndDeposit(owner, finance, vault, 'Token 2', 16) + const erc20Token1 = await deployErc20TokenAndDeposit(owner, finance, 'Token 1', 18) + const erc20Token2 = await deployErc20TokenAndDeposit(owner, finance, 'Token 2', 16) await payroll.addAllowedToken(denominationToken.address, { from }) await payroll.addAllowedToken(erc20Token1.address, { from }) @@ -75,23 +74,26 @@ contract('Payroll allowed tokens,', ([owner, employee, anotherEmployee, anyone]) context('when it reaches the maximum amount allowed', () => { let tokenAddresses = [], MAX_ALLOWED_TOKENS - before('deploy multiple tokens', async () => { - MAX_ALLOWED_TOKENS = (await payroll.getMaxAllowedTokens()).valueOf() + before('deploy multiple tokens and set rates', async () => { + MAX_ALLOWED_TOKENS = (await payrollBase.getMaxAllowedTokens()).valueOf() for (let i = 0; i < MAX_ALLOWED_TOKENS; i++) { - const token = await deployErc20TokenAndDeposit(owner, finance, vault, `Token ${i}`, 18); + const token = await deployErc20TokenAndDeposit(owner, finance, `Token ${i}`, 18); tokenAddresses.push(token.address) } }) - beforeEach('allow tokens and add employee', async () => { + beforeEach('allow tokens, set rates, and add employee', async () => { await Promise.all(tokenAddresses.map(address => payroll.addAllowedToken(address, { from: owner }))) assert.equal(await payroll.getAllowedTokensArrayLength(), MAX_ALLOWED_TOKENS, 'amount of allowed tokens does not match') + const rates = tokenAddresses.map(() => 5) + await setTokenRates(priceFeed, denominationToken, tokenAddresses, rates) + await payroll.addEmployee(employee, 100000, 'Boss', NOW - ONE_MONTH, { from: owner }) }) it('can not add one more token', async () => { - const erc20Token = await deployErc20TokenAndDeposit(owner, finance, vault, 'Extra token', 18) + const erc20Token = await deployErc20TokenAndDeposit(owner, finance, 'Extra token', 18) await assertRevert(payroll.addAllowedToken(erc20Token.address), 'PAYROLL_MAX_ALLOWED_TOKENS') }) @@ -100,10 +102,10 @@ contract('Payroll allowed tokens,', ([owner, employee, anotherEmployee, anyone]) const allocations = tokenAddresses.map(() => 100 / MAX_ALLOWED_TOKENS) const allocationTx = await payroll.determineAllocation(tokenAddresses, allocations, { from: employee }) - assert.isBelow(allocationTx.receipt.cumulativeGasUsed, MAX_GAS_USED, 'Too much gas consumed for allocation') + assert.isBelow(allocationTx.receipt.cumulativeGasUsed, MAX_GAS_USED, 'too much gas consumed for allocation') const paydayTx = await payroll.payday(PAYMENT_TYPES.PAYROLL, 0, { from: employee }) - assert.isBelow(paydayTx.receipt.cumulativeGasUsed, MAX_GAS_USED, 'Too much gas consumed for payday') + assert.isBelow(paydayTx.receipt.cumulativeGasUsed, MAX_GAS_USED, 'too much gas consumed for payday') }) }) }) diff --git a/future-apps/payroll/test/contracts/Payroll_bonuses.test.js b/future-apps/payroll/test/contracts/Payroll_bonuses.test.js index f2824d6b94..89bce466ac 100644 --- a/future-apps/payroll/test/contracts/Payroll_bonuses.test.js +++ b/future-apps/payroll/test/contracts/Payroll_bonuses.test.js @@ -1,29 +1,33 @@ const PAYMENT_TYPES = require('../helpers/payment_types') +const setTokenRates = require('../helpers/set_token_rates')(web3) const { assertRevert } = require('@aragon/test-helpers/assertThrow') +const { bn, maxUint256 } = require('../helpers/numbers')(web3) const { getEvents, getEventArgument } = require('../helpers/events') -const { bigExp, maxUint256 } = require('../helpers/numbers')(web3) -const { deployErc20TokenAndDeposit, deployContracts, createPayrollInstance, mockTimestamps } = require('../helpers/setup.js')(artifacts, web3) +const { deployErc20TokenAndDeposit, deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy.js')(artifacts, web3) contract('Payroll bonuses', ([owner, employee, anyone]) => { - let dao, payroll, payrollBase, finance, vault, priceFeed, denominationToken, anotherToken + let dao, payroll, payrollBase, finance, vault, priceFeed, denominationToken, anotherToken, anotherTokenRate const NOW = 1553703809 // random fixed timestamp in seconds const ONE_MONTH = 60 * 60 * 24 * 31 const TWO_MONTHS = ONE_MONTH * 2 const RATE_EXPIRATION_TIME = TWO_MONTHS - const PCT_ONE = bigExp(1, 18) const TOKEN_DECIMALS = 18 - before('setup base apps and tokens', async () => { - ({ dao, finance, vault, priceFeed, payrollBase } = await deployContracts(owner)) - anotherToken = await deployErc20TokenAndDeposit(owner, finance, vault, 'Another token', TOKEN_DECIMALS) - denominationToken = await deployErc20TokenAndDeposit(owner, finance, vault, 'Denomination Token', TOKEN_DECIMALS) + const increaseTime = async seconds => { + await payroll.mockIncreaseTime(seconds) + await priceFeed.mockIncreaseTime(seconds) + } + + before('deploy base apps and tokens', async () => { + ({ dao, finance, vault, payrollBase } = await deployContracts(owner)) + anotherToken = await deployErc20TokenAndDeposit(owner, finance, 'Another token', TOKEN_DECIMALS) + denominationToken = await deployErc20TokenAndDeposit(owner, finance, 'Denomination Token', TOKEN_DECIMALS) }) - beforeEach('setup payroll instance', async () => { - payroll = await createPayrollInstance(dao, payrollBase, owner) - await mockTimestamps(payroll, priceFeed, NOW) + beforeEach('create payroll and price feed instance', async () => { + ({ payroll, priceFeed } = await createPayrollAndPriceFeed(dao, payrollBase, owner, NOW)) }) describe('addBonus', () => { @@ -100,7 +104,7 @@ contract('Payroll bonuses', ([owner, employee, anyone]) => { context('when the given employee is not active', () => { beforeEach('terminate employee', async () => { await payroll.terminateEmployeeNow(employeeId, { from: owner }) - await payroll.mockIncreaseTime(ONE_MONTH) + await increaseTime(ONE_MONTH) }) it('reverts', async () => { @@ -145,6 +149,11 @@ contract('Payroll bonuses', ([owner, employee, anyone]) => { await payroll.initialize(finance.address, denominationToken.address, priceFeed.address, RATE_EXPIRATION_TIME, { from: owner }) }) + beforeEach('set token rates', async () => { + anotherTokenRate = bn(5) + await setTokenRates(priceFeed, denominationToken, [anotherToken], [anotherTokenRate]) + }) + context('when the sender is an employee', () => { const from = employee let employeeId, salary = 1000 @@ -153,7 +162,7 @@ contract('Payroll bonuses', ([owner, employee, anyone]) => { const receipt = await payroll.addEmployeeNow(employee, salary, 'Boss') employeeId = getEventArgument(receipt, 'AddEmployee', 'employeeId') - await payroll.mockIncreaseTime(ONE_MONTH) + await increaseTime(ONE_MONTH) }) context('when the employee has already set some token allocations', () => { @@ -189,7 +198,6 @@ contract('Payroll bonuses', ([owner, employee, anyone]) => { assert.equal(currentDenominationTokenBalance.toString(), expectedDenominationTokenBalance.toString(), 'current denomination token balance does not match') const currentAnotherTokenBalance = await anotherToken.balanceOf(employee) - const anotherTokenRate = (await priceFeed.get(denominationToken.address, anotherToken.address))[0].div(PCT_ONE) const expectedAnotherTokenBalance = anotherTokenRate.mul(requestedAnotherTokenAmount).plus(previousAnotherTokenBalance).trunc() assert.equal(currentAnotherTokenBalance.toString(), expectedAnotherTokenBalance.toString(), 'current token balance does not match') }) @@ -206,7 +214,6 @@ contract('Payroll bonuses', ([owner, employee, anyone]) => { assert.equal(denominationTokenEvent.amount.toString(), requestedDenominationTokenAmount, 'payment amount does not match') assert.equal(denominationTokenEvent.paymentReference, 'Bonus', 'payment reference does not match') - const anotherTokenRate = (await priceFeed.get(denominationToken.address, anotherToken.address))[0].div(PCT_ONE) const anotherTokenEvent = events.find(e => e.args.token === anotherToken.address).args assert.equal(anotherTokenEvent.employee, employee, 'employee address does not match') assert.equal(anotherTokenEvent.token, anotherToken.address, 'token address does not match') @@ -236,7 +243,8 @@ contract('Payroll bonuses', ([owner, employee, anyone]) => { context('when exchange rates are expired', () => { beforeEach('expire exchange rates', async () => { - await priceFeed.mockSetTimestamp(NOW - TWO_MONTHS) + const expiredTimestamp = (await payroll.getTimestampPublic()).sub(RATE_EXPIRATION_TIME + 1) + await setTokenRates(priceFeed, denominationToken, [anotherToken], [anotherTokenRate], expiredTimestamp) }) it('reverts', async () => { @@ -288,7 +296,8 @@ contract('Payroll bonuses', ([owner, employee, anyone]) => { context('when exchange rates are expired', () => { beforeEach('expire exchange rates', async () => { - await priceFeed.mockSetTimestamp(NOW - TWO_MONTHS) + const expiredTimestamp = (await payroll.getTimestampPublic()).sub(RATE_EXPIRATION_TIME + 1) + await setTokenRates(priceFeed, denominationToken, [anotherToken], [anotherTokenRate], expiredTimestamp) }) it('reverts', async () => { @@ -378,7 +387,8 @@ contract('Payroll bonuses', ([owner, employee, anyone]) => { context('when exchange rates are expired', () => { beforeEach('expire exchange rates', async () => { - await priceFeed.mockSetTimestamp(NOW - TWO_MONTHS) + const expiredTimestamp = (await payroll.getTimestampPublic()).sub(RATE_EXPIRATION_TIME + 1) + await setTokenRates(priceFeed, denominationToken, [anotherToken], [anotherTokenRate], expiredTimestamp) }) it('reverts', async () => { diff --git a/future-apps/payroll/test/contracts/Payroll_forwarding.test.js b/future-apps/payroll/test/contracts/Payroll_forwarding.test.js index e64e886993..cc51558211 100644 --- a/future-apps/payroll/test/contracts/Payroll_forwarding.test.js +++ b/future-apps/payroll/test/contracts/Payroll_forwarding.test.js @@ -1,12 +1,12 @@ const { assertRevert } = require('@aragon/test-helpers/assertThrow') const { encodeCallScript } = require('@aragon/test-helpers/evmScript') const { getEventArgument } = require('../helpers/events') -const { deployErc20TokenAndDeposit, deployContracts, createPayrollInstance, mockTimestamps } = require('../helpers/setup.js')(artifacts, web3) +const { deployErc20TokenAndDeposit, deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy.js')(artifacts, web3) const ExecutionTarget = artifacts.require('ExecutionTarget') -contract('Payroll forwarding,', ([owner, employee, anotherEmployee, anyone]) => { - let dao, payroll, payrollBase, finance, vault, priceFeed, denominationToken, anotherToken +contract('Payroll forwarding,', ([owner, employee, anyone]) => { + let dao, payroll, payrollBase, finance, vault, priceFeed, denominationToken const NOW = 1553703809 // random fixed timestamp in seconds const ONE_MONTH = 60 * 60 * 24 * 31 @@ -15,15 +15,13 @@ contract('Payroll forwarding,', ([owner, employee, anotherEmployee, anyone]) => const TOKEN_DECIMALS = 18 - before('setup base apps and tokens', async () => { - ({ dao, finance, vault, priceFeed, payrollBase } = await deployContracts(owner)) - anotherToken = await deployErc20TokenAndDeposit(owner, finance, vault, 'Another token', TOKEN_DECIMALS) - denominationToken = await deployErc20TokenAndDeposit(owner, finance, vault, 'Denomination Token', TOKEN_DECIMALS) + before('deploy base apps and tokens', async () => { + ({ dao, finance, vault, payrollBase } = await deployContracts(owner)) + denominationToken = await deployErc20TokenAndDeposit(owner, finance, 'Denomination Token', TOKEN_DECIMALS) }) - beforeEach('setup payroll instance', async () => { - payroll = await createPayrollInstance(dao, payrollBase, owner) - await mockTimestamps(payroll, priceFeed, NOW) + beforeEach('create payroll and price feed instance', async () => { + ({ payroll, priceFeed } = await createPayrollAndPriceFeed(dao, payrollBase, owner, NOW)) }) describe('isForwarder', () => { 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..257201e878 100644 --- a/future-apps/payroll/test/contracts/Payroll_gas_costs.test.js +++ b/future-apps/payroll/test/contracts/Payroll_gas_costs.test.js @@ -1,9 +1,10 @@ const PAYMENT_TYPES = require('../helpers/payment_types') +const setTokenRates = require('../helpers/set_token_rates')(web3) const { annualSalaryPerSecond } = require('../helpers/numbers')(web3) -const { deployErc20TokenAndDeposit, deployContracts, createPayrollInstance, mockTimestamps } = require('../helpers/setup.js')(artifacts, web3) +const { deployErc20TokenAndDeposit, deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy.js')(artifacts, web3) contract('Payroll gas costs', ([owner, employee, anotherEmployee]) => { - let dao, payroll, payrollBase, finance, vault, priceFeed, denominationToken, anotherToken + let dao, payroll, payrollBase, finance, vault, priceFeed, denominationToken const NOW = 1553703809 // random fixed timestamp in seconds const ONE_MONTH = 60 * 60 * 24 * 31 @@ -12,23 +13,21 @@ contract('Payroll gas costs', ([owner, employee, anotherEmployee]) => { const TOKEN_DECIMALS = 18 - before('setup base apps and tokens', async () => { - ({ dao, finance, vault, priceFeed, payrollBase } = await deployContracts(owner)) - anotherToken = await deployErc20TokenAndDeposit(owner, finance, vault, 'Another token', TOKEN_DECIMALS) - denominationToken = await deployErc20TokenAndDeposit(owner, finance, vault, 'Denomination Token', TOKEN_DECIMALS) + before('deploy base apps and tokens', async () => { + ({ dao, finance, vault, payrollBase } = await deployContracts(owner)) + denominationToken = await deployErc20TokenAndDeposit(owner, finance, 'Denomination Token', TOKEN_DECIMALS) }) - beforeEach('setup payroll instance', async () => { - payroll = await createPayrollInstance(dao, payrollBase, owner) - await mockTimestamps(payroll, priceFeed, NOW) + beforeEach('create payroll and price feed instance', async () => { + ({ payroll, priceFeed } = await createPayrollAndPriceFeed(dao, payrollBase, owner, NOW)) }) describe('gas costs', () => { let erc20Token1, erc20Token2 - before('deploy tokens', async () => { - erc20Token1 = await deployErc20TokenAndDeposit(owner, finance, vault, 'Token 1', 16) - erc20Token2 = await deployErc20TokenAndDeposit(owner, finance, vault, 'Token 2', 18) + before('deploy more tokens', async () => { + erc20Token1 = await deployErc20TokenAndDeposit(owner, finance, 'Token 1', 16) + erc20Token2 = await deployErc20TokenAndDeposit(owner, finance, 'Token 2', 18) }) beforeEach('initialize payroll app', async () => { @@ -53,10 +52,13 @@ contract('Payroll gas costs', ([owner, employee, anotherEmployee]) => { }) context('when there are some allowed tokens', function () { - beforeEach('allow tokens', async () => { + const erc20Token1Rate = 2, erc20Token2Rate = 5 + + beforeEach('allow tokens and set rates', async () => { await payroll.addAllowedToken(denominationToken.address, { from: owner }) await payroll.addAllowedToken(erc20Token1.address, { from: owner }) await payroll.addAllowedToken(erc20Token2.address, { from: owner }) + await setTokenRates(priceFeed, denominationToken, [erc20Token1, erc20Token2], [erc20Token1Rate, erc20Token2Rate]) }) it('expends ~270k gas per allowed token', async () => { diff --git a/future-apps/payroll/test/contracts/Payroll_get_employee.test.js b/future-apps/payroll/test/contracts/Payroll_get_employee.test.js index 5213be82d4..6fb9f34fe5 100644 --- a/future-apps/payroll/test/contracts/Payroll_get_employee.test.js +++ b/future-apps/payroll/test/contracts/Payroll_get_employee.test.js @@ -1,10 +1,10 @@ const { maxUint64 } = require('../helpers/numbers')(web3) const { assertRevert } = require('@aragon/test-helpers/assertThrow') const { getEventArgument } = require('../helpers/events') -const { deployErc20TokenAndDeposit, deployContracts, createPayrollInstance, mockTimestamps } = require('../helpers/setup.js')(artifacts, web3) +const { deployErc20TokenAndDeposit, deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy.js')(artifacts, web3) contract('Payroll employee getters', ([owner, employee]) => { - let dao, payroll, payrollBase, finance, vault, priceFeed, denominationToken, anotherToken + let dao, payroll, payrollBase, finance, vault, priceFeed, denominationToken const NOW = 1553703809 // random fixed timestamp in seconds const ONE_MONTH = 60 * 60 * 24 * 31 @@ -15,15 +15,13 @@ contract('Payroll employee getters', ([owner, employee]) => { const currentTimestamp = async () => payroll.getTimestampPublic() - before('setup base apps and tokens', async () => { - ({ dao, finance, vault, priceFeed, payrollBase } = await deployContracts(owner)) - anotherToken = await deployErc20TokenAndDeposit(owner, finance, vault, 'Another token', TOKEN_DECIMALS) - denominationToken = await deployErc20TokenAndDeposit(owner, finance, vault, 'Denomination Token', TOKEN_DECIMALS) + before('deploy base apps and tokens', async () => { + ({ dao, finance, vault, payrollBase } = await deployContracts(owner)) + denominationToken = await deployErc20TokenAndDeposit(owner, finance, 'Denomination Token', TOKEN_DECIMALS) }) - beforeEach('setup payroll instance', async () => { - payroll = await createPayrollInstance(dao, payrollBase, owner) - await mockTimestamps(payroll, priceFeed, NOW) + beforeEach('create payroll and price feed instance', async () => { + ({ payroll, priceFeed } = await createPayrollAndPriceFeed(dao, payrollBase, owner, NOW)) }) describe('getEmployee', () => { diff --git a/future-apps/payroll/test/contracts/Payroll_initialize.test.js b/future-apps/payroll/test/contracts/Payroll_initialize.test.js index 1b85dc3dde..95ab36b032 100644 --- a/future-apps/payroll/test/contracts/Payroll_initialize.test.js +++ b/future-apps/payroll/test/contracts/Payroll_initialize.test.js @@ -1,10 +1,10 @@ const { assertRevert } = require('@aragon/test-helpers/assertThrow') -const { deployErc20TokenAndDeposit, deployContracts, createPayrollInstance, mockTimestamps } = require('../helpers/setup.js')(artifacts, web3) +const { deployErc20TokenAndDeposit, deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy.js')(artifacts, web3) const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' contract('Payroll initialization', ([owner]) => { - let dao, payroll, payrollBase, finance, vault, priceFeed, denominationToken, anotherToken + let dao, payroll, payrollBase, finance, vault, priceFeed, denominationToken const NOW = 1553703809 // random fixed timestamp in seconds const ONE_MONTH = 60 * 60 * 24 * 31 @@ -13,15 +13,13 @@ contract('Payroll initialization', ([owner]) => { const TOKEN_DECIMALS = 18 - before('setup base apps and tokens', async () => { - ({ dao, finance, vault, priceFeed, payrollBase } = await deployContracts(owner)) - anotherToken = await deployErc20TokenAndDeposit(owner, finance, vault, 'Another token', TOKEN_DECIMALS) - denominationToken = await deployErc20TokenAndDeposit(owner, finance, vault, 'Denomination Token', TOKEN_DECIMALS) + before('deploy base apps and tokens', async () => { + ({ dao, finance, vault, payrollBase } = await deployContracts(owner)) + denominationToken = await deployErc20TokenAndDeposit(owner, finance, 'Denomination Token', TOKEN_DECIMALS) }) - beforeEach('setup payroll instance', async () => { - payroll = await createPayrollInstance(dao, payrollBase, owner) - await mockTimestamps(payroll, priceFeed, NOW) + beforeEach('create payroll and price feed instance', async () => { + ({ payroll, priceFeed } = await createPayrollAndPriceFeed(dao, payrollBase, owner, NOW)) }) describe('initialize', function () { diff --git a/future-apps/payroll/test/contracts/Payroll_modify_employee.test.js b/future-apps/payroll/test/contracts/Payroll_modify_employee.test.js index 58c15f1fca..6bfe6389ed 100644 --- a/future-apps/payroll/test/contracts/Payroll_modify_employee.test.js +++ b/future-apps/payroll/test/contracts/Payroll_modify_employee.test.js @@ -1,12 +1,12 @@ const { assertRevert } = require('@aragon/test-helpers/assertThrow') const { annualSalaryPerSecond } = require('../helpers/numbers')(web3) const { getEvents, getEventArgument } = require('../helpers/events') -const { deployErc20TokenAndDeposit, deployContracts, createPayrollInstance, mockTimestamps } = require('../helpers/setup.js')(artifacts, web3) +const { deployErc20TokenAndDeposit, deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy.js')(artifacts, web3) const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' contract('Payroll employees modification', ([owner, employee, anotherEmployee, anyone]) => { - let dao, payroll, payrollBase, finance, vault, priceFeed, denominationToken, anotherToken + let dao, payroll, payrollBase, finance, vault, priceFeed, denominationToken const NOW = 1553703809 // random fixed timestamp in seconds const ONE_MONTH = 60 * 60 * 24 * 31 @@ -15,15 +15,18 @@ contract('Payroll employees modification', ([owner, employee, anotherEmployee, a const TOKEN_DECIMALS = 18 - before('setup base apps and tokens', async () => { - ({ dao, finance, vault, priceFeed, payrollBase } = await deployContracts(owner)) - anotherToken = await deployErc20TokenAndDeposit(owner, finance, vault, 'Another token', TOKEN_DECIMALS) - denominationToken = await deployErc20TokenAndDeposit(owner, finance, vault, 'Denomination Token', TOKEN_DECIMALS) + const increaseTime = async seconds => { + await payroll.mockIncreaseTime(seconds) + await priceFeed.mockIncreaseTime(seconds) + } + + before('deploy base apps and tokens', async () => { + ({ dao, finance, vault, payrollBase } = await deployContracts(owner)) + denominationToken = await deployErc20TokenAndDeposit(owner, finance, 'Denomination Token', TOKEN_DECIMALS) }) - beforeEach('setup payroll instance', async () => { - payroll = await createPayrollInstance(dao, payrollBase, owner) - await mockTimestamps(payroll, priceFeed, NOW) + beforeEach('create payroll and price feed instance', async () => { + ({ payroll, priceFeed } = await createPayrollAndPriceFeed(dao, payrollBase, owner, NOW)) }) describe('setEmployeeSalary', () => { @@ -55,10 +58,10 @@ contract('Payroll employees modification', ([owner, employee, anotherEmployee, a }) it('adds previous owed salary to the accrued salary', async () => { - await payroll.mockIncreaseTime(ONE_MONTH) + await increaseTime(ONE_MONTH) const receipt = await payroll.setEmployeeSalary(employeeId, newSalary, { from }) - await payroll.mockIncreaseTime(ONE_MONTH) + await increaseTime(ONE_MONTH) const accruedSalary = (await payroll.getEmployee(employeeId))[4] const expectedAccruedSalary = previousSalary * ONE_MONTH @@ -71,9 +74,9 @@ contract('Payroll employees modification', ([owner, employee, anotherEmployee, a }) it('accrues all previous owed salary as accrued salary', async () => { - await payroll.mockIncreaseTime(ONE_MONTH) + await increaseTime(ONE_MONTH) await payroll.setEmployeeSalary(employeeId, newSalary, { from }) - await payroll.mockIncreaseTime(ONE_MONTH) + await increaseTime(ONE_MONTH) await payroll.setEmployeeSalary(employeeId, newSalary * 2, { from }) const accruedSalary = (await payroll.getEmployee(employeeId))[4] @@ -107,7 +110,7 @@ contract('Payroll employees modification', ([owner, employee, anotherEmployee, a context('when the given employee is not active', () => { beforeEach('terminate employee', async () => { await payroll.terminateEmployeeNow(employeeId, { from: owner }) - await payroll.mockIncreaseTime(ONE_MONTH) + await increaseTime(ONE_MONTH) }) it('reverts', async () => { @@ -215,7 +218,7 @@ contract('Payroll employees modification', ([owner, employee, anotherEmployee, a context('when the given employee is not active', () => { beforeEach('terminate employee', async () => { await payroll.terminateEmployeeNow(employeeId, { from: owner }) - await payroll.mockIncreaseTime(ONE_MONTH) + await increaseTime(ONE_MONTH) }) itHandlesChangingEmployeeAddressSuccessfully() diff --git a/future-apps/payroll/test/contracts/Payroll_payday.test.js b/future-apps/payroll/test/contracts/Payroll_payday.test.js index 99c5857294..c1c6cc5714 100644 --- a/future-apps/payroll/test/contracts/Payroll_payday.test.js +++ b/future-apps/payroll/test/contracts/Payroll_payday.test.js @@ -1,29 +1,33 @@ const PAYMENT_TYPES = require('../helpers/payment_types') +const setTokenRates = require('../helpers/set_token_rates')(web3) const { assertRevert } = require('@aragon/test-helpers/assertThrow') +const { bn, maxUint256 } = require('../helpers/numbers')(web3) const { getEventArgument } = require('../helpers/events') -const { bigExp, maxUint256 } = require('../helpers/numbers')(web3) -const { deployErc20TokenAndDeposit, deployContracts, createPayrollInstance, mockTimestamps } = require('../helpers/setup.js')(artifacts, web3) +const { deployErc20TokenAndDeposit, deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy.js')(artifacts, web3) contract('Payroll payday', ([owner, employee, anyone]) => { - let dao, payroll, payrollBase, finance, vault, priceFeed, denominationToken, anotherToken + let dao, payroll, payrollBase, finance, vault, priceFeed, denominationToken, anotherToken, anotherTokenRate const NOW = 1553703809 // random fixed timestamp in seconds const ONE_MONTH = 60 * 60 * 24 * 31 const TWO_MONTHS = ONE_MONTH * 2 const RATE_EXPIRATION_TIME = TWO_MONTHS - const PCT_ONE = bigExp(1, 18) const TOKEN_DECIMALS = 18 - before('setup base apps and tokens', async () => { - ({ dao, finance, vault, priceFeed, payrollBase } = await deployContracts(owner)) - anotherToken = await deployErc20TokenAndDeposit(owner, finance, vault, 'Another token', TOKEN_DECIMALS) - denominationToken = await deployErc20TokenAndDeposit(owner, finance, vault, 'Denomination Token', TOKEN_DECIMALS) + const increaseTime = async seconds => { + await payroll.mockIncreaseTime(seconds) + await priceFeed.mockIncreaseTime(seconds) + } + + before('deploy base apps and tokens', async () => { + ({ dao, finance, vault, payrollBase } = await deployContracts(owner)) + anotherToken = await deployErc20TokenAndDeposit(owner, finance, 'Another token', TOKEN_DECIMALS) + denominationToken = await deployErc20TokenAndDeposit(owner, finance, 'Denomination Token', TOKEN_DECIMALS) }) - beforeEach('setup payroll instance', async () => { - payroll = await createPayrollInstance(dao, payrollBase, owner) - await mockTimestamps(payroll, priceFeed, NOW) + beforeEach('create payroll and price feed instance', async () => { + ({ payroll, priceFeed } = await createPayrollAndPriceFeed(dao, payrollBase, owner, NOW)) }) describe('payroll payday', () => { @@ -32,6 +36,11 @@ contract('Payroll payday', ([owner, employee, anyone]) => { await payroll.initialize(finance.address, denominationToken.address, priceFeed.address, RATE_EXPIRATION_TIME, { from: owner }) }) + beforeEach('set token rates', async () => { + anotherTokenRate = bn(5) + await setTokenRates(priceFeed, denominationToken, [anotherToken], [anotherTokenRate]) + }) + context('when the sender is an employee', () => { let employeeId const from = employee @@ -69,7 +78,6 @@ contract('Payroll payday', ([owner, employee, anyone]) => { assert.equal(currentDenominationTokenBalance.toString(), expectedDenominationTokenBalance.toString(), 'current denomination token balance does not match') const currentAnotherTokenBalance = await anotherToken.balanceOf(employee) - const anotherTokenRate = (await priceFeed.get(denominationToken.address, anotherToken.address))[0].div(PCT_ONE) const expectedAnotherTokenBalance = anotherTokenRate.mul(requestedAnotherTokenAmount).plus(previousAnotherTokenBalance).trunc() assert.equal(currentAnotherTokenBalance.toString(), expectedAnotherTokenBalance.toString(), 'current token balance does not match') }) @@ -86,7 +94,6 @@ contract('Payroll payday', ([owner, employee, anyone]) => { assert.equal(denominationTokenEvent.amount.toString(), requestedDenominationTokenAmount, 'payment amount does not match') assert.equal(denominationTokenEvent.paymentReference, 'Payroll', 'payment reference does not match') - const anotherTokenRate = (await priceFeed.get(denominationToken.address, anotherToken.address))[0].div(PCT_ONE) const anotherTokenEvent = events.find(e => e.args.token === anotherToken.address).args assert.equal(anotherTokenEvent.employee, employee, 'employee address does not match') assert.equal(anotherTokenEvent.token, anotherToken.address, 'token address does not match') @@ -107,8 +114,8 @@ contract('Payroll payday', ([owner, employee, anyone]) => { const newDenominationTokenOwedAmount = Math.round(newOwedAmount * denominationTokenAllocation / 100) const newAnotherTokenOwedAmount = Math.round(newOwedAmount * anotherTokenAllocation / 100) - await payroll.mockIncreaseTime(ONE_MONTH) - await priceFeed.mockIncreaseTime(ONE_MONTH) + await increaseTime(ONE_MONTH) + await setTokenRates(priceFeed, denominationToken, [anotherToken], [anotherTokenRate]) await payroll.payday(PAYMENT_TYPES.PAYROLL, newOwedAmount, { from }) const currentDenominationTokenBalance = await denominationToken.balanceOf(employee) @@ -116,7 +123,6 @@ contract('Payroll payday', ([owner, employee, anyone]) => { assert.equal(currentDenominationTokenBalance.toString(), expectedDenominationTokenBalance.toString(), 'current denomination token balance does not match') const currentAnotherTokenBalance = await anotherToken.balanceOf(employee) - const anotherTokenRate = (await priceFeed.get(denominationToken.address, anotherToken.address))[0].div(PCT_ONE) const expectedAnotherTokenBalance = anotherTokenRate.mul(requestedAnotherTokenAmount + newAnotherTokenOwedAmount).plus(previousAnotherTokenBalance) assert.equal(currentAnotherTokenBalance.toString(), expectedAnotherTokenBalance.toString(), 'current token balance does not match') }) @@ -161,7 +167,8 @@ contract('Payroll payday', ([owner, employee, anyone]) => { context('when exchange rates are expired', () => { beforeEach('expire exchange rates', async () => { - await priceFeed.mockSetTimestamp(NOW - TWO_MONTHS) + const expiredTimestamp = (await payroll.getTimestampPublic()).sub(RATE_EXPIRATION_TIME + 1) + await setTokenRates(priceFeed, denominationToken, [anotherToken], [anotherTokenRate], expiredTimestamp) }) it('reverts', async () => { @@ -253,7 +260,8 @@ contract('Payroll payday', ([owner, employee, anyone]) => { context('when exchange rates are expired', () => { beforeEach('expire exchange rates', async () => { - await priceFeed.mockSetTimestamp(NOW - TWO_MONTHS) + const expiredTimestamp = (await payroll.getTimestampPublic()).sub(RATE_EXPIRATION_TIME + 1) + await setTokenRates(priceFeed, denominationToken, [anotherToken], [anotherTokenRate], expiredTimestamp) }) it('reverts', async () => { @@ -272,7 +280,7 @@ contract('Payroll payday', ([owner, employee, anyone]) => { const currentOwedSalary = salary * ONE_MONTH beforeEach('accumulate some pending salary', async () => { - await payroll.mockIncreaseTime(ONE_MONTH) + await increaseTime(ONE_MONTH) }) context('when the requested amount is zero', () => { @@ -342,8 +350,7 @@ contract('Payroll payday', ([owner, employee, anyone]) => { beforeEach('accrue some salary', async () => { await payroll.setEmployeeSalary(employeeId, previousSalary, { from: owner }) - await payroll.mockIncreaseTime(ONE_MONTH) - await priceFeed.mockIncreaseTime(ONE_MONTH) + await increaseTime(ONE_MONTH) await payroll.setEmployeeSalary(employeeId, salary, { from: owner }) }) @@ -351,8 +358,9 @@ contract('Payroll payday', ([owner, employee, anyone]) => { const currentOwedSalary = salary * ONE_MONTH const totalOwedSalary = previousOwedSalary + currentOwedSalary - beforeEach('accumulate some pending salary', async () => { - await payroll.mockIncreaseTime(ONE_MONTH) + beforeEach('accumulate some pending salary and renew token rates', async () => { + await increaseTime(ONE_MONTH) + await setTokenRates(priceFeed, denominationToken, [anotherToken], [anotherTokenRate]) }) context('when the requested amount is zero', () => { @@ -423,7 +431,7 @@ contract('Payroll payday', ([owner, employee, anyone]) => { const owedSalary = salary * ONE_MONTH beforeEach('accumulate some pending salary', async () => { - await payroll.mockIncreaseTime(ONE_MONTH) + await increaseTime(ONE_MONTH) }) context('when the requested amount is lower than the total owed salary', () => { @@ -477,7 +485,8 @@ contract('Payroll payday', ([owner, employee, anyone]) => { context('when exchange rates are expired', () => { beforeEach('expire exchange rates', async () => { - await priceFeed.mockSetTimestamp(NOW - TWO_MONTHS) + const expiredTimestamp = (await payroll.getTimestampPublic()).sub(RATE_EXPIRATION_TIME + 1) + await setTokenRates(priceFeed, denominationToken, [anotherToken], [anotherTokenRate], expiredTimestamp) }) itReverts(requestedAmount, expiredRatesReason) @@ -529,7 +538,7 @@ contract('Payroll payday', ([owner, employee, anyone]) => { const itRevertsAnyAttemptToWithdrawPartialPayroll = () => { context('when the employee has some pending salary', () => { beforeEach('accumulate some pending salary', async () => { - await payroll.mockIncreaseTime(ONE_MONTH) + await increaseTime(ONE_MONTH) }) context('when the requested amount is greater than zero', () => { @@ -600,7 +609,7 @@ contract('Payroll payday', ([owner, employee, anyone]) => { const owedSalary = maxUint256() beforeEach('accumulate some pending salary', async () => { - await payroll.mockIncreaseTime(ONE_MONTH) + await increaseTime(ONE_MONTH) }) context('when the requested amount is zero', () => { @@ -627,7 +636,6 @@ contract('Payroll payday', ([owner, employee, anyone]) => { assert.equal(currentDenominationTokenBalance.toString(), expectedDenominationTokenBalance.toString(), 'current denomination token balance does not match') const currentAnotherTokenBalance = await anotherToken.balanceOf(employee) - const anotherTokenRate = (await priceFeed.get(denominationToken.address, anotherToken.address))[0].div(PCT_ONE) const expectedAnotherTokenBalance = anotherTokenRate.mul(requestedAnotherTokenAmount).plus(previousAnotherTokenBalance).trunc() assert.equal(currentAnotherTokenBalance.toString(), expectedAnotherTokenBalance.toString(), 'current token balance does not match') }) @@ -644,7 +652,6 @@ contract('Payroll payday', ([owner, employee, anyone]) => { assert.equal(denominationTokenEvent.amount.toString(), requestedDenominationTokenAmount, 'payment amount does not match') assert.equal(denominationTokenEvent.paymentReference, 'Payroll', 'payment reference does not match') - const anotherTokenRate = (await priceFeed.get(denominationToken.address, anotherToken.address))[0].div(PCT_ONE) const anotherTokenEvent = events.find(e => e.args.token === anotherToken.address).args assert.equal(anotherTokenEvent.employee, employee, 'employee address does not match') assert.equal(anotherTokenEvent.token, anotherToken.address, 'token address does not match') @@ -661,8 +668,8 @@ contract('Payroll payday', ([owner, employee, anyone]) => { await payroll.payday(PAYMENT_TYPES.PAYROLL, requestedAmount, { from }) - await payroll.mockIncreaseTime(ONE_MONTH) - await priceFeed.mockIncreaseTime(ONE_MONTH) + await increaseTime(ONE_MONTH) + await setTokenRates(priceFeed, denominationToken, [anotherToken], [anotherTokenRate]) await payroll.payday(PAYMENT_TYPES.PAYROLL, requestedAmount, { from }) const currentDenominationTokenBalance = await denominationToken.balanceOf(employee) @@ -670,7 +677,6 @@ contract('Payroll payday', ([owner, employee, anyone]) => { assert.equal(currentDenominationTokenBalance.toString(), expectedDenominationTokenBalance.toString(), 'current denomination token balance does not match') const currentAnotherTokenBalance = await anotherToken.balanceOf(employee) - const anotherTokenRate = (await priceFeed.get(denominationToken.address, anotherToken.address))[0].div(PCT_ONE) const expectedAnotherTokenBalance = anotherTokenRate.mul(requestedAnotherTokenAmount * 2).plus(previousAnotherTokenBalance) assert.equal(currentAnotherTokenBalance.toString(), expectedAnotherTokenBalance.toString(), 'current token balance does not match') }) @@ -706,7 +712,8 @@ contract('Payroll payday', ([owner, employee, anyone]) => { context('when exchange rates are expired', () => { beforeEach('expire exchange rates', async () => { - await priceFeed.mockSetTimestamp(NOW - TWO_MONTHS) + const expiredTimestamp = (await payroll.getTimestampPublic()).sub(RATE_EXPIRATION_TIME + 1) + await setTokenRates(priceFeed, denominationToken, [anotherToken], [anotherTokenRate], expiredTimestamp) }) it('reverts', async () => { @@ -775,7 +782,7 @@ contract('Payroll payday', ([owner, employee, anyone]) => { const owedSalary = maxUint256() beforeEach('accumulate some pending salary', async () => { - await payroll.mockIncreaseTime(ONE_MONTH) + await increaseTime(ONE_MONTH) }) context('when the requested amount is zero', () => { diff --git a/future-apps/payroll/test/contracts/Payroll_reimbursements.test.js b/future-apps/payroll/test/contracts/Payroll_reimbursements.test.js index 4b874ff6fe..8e9fcbe026 100644 --- a/future-apps/payroll/test/contracts/Payroll_reimbursements.test.js +++ b/future-apps/payroll/test/contracts/Payroll_reimbursements.test.js @@ -1,29 +1,33 @@ const PAYMENT_TYPES = require('../helpers/payment_types') +const setTokenRates = require('../helpers/set_token_rates')(web3) const { assertRevert } = require('@aragon/test-helpers/assertThrow') +const { bn, maxUint256 } = require('../helpers/numbers')(web3) const { getEvents, getEventArgument } = require('../helpers/events') -const { bigExp, maxUint256 } = require('../helpers/numbers')(web3) -const { deployErc20TokenAndDeposit, deployContracts, createPayrollInstance, mockTimestamps } = require('../helpers/setup.js')(artifacts, web3) +const { deployErc20TokenAndDeposit, deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy.js')(artifacts, web3) contract('Payroll reimbursements', ([owner, employee, anyone]) => { - let dao, payroll, payrollBase, finance, vault, priceFeed, denominationToken, anotherToken + let dao, payroll, payrollBase, finance, vault, priceFeed, denominationToken, anotherToken, anotherTokenRate const NOW = 1553703809 // random fixed timestamp in seconds const ONE_MONTH = 60 * 60 * 24 * 31 const TWO_MONTHS = ONE_MONTH * 2 const RATE_EXPIRATION_TIME = TWO_MONTHS - const PCT_ONE = bigExp(1, 18) const TOKEN_DECIMALS = 18 - before('setup base apps and tokens', async () => { - ({ dao, finance, vault, priceFeed, payrollBase } = await deployContracts(owner)) - anotherToken = await deployErc20TokenAndDeposit(owner, finance, vault, 'Another token', TOKEN_DECIMALS) - denominationToken = await deployErc20TokenAndDeposit(owner, finance, vault, 'Denomination Token', TOKEN_DECIMALS) + const increaseTime = async seconds => { + await payroll.mockIncreaseTime(seconds) + await priceFeed.mockIncreaseTime(seconds) + } + + before('deploy base apps and tokens', async () => { + ({ dao, finance, vault, payrollBase } = await deployContracts(owner)) + anotherToken = await deployErc20TokenAndDeposit(owner, finance, 'Another token', TOKEN_DECIMALS) + denominationToken = await deployErc20TokenAndDeposit(owner, finance, 'Denomination Token', TOKEN_DECIMALS) }) - beforeEach('setup payroll instance', async () => { - payroll = await createPayrollInstance(dao, payrollBase, owner) - await mockTimestamps(payroll, priceFeed, NOW) + beforeEach('create payroll and price feed instance', async () => { + ({ payroll, priceFeed } = await createPayrollAndPriceFeed(dao, payrollBase, owner, NOW)) }) describe('addReimbursement', () => { @@ -89,7 +93,7 @@ contract('Payroll reimbursements', ([owner, employee, anyone]) => { context('when the given employee is not active', () => { beforeEach('terminate employee', async () => { await payroll.terminateEmployeeNow(employeeId, { from: owner }) - await payroll.mockIncreaseTime(ONE_MONTH) + await increaseTime(ONE_MONTH) }) it('reverts', async () => { @@ -134,6 +138,11 @@ contract('Payroll reimbursements', ([owner, employee, anyone]) => { await payroll.initialize(finance.address, denominationToken.address, priceFeed.address, RATE_EXPIRATION_TIME, { from: owner }) }) + beforeEach('set token rates', async () => { + anotherTokenRate = bn(5) + await setTokenRates(priceFeed, denominationToken, [anotherToken], [anotherTokenRate]) + }) + context('when the sender is an employee', () => { const from = employee let employeeId, salary = 1000 @@ -142,7 +151,7 @@ contract('Payroll reimbursements', ([owner, employee, anyone]) => { const receipt = await payroll.addEmployeeNow(employee, salary, 'Boss') employeeId = getEventArgument(receipt, 'AddEmployee', 'employeeId') - await payroll.mockIncreaseTime(ONE_MONTH) + await increaseTime(ONE_MONTH) }) context('when the employee has already set some token allocations', () => { @@ -178,7 +187,6 @@ contract('Payroll reimbursements', ([owner, employee, anyone]) => { assert.equal(currentDenominationTokenBalance.toString(), expectedDenominationTokenBalance.toString(), 'current denomination token balance does not match') const currentAnotherTokenBalance = await anotherToken.balanceOf(employee) - const anotherTokenRate = (await priceFeed.get(denominationToken.address, anotherToken.address))[0].div(PCT_ONE) const expectedAnotherTokenBalance = anotherTokenRate.mul(requestedAnotherTokenAmount).plus(previousAnotherTokenBalance).trunc() assert.equal(currentAnotherTokenBalance.toString(), expectedAnotherTokenBalance.toString(), 'current token balance does not match') }) @@ -195,7 +203,6 @@ contract('Payroll reimbursements', ([owner, employee, anyone]) => { assert.equal(denominationTokenEvent.amount.toString(), requestedDenominationTokenAmount, 'payment amount does not match') assert.equal(denominationTokenEvent.paymentReference, 'Reimbursement', 'payment reference does not match') - const anotherTokenRate = (await priceFeed.get(denominationToken.address, anotherToken.address))[0].div(PCT_ONE) const anotherTokenEvent = events.find(e => e.args.token === anotherToken.address).args assert.equal(anotherTokenEvent.employee, employee, 'employee address does not match') assert.equal(anotherTokenEvent.token, anotherToken.address, 'token address does not match') @@ -225,7 +232,8 @@ contract('Payroll reimbursements', ([owner, employee, anyone]) => { context('when exchange rates are expired', () => { beforeEach('expire exchange rates', async () => { - await priceFeed.mockSetTimestamp(NOW - TWO_MONTHS) + const expiredTimestamp = (await payroll.getTimestampPublic()).sub(RATE_EXPIRATION_TIME + 1) + await setTokenRates(priceFeed, denominationToken, [anotherToken], [anotherTokenRate], expiredTimestamp) }) it('reverts', async () => { @@ -277,7 +285,8 @@ contract('Payroll reimbursements', ([owner, employee, anyone]) => { context('when exchange rates are expired', () => { beforeEach('expire exchange rates', async () => { - await priceFeed.mockSetTimestamp(NOW - TWO_MONTHS) + const expiredTimestamp = (await payroll.getTimestampPublic()).sub(RATE_EXPIRATION_TIME + 1) + await setTokenRates(priceFeed, denominationToken, [anotherToken], [anotherTokenRate], expiredTimestamp) }) it('reverts', async () => { @@ -367,7 +376,8 @@ contract('Payroll reimbursements', ([owner, employee, anyone]) => { context('when exchange rates are expired', () => { beforeEach('expire exchange rates', async () => { - await priceFeed.mockSetTimestamp(NOW - TWO_MONTHS) + const expiredTimestamp = (await payroll.getTimestampPublic()).sub(RATE_EXPIRATION_TIME + 1) + await setTokenRates(priceFeed, denominationToken, [anotherToken], [anotherTokenRate], expiredTimestamp) }) it('reverts', async () => { diff --git a/future-apps/payroll/test/contracts/Payroll_settings.test.js b/future-apps/payroll/test/contracts/Payroll_settings.test.js index 4f994dc9fd..43cb3ac8c8 100644 --- a/future-apps/payroll/test/contracts/Payroll_settings.test.js +++ b/future-apps/payroll/test/contracts/Payroll_settings.test.js @@ -1,12 +1,12 @@ const { getEvents } = require('../helpers/events') const { assertRevert } = require('@aragon/test-helpers/assertThrow') -const { deployErc20TokenAndDeposit, deployContracts, createPayrollInstance, mockTimestamps } = require('../helpers/setup.js')(artifacts, web3) +const { deployErc20TokenAndDeposit, deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy.js')(artifacts, web3) const PriceFeed = artifacts.require('PriceFeedMock') const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' -contract('Payroll settings', ([owner, employee, anotherEmployee, anyone]) => { - let dao, payroll, payrollBase, finance, vault, priceFeed, denominationToken, anotherToken +contract('Payroll settings', ([owner, anyone]) => { + let dao, payroll, payrollBase, finance, vault, priceFeed, denominationToken const NOW = 1553703809 // random fixed timestamp in seconds const ONE_MONTH = 60 * 60 * 24 * 31 @@ -15,15 +15,13 @@ contract('Payroll settings', ([owner, employee, anotherEmployee, anyone]) => { const TOKEN_DECIMALS = 18 - before('setup base apps and tokens', async () => { - ({ dao, finance, vault, priceFeed, payrollBase } = await deployContracts(owner)) - anotherToken = await deployErc20TokenAndDeposit(owner, finance, vault, 'Another token', TOKEN_DECIMALS) - denominationToken = await deployErc20TokenAndDeposit(owner, finance, vault, 'Denomination Token', TOKEN_DECIMALS) + before('deploy base apps and tokens', async () => { + ({ dao, finance, vault, payrollBase } = await deployContracts(owner)) + denominationToken = await deployErc20TokenAndDeposit(owner, finance, 'Denomination Token', TOKEN_DECIMALS) }) - beforeEach('setup payroll instance', async () => { - payroll = await createPayrollInstance(dao, payrollBase, owner) - await mockTimestamps(payroll, priceFeed, NOW) + beforeEach('create payroll and price feed instance', async () => { + ({ payroll, priceFeed } = await createPayrollAndPriceFeed(dao, payrollBase, owner, NOW)) }) describe('setPriceFeed', () => { diff --git a/future-apps/payroll/test/contracts/Payroll_terminate_employee.test.js b/future-apps/payroll/test/contracts/Payroll_terminate_employee.test.js index 918d8eab63..87eb3f786a 100644 --- a/future-apps/payroll/test/contracts/Payroll_terminate_employee.test.js +++ b/future-apps/payroll/test/contracts/Payroll_terminate_employee.test.js @@ -2,10 +2,10 @@ const PAYMENT_TYPES = require('../helpers/payment_types') const { assertRevert } = require('@aragon/test-helpers/assertThrow') const { getEvents, getEventArgument } = require('../helpers/events') const { bn, maxUint64, annualSalaryPerSecond } = require('../helpers/numbers')(web3) -const { deployErc20TokenAndDeposit, deployContracts, createPayrollInstance, mockTimestamps } = require('../helpers/setup.js')(artifacts, web3) +const { deployErc20TokenAndDeposit, deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy.js')(artifacts, web3) contract('Payroll employees termination', ([owner, employee, anyone]) => { - let dao, payroll, payrollBase, finance, vault, priceFeed, denominationToken, anotherToken + let dao, payroll, payrollBase, finance, vault, priceFeed, denominationToken const NOW = 1553703809 // random fixed timestamp in seconds const ONE_MONTH = 60 * 60 * 24 * 31 @@ -16,15 +16,18 @@ contract('Payroll employees termination', ([owner, employee, anyone]) => { const currentTimestamp = async () => payroll.getTimestampPublic() - before('setup base apps and tokens', async () => { - ({ dao, finance, vault, priceFeed, payrollBase } = await deployContracts(owner)) - anotherToken = await deployErc20TokenAndDeposit(owner, finance, vault, 'Another token', TOKEN_DECIMALS) - denominationToken = await deployErc20TokenAndDeposit(owner, finance, vault, 'Denomination Token', TOKEN_DECIMALS) + const increaseTime = async seconds => { + await payroll.mockIncreaseTime(seconds) + await priceFeed.mockIncreaseTime(seconds) + } + + before('deploy base apps and tokens', async () => { + ({ dao, finance, vault, payrollBase } = await deployContracts(owner)) + denominationToken = await deployErc20TokenAndDeposit(owner, finance, 'Denomination Token', TOKEN_DECIMALS) }) - beforeEach('setup payroll instance', async () => { - payroll = await createPayrollInstance(dao, payrollBase, owner) - await mockTimestamps(payroll, priceFeed, NOW) + beforeEach('create payroll and price feed instance', async () => { + ({ payroll, priceFeed } = await createPayrollAndPriceFeed(dao, payrollBase, owner, NOW)) }) describe('terminateEmployeeNow', () => { @@ -74,14 +77,14 @@ contract('Payroll employees termination', ([owner, employee, anyone]) => { await payroll.determineAllocation([denominationToken.address], [100], { from: employee }) // Accrue some salary and extras - await payroll.mockIncreaseTime(ONE_MONTH) + await increaseTime(ONE_MONTH) const owedSalary = salary.mul(ONE_MONTH) const reimbursement = 1000 await payroll.addReimbursement(employeeId, reimbursement, { from: owner }) // Terminate employee and travel some time in the future await payroll.terminateEmployeeNow(employeeId, { from }) - await payroll.mockIncreaseTime(ONE_MONTH) + await increaseTime(ONE_MONTH) // Request owed money await payroll.payday(PAYMENT_TYPES.PAYROLL, 0, { from: employee }) @@ -95,11 +98,11 @@ contract('Payroll employees termination', ([owner, employee, anyone]) => { it('can re-add a removed employee', async () => { await payroll.determineAllocation([denominationToken.address], [100], { from: employee }) - await payroll.mockIncreaseTime(ONE_MONTH) + await increaseTime(ONE_MONTH) // Terminate employee and travel some time in the future await payroll.terminateEmployeeNow(employeeId, { from }) - await payroll.mockIncreaseTime(ONE_MONTH) + await increaseTime(ONE_MONTH) // Request owed money await payroll.payday(PAYMENT_TYPES.PAYROLL, 0, { from: employee }) @@ -123,7 +126,7 @@ contract('Payroll employees termination', ([owner, employee, anyone]) => { context('when the employee was already terminated', () => { beforeEach('terminate employee', async () => { await payroll.terminateEmployeeNow(employeeId, { from }) - await payroll.mockIncreaseTime(ONE_MONTH + 1) + await increaseTime(ONE_MONTH + 1) }) it('reverts', async () => { @@ -213,14 +216,14 @@ contract('Payroll employees termination', ([owner, employee, anyone]) => { await payroll.determineAllocation([denominationToken.address], [100], { from: employee }) // Accrue some salary and extras - await payroll.mockIncreaseTime(ONE_MONTH) + await increaseTime(ONE_MONTH) const owedSalary = salary.times(ONE_MONTH) const reimbursement = 1000 await payroll.addReimbursement(employeeId, reimbursement, { from: owner }) // Terminate employee and travel some time in the future await payroll.terminateEmployee(employeeId, endDate, { from }) - await payroll.mockIncreaseTime(ONE_MONTH) + await increaseTime(ONE_MONTH) // Request owed money await payroll.payday(PAYMENT_TYPES.PAYROLL, 0, { from: employee }) @@ -234,11 +237,11 @@ contract('Payroll employees termination', ([owner, employee, anyone]) => { it('can re-add a removed employee', async () => { await payroll.determineAllocation([denominationToken.address], [100], { from: employee }) - await payroll.mockIncreaseTime(ONE_MONTH) + await increaseTime(ONE_MONTH) // Terminate employee and travel some time in the future await payroll.terminateEmployee(employeeId, endDate, { from }) - await payroll.mockIncreaseTime(ONE_MONTH) + await increaseTime(ONE_MONTH) // Request owed money await payroll.payday(PAYMENT_TYPES.PAYROLL, 0, { from: employee }) @@ -262,7 +265,7 @@ contract('Payroll employees termination', ([owner, employee, anyone]) => { context('when the given end date is in the past', () => { beforeEach('set future end date', async () => { endDate = await currentTimestamp() - await payroll.mockIncreaseTime(ONE_MONTH + 1) + await increaseTime(ONE_MONTH + 1) }) it('reverts', async () => { @@ -288,7 +291,7 @@ contract('Payroll employees termination', ([owner, employee, anyone]) => { context('when the previous end date was reached', () => { beforeEach('travel in the future', async () => { - await payroll.mockIncreaseTime(ONE_MONTH + 1) + await increaseTime(ONE_MONTH + 1) }) it('reverts', async () => { diff --git a/future-apps/payroll/test/contracts/Payroll_token_allocations.test.js b/future-apps/payroll/test/contracts/Payroll_token_allocations.test.js index afe9712805..80832c40a7 100644 --- a/future-apps/payroll/test/contracts/Payroll_token_allocations.test.js +++ b/future-apps/payroll/test/contracts/Payroll_token_allocations.test.js @@ -1,11 +1,11 @@ const { assertRevert } = require('@aragon/test-helpers/assertThrow') const { getEvents, getEventArgument } = require('../helpers/events') -const { deployErc20TokenAndDeposit, deployContracts, createPayrollInstance, mockTimestamps } = require('../helpers/setup.js')(artifacts, web3) +const { deployErc20TokenAndDeposit, deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy.js')(artifacts, web3) const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' -contract('Payroll token allocations', ([owner, employee, anotherEmployee, anyone]) => { - let dao, payroll, payrollBase, finance, vault, priceFeed, denominationToken, anotherToken +contract('Payroll token allocations', ([owner, employee, anyone]) => { + let dao, payroll, payrollBase, finance, vault, priceFeed, denominationToken const NOW = 1553703809 // random fixed timestamp in seconds const ONE_MONTH = 60 * 60 * 24 * 31 @@ -14,24 +14,22 @@ contract('Payroll token allocations', ([owner, employee, anotherEmployee, anyone const TOKEN_DECIMALS = 18 - before('setup base apps and tokens', async () => { - ({ dao, finance, vault, priceFeed, payrollBase } = await deployContracts(owner)) - anotherToken = await deployErc20TokenAndDeposit(owner, finance, vault, 'Another token', TOKEN_DECIMALS) - denominationToken = await deployErc20TokenAndDeposit(owner, finance, vault, 'Denomination Token', TOKEN_DECIMALS) + before('deploy base apps and tokens', async () => { + ({ dao, finance, vault, payrollBase } = await deployContracts(owner)) + denominationToken = await deployErc20TokenAndDeposit(owner, finance, 'Denomination Token', TOKEN_DECIMALS) }) - beforeEach('setup payroll instance', async () => { - payroll = await createPayrollInstance(dao, payrollBase, owner) - await mockTimestamps(payroll, priceFeed, NOW) + beforeEach('create payroll and price feed instance', async () => { + ({ payroll, priceFeed } = await createPayrollAndPriceFeed(dao, payrollBase, owner, NOW)) }) describe('determineAllocation', () => { const tokenAddresses = [] before('deploy some tokens', async () => { - const token1 = await deployErc20TokenAndDeposit(owner, finance, vault, 'Token 1', 14) - const token2 = await deployErc20TokenAndDeposit(owner, finance, vault, 'Token 2', 14) - const token3 = await deployErc20TokenAndDeposit(owner, finance, vault, 'Token 3', 14) + const token1 = await deployErc20TokenAndDeposit(owner, finance, 'Token 1', 14) + const token2 = await deployErc20TokenAndDeposit(owner, finance, 'Token 2', 14) + const token3 = await deployErc20TokenAndDeposit(owner, finance, 'Token 3', 14) tokenAddresses.push(token1.address, token2.address, token3.address) }) @@ -82,7 +80,7 @@ contract('Payroll token allocations', ([owner, employee, anotherEmployee, anyone let token beforeEach('submit previous allocation', async () => { - token = await deployErc20TokenAndDeposit(owner, finance, vault, 'Previous Token', 18) + token = await deployErc20TokenAndDeposit(owner, finance, 'Previous Token', 18) await payroll.addAllowedToken(token.address, { from: owner }) await payroll.determineAllocation([token.address], [100], { from }) @@ -140,7 +138,7 @@ contract('Payroll token allocations', ([owner, employee, anotherEmployee, anyone let notAllowedToken beforeEach('deploy new token', async () => { - notAllowedToken = await deployErc20TokenAndDeposit(owner, finance, vault, 'Not-allowed token', 14) + notAllowedToken = await deployErc20TokenAndDeposit(owner, finance, 'Not-allowed token', 14) }) it('reverts', async () => { diff --git a/future-apps/payroll/test/helpers/setup.js b/future-apps/payroll/test/helpers/deploy.js similarity index 92% rename from future-apps/payroll/test/helpers/setup.js rename to future-apps/payroll/test/helpers/deploy.js index ff6b3694ec..68b8c5d7a9 100644 --- a/future-apps/payroll/test/helpers/setup.js +++ b/future-apps/payroll/test/helpers/deploy.js @@ -12,7 +12,7 @@ module.exports = (artifacts, web3) => { const DAOFactory = getContract('DAOFactory') const EVMScriptRegistryFactory = getContract('EVMScriptRegistryFactory') - async function deployErc20TokenAndDeposit(sender, finance, vault, name = 'ERC20Token', decimals = 18) { + async function deployErc20TokenAndDeposit(sender, finance, name = 'ERC20Token', decimals = 18) { const token = await getContract('MiniMeToken').new('0x0', '0x0', 0, name, decimals, 'E20', true) // dummy parameters for minime const amount = bigExp(1e18, decimals) await token.generateTokens(sender, amount) @@ -62,13 +62,12 @@ module.exports = (artifacts, web3) => { const SECONDS_IN_A_YEAR = 31557600 // 365.25 days await finance.initialize(vault.address, SECONDS_IN_A_YEAR) // more than one day - const priceFeed = await PriceFeed.new() const payrollBase = await Payroll.new() - return { dao, finance, vault, priceFeed, payrollBase } + return { dao, finance, vault, payrollBase } } - async function createPayrollInstance(dao, payrollBase, owner) { + async function createPayrollAndPriceFeed(dao, payrollBase, owner, currentTimestamp) { const receipt = await dao.newAppInstance('0x4321', payrollBase.address, '0x', false, { from: owner }) const payroll = Payroll.at(getEventArgument(receipt, 'NewAppProxy', 'proxy')) @@ -92,18 +91,16 @@ module.exports = (artifacts, web3) => { await acl.createPermission(owner, payroll.address, SET_EMPLOYEE_SALARY_ROLE, owner, { from: owner }) await acl.createPermission(owner, payroll.address, ALLOWED_TOKENS_MANAGER_ROLE, owner, { from: owner }) - return payroll - } + const priceFeed = await PriceFeed.new() + await priceFeed.mockSetTimestamp(currentTimestamp) + await payroll.mockSetTimestamp(currentTimestamp) - async function mockTimestamps(payroll, priceFeed, now) { - await priceFeed.mockSetTimestamp(now) - await payroll.mockSetTimestamp(now) + return { payroll, priceFeed } } return { deployContracts, deployErc20TokenAndDeposit, - createPayrollInstance, - mockTimestamps + createPayrollAndPriceFeed } } diff --git a/future-apps/payroll/test/helpers/set_token_rates.js b/future-apps/payroll/test/helpers/set_token_rates.js new file mode 100644 index 0000000000..7be2afdcef --- /dev/null +++ b/future-apps/payroll/test/helpers/set_token_rates.js @@ -0,0 +1,23 @@ +const SIG = '00'.repeat(65) // sig full of 0s + +module.exports = web3 => { + + function formatRate(n) { + const { bn, bigExp } = require('./numbers')(web3) + const ONE = bigExp(1, 18) + return bn(n.toFixed(18)).times(ONE) + } + + return async function setTokenRates(feed, denominationToken, tokens, rates, when = undefined) { + if (!when) when = await feed.getTimestampPublic() + + const bases = tokens.map(token => typeof(token) === 'object' ? token.address : token) + const quotes = tokens.map(() => typeof(denominationToken) === 'object' ? denominationToken.address : denominationToken) + const xrts = rates.map(rate => formatRate(rate)) + const whens = tokens.map(() => when) + const sigs = `0x${SIG.repeat(tokens.length)}` + + return feed.updateMany(bases, quotes, xrts, whens, sigs) + } + +} diff --git a/shared/test-helpers/assertThrow.js b/shared/test-helpers/assertThrow.js index 8e330fa594..98c1999299 100644 --- a/shared/test-helpers/assertThrow.js +++ b/shared/test-helpers/assertThrow.js @@ -8,7 +8,7 @@ async function assertThrows(blockOrPromise, expectedErrorCode, expectedReason) { return error } // assert.fail() for some reason does not have its error string printed 🤷 - assert(0, `Expected "${expectedErrorCode}"${expectedReason ? ` (with reason: "${expectedReason}")` : ''} but it did not fail`) + assert(false, `Expected "${expectedErrorCode}"${expectedReason ? ` (with reason: "${expectedReason}")` : ''} but it did not fail`) } module.exports = { @@ -28,8 +28,8 @@ module.exports = { if (process.env.SOLIDITY_COVERAGE !== 'true' && expectedReason) { assert.equal( - expectedReason, error.reason, + expectedReason, `Expected revert reason "${expectedReason}" but failed with "${error.reason || 'no reason'}" instead.` ) }