From f6c26d9fd12b13cb220058cc7a0f653a0a1b872f Mon Sep 17 00:00:00 2001 From: Facundo Spagnuolo Date: Thu, 25 Apr 2019 11:26:29 -0300 Subject: [PATCH 1/6] payroll: add rates handling tests --- future-apps/payroll/contracts/Payroll.sol | 2 +- .../test/contracts/Payroll_rates.test.js | 142 ++++++++++++++++++ .../payroll/test/helpers/set_token_rates.js | 4 +- 3 files changed, 145 insertions(+), 3 deletions(-) create mode 100644 future-apps/payroll/test/contracts/Payroll_rates.test.js diff --git a/future-apps/payroll/contracts/Payroll.sol b/future-apps/payroll/contracts/Payroll.sol index bd20d595cd..98a6f9f801 100644 --- a/future-apps/payroll/contracts/Payroll.sol +++ b/future-apps/payroll/contracts/Payroll.sol @@ -665,7 +665,7 @@ contract Payroll is EtherTokenConstant, IForwarder, IsContract, AragonApp { return ONE; } - (uint128 xrt, uint64 when) = feed.get(_token, denominationToken); + (uint128 xrt, uint64 when) = feed.get(denominationToken, _token); // Check the price feed is recent enough if (getTimestamp64().sub(when) >= rateExpiryTime) { diff --git a/future-apps/payroll/test/contracts/Payroll_rates.test.js b/future-apps/payroll/test/contracts/Payroll_rates.test.js new file mode 100644 index 0000000000..3403fd18b2 --- /dev/null +++ b/future-apps/payroll/test/contracts/Payroll_rates.test.js @@ -0,0 +1,142 @@ +const PAYMENT_TYPES = require('../helpers/payment_types') +const { bigExp, bn } = require('../helpers/numbers')(web3) +const { getEventArgument } = require('../helpers/events') +const { deployErc20TokenAndDeposit, deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy.js')(artifacts, web3) + +contract('Payroll rates handling,', ([owner, employee]) => { + let USD, ETH, DAI, ANT + let dao, payroll, payrollBase, finance, vault, priceFeed, employeeId + + const NOW = 1553703809 // random fixed timestamp in seconds + const TWO_MINUTES = 60 * 2 + const ONE_MONTH = 60 * 60 * 24 * 31 + const RATE_EXPIRATION_TIME = ONE_MONTH + + const TOKEN_DECIMALS = 18 + const ONE = bigExp(1, 18) + const SIG = '00'.repeat(65) // sig full of 0s + + const formatRate = (n) => bn(n.toFixed(18)).times(ONE) + + const increaseTime = async seconds => { + await payroll.mockIncreaseTime(seconds) + await priceFeed.mockIncreaseTime(seconds) + } + + before('deploy contracts and tokens', async () => { + ({ dao, finance, vault, payrollBase } = await deployContracts(owner)) + + USD = '0x0000000000000000000000000000000000000001' + ETH = '0x0000000000000000000000000000000000000000' + await finance.deposit(ETH, bn(50, 18), 'Initial ETH deposit', { from: owner, value: bn(50, 18) }) + + DAI = await deployErc20TokenAndDeposit(owner, finance, 'DAI', TOKEN_DECIMALS) + ANT = await deployErc20TokenAndDeposit(owner, finance, 'ANT', TOKEN_DECIMALS) + }) + + beforeEach('initialize payroll app with USD as denomination token', async () => { + ({ payroll, priceFeed } = await createPayrollAndPriceFeed(dao, payrollBase, owner, NOW)) + await payroll.initialize(finance.address, USD, priceFeed.address, RATE_EXPIRATION_TIME, { from: owner }) + }) + + describe('rates', () => { + beforeEach('set rates and allow tokens', async () => { + const DAI_rate = bn(1) + const ANT_rate = bn(0.5) + const ETH_rate = bn(20) + + await priceFeed.update(ETH, USD, formatRate(ETH_rate), NOW, SIG) + await priceFeed.update(DAI.address, USD, formatRate(DAI_rate), NOW, SIG) + await priceFeed.update(ANT.address, USD, formatRate(ANT_rate), NOW, SIG) + + await payroll.addAllowedToken(ETH, { from: owner }) + await payroll.addAllowedToken(DAI.address, { from: owner }) + await payroll.addAllowedToken(ANT.address, { from: owner }) + }) + + beforeEach('add employee with salary 1 USD per second', async () => { + const salary = 1 + const receipt = await payroll.addEmployee(employee, salary, 'Boss', await payroll.getTimestampPublic()) + employeeId = getEventArgument(receipt, 'AddEmployee', 'employeeId') + }) + + beforeEach('increase time two minutes', async () => { + await increaseTime(TWO_MINUTES) + }) + + context('when the employee requests only ETH', () => { + beforeEach('set token allocation', async () => { + await payroll.determineAllocation([ETH], [100], { from: employee }) + }) + + it('receives the expected amount of ETH', async () => { + const previousETH = await web3.eth.getBalance(employee) + const previousDAI = await DAI.balanceOf(employee) + const previousANT = await ANT.balanceOf(employee) + + const { tx, receipt } = await payroll.payday(PAYMENT_TYPES.PAYROLL, 0, { from: employee }) + const { gasPrice } = await web3.eth.getTransaction(tx) + const txCost = gasPrice.mul(receipt.gasUsed) + + const currentETH = await web3.eth.getBalance(employee) + assert.equal(currentETH.minus(previousETH).plus(txCost).toString(), 6, 'expected current ETH amount does not match') + + const currentDAI = await DAI.balanceOf(employee) + assert.equal(currentDAI.toString(), previousDAI.toString(), 'expected current DAI amount does not match') + + const currentANT = await ANT.balanceOf(employee) + assert.equal(currentANT.toString(), previousANT.toString(), 'expected current ANT amount does not match') + }) + }) + + context('when the employee requests only ANT', () => { + beforeEach('set token allocation', async () => { + await payroll.determineAllocation([ANT.address], [100], { from: employee }) + }) + + it('receives the expected amount of ANT', async () => { + const previousETH = await web3.eth.getBalance(employee) + const previousDAI = await DAI.balanceOf(employee) + const previousANT = await ANT.balanceOf(employee) + + const { tx, receipt } = await payroll.payday(PAYMENT_TYPES.PAYROLL, 0, { from: employee }) + const { gasPrice } = await web3.eth.getTransaction(tx) + const txCost = gasPrice.mul(receipt.gasUsed) + + const currentETH = await web3.eth.getBalance(employee) + assert.equal(currentETH.plus(txCost).toString(), previousETH.toString(), 'expected current ETH amount does not match') + + const currentDAI = await DAI.balanceOf(employee) + assert.equal(currentDAI.toString(), previousDAI.toString(), 'expected current DAI amount does not match') + + const currentANT = await ANT.balanceOf(employee) + assert.equal(currentANT.minus(previousANT).toString(), 240, 'expected current ANT amount does not match') + }) + }) + + context('when the employee requests multiple tokens', () => { + beforeEach('set token allocations', async () => { + await payroll.determineAllocation([ETH, DAI.address, ANT.address], [50, 25, 25], { from: employee }) + }) + + it('receives the expected amount of tokens', async () => { + const previousETH = await web3.eth.getBalance(employee) + const previousDAI = await DAI.balanceOf(employee) + const previousANT = await ANT.balanceOf(employee) + + const { tx, receipt } = await payroll.payday(PAYMENT_TYPES.PAYROLL, 0, { from: employee }) + const { gasPrice } = await web3.eth.getTransaction(tx) + const txCost = gasPrice.mul(receipt.gasUsed) + + const currentETH = await web3.eth.getBalance(employee) + assert.equal(currentETH.minus(previousETH).plus(txCost).toString(), 3, 'expected current ETH amount does not match') + + const currentDAI = await DAI.balanceOf(employee) + assert.equal(currentDAI.minus(previousDAI).toString(), 30, 'expected current DAI amount does not match') + + const currentANT = await ANT.balanceOf(employee) + assert.equal(currentANT.minus(previousANT).toString(), 60, 'expected current ANT amount does not match') + }) + }) + }) +}) diff --git a/future-apps/payroll/test/helpers/set_token_rates.js b/future-apps/payroll/test/helpers/set_token_rates.js index 7be2afdcef..61c7f3b41c 100644 --- a/future-apps/payroll/test/helpers/set_token_rates.js +++ b/future-apps/payroll/test/helpers/set_token_rates.js @@ -11,8 +11,8 @@ module.exports = web3 => { 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 quotes = tokens.map(token => typeof(token) === 'object' ? token.address : token) + const bases = 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)}` From 4a0f4b357834a55d4c59482b264fd68f901d7cb9 Mon Sep 17 00:00:00 2001 From: Facundo Spagnuolo Date: Thu, 25 Apr 2019 19:44:09 -0300 Subject: [PATCH 2/6] Payroll: Update tests to using a real denomination token --- future-apps/payroll/contracts/Payroll.sol | 2 +- .../contracts/Payroll_add_employee.test.js | 27 +-- .../contracts/Payroll_allowed_tokens.test.js | 56 +++-- .../test/contracts/Payroll_bonuses.test.js | 91 ++++---- .../test/contracts/Payroll_forwarding.test.js | 29 ++- .../test/contracts/Payroll_gas_costs.test.js | 65 +++--- .../contracts/Payroll_get_employee.test.js | 29 ++- .../test/contracts/Payroll_initialize.test.js | 28 +-- .../contracts/Payroll_modify_employee.test.js | 28 +-- .../test/contracts/Payroll_payday.test.js | 195 +++++++++--------- .../test/contracts/Payroll_rates.test.js | 37 +--- .../test/contracts/Payroll_reentrancy.test.js | 8 +- .../contracts/Payroll_reimbursements.test.js | 87 ++++---- .../test/contracts/Payroll_settings.test.js | 21 +- .../Payroll_terminate_employee.test.js | 46 ++--- .../Payroll_token_allocations.test.js | 47 ++--- future-apps/payroll/test/helpers/deploy.js | 3 +- future-apps/payroll/test/helpers/numbers.js | 17 +- .../payroll/test/helpers/set_token_rates.js | 23 --- future-apps/payroll/test/helpers/time.js | 18 ++ future-apps/payroll/test/helpers/tokens.js | 62 ++++++ 21 files changed, 437 insertions(+), 482 deletions(-) delete mode 100644 future-apps/payroll/test/helpers/set_token_rates.js create mode 100644 future-apps/payroll/test/helpers/time.js create mode 100644 future-apps/payroll/test/helpers/tokens.js diff --git a/future-apps/payroll/contracts/Payroll.sol b/future-apps/payroll/contracts/Payroll.sol index 98a6f9f801..bd20d595cd 100644 --- a/future-apps/payroll/contracts/Payroll.sol +++ b/future-apps/payroll/contracts/Payroll.sol @@ -665,7 +665,7 @@ contract Payroll is EtherTokenConstant, IForwarder, IsContract, AragonApp { return ONE; } - (uint128 xrt, uint64 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) { 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 f0f4460723..9101ce2450 100644 --- a/future-apps/payroll/test/contracts/Payroll_add_employee.test.js +++ b/future-apps/payroll/test/contracts/Payroll_add_employee.test.js @@ -1,23 +1,18 @@ const { assertRevert } = require('@aragon/test-helpers/assertThrow') const { getEvents, getEventArgument } = require('../helpers/events') -const { maxUint64, annualSalaryPerSecond } = require('../helpers/numbers')(web3) -const { deployErc20TokenAndDeposit, deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy.js')(artifacts, web3) +const { MAX_UINT64, annualSalaryPerSecond } = require('../helpers/numbers')(web3) +const { USD, deployDAI } = require('../helpers/tokens.js')(artifacts, web3) +const { NOW, TWO_MONTHS, RATE_EXPIRATION_TIME } = require('../helpers/time') +const { deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy')(artifacts, web3) const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' contract('Payroll employees addition', ([owner, employee, anotherEmployee, anyone]) => { - let dao, payroll, payrollBase, finance, vault, priceFeed, denominationToken - - 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 TOKEN_DECIMALS = 18 + let dao, payroll, payrollBase, finance, vault, priceFeed, DAI before('deploy base apps and tokens', async () => { ({ dao, finance, vault, payrollBase } = await deployContracts(owner)) - denominationToken = await deployErc20TokenAndDeposit(owner, finance, 'Denomination Token', TOKEN_DECIMALS) + DAI = await deployDAI(owner, finance) }) beforeEach('create payroll and price feed instance', async () => { @@ -26,11 +21,11 @@ contract('Payroll employees addition', ([owner, employee, anotherEmployee, anyon describe('addEmployee', () => { const role = 'Boss' - const salary = annualSalaryPerSecond(100000, TOKEN_DECIMALS) + const salary = annualSalaryPerSecond(100000) context('when it has already been initialized', function () { - beforeEach('initialize payroll app', async () => { - await payroll.initialize(finance.address, denominationToken.address, priceFeed.address, RATE_EXPIRATION_TIME, { from: owner }) + beforeEach('initialize payroll app using USD as denomination token', async () => { + await payroll.initialize(finance.address, USD, priceFeed.address, RATE_EXPIRATION_TIME, { from: owner }) }) context('when the sender has permissions to add employees', () => { @@ -69,7 +64,7 @@ contract('Payroll employees addition', ([owner, employee, anotherEmployee, anyon it('can add another employee', async () => { const anotherRole = 'Manager' - const anotherSalary = annualSalaryPerSecond(120000, TOKEN_DECIMALS) + const anotherSalary = annualSalaryPerSecond(120000) const receipt = await payroll.addEmployee(anotherEmployee, anotherSalary, anotherRole, startDate) const anotherEmployeeId = getEventArgument(receipt, 'AddEmployee', 'employeeId') @@ -91,7 +86,7 @@ contract('Payroll employees addition', ([owner, employee, anotherEmployee, anyon assert.equal(accruedSalary, 0, 'employee accrued salary does not match') assert.equal(employeeSalary.toString(), anotherSalary.toString(), 'employee salary does not match') assert.equal(lastPayroll.toString(), startDate.toString(), 'employee last payroll does not match') - assert.equal(endDate.toString(), maxUint64(), 'employee end date does not match') + assert.equal(endDate.toString(), MAX_UINT64, 'employee end date does not match') }) }) 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 17c5f412cb..4587770d58 100644 --- a/future-apps/payroll/test/contracts/Payroll_allowed_tokens.test.js +++ b/future-apps/payroll/test/contracts/Payroll_allowed_tokens.test.js @@ -1,25 +1,19 @@ 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, createPayrollAndPriceFeed } = require('../helpers/deploy.js')(artifacts, web3) +const { NOW, ONE_MONTH, RATE_EXPIRATION_TIME } = require('../helpers/time') +const { deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy')(artifacts, web3) +const { USD, deployDAI, deployTokenAndDeposit, setTokenRates } = require('../helpers/tokens.js')(artifacts, web3) const MAX_GAS_USED = 6.5e6 const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' 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 - const TWO_MONTHS = ONE_MONTH * 2 - const RATE_EXPIRATION_TIME = TWO_MONTHS - - const TOKEN_DECIMALS = 18 + let dao, payroll, payrollBase, finance, vault, priceFeed, DAI before('deploy base apps and tokens', async () => { ({ dao, finance, vault, payrollBase } = await deployContracts(owner)) - denominationToken = await deployErc20TokenAndDeposit(owner, finance, 'Denomination Token', TOKEN_DECIMALS) + DAI = await deployDAI(owner, finance) }) beforeEach('create payroll and price feed instance', async () => { @@ -28,8 +22,8 @@ contract('Payroll allowed tokens,', ([owner, employee, anyone]) => { describe('addAllowedToken', () => { context('when it has already been initialized', function () { - beforeEach('initialize payroll app', async () => { - await payroll.initialize(finance.address, denominationToken.address, priceFeed.address, RATE_EXPIRATION_TIME, { from: owner }) + beforeEach('initialize payroll app using USD as denomination token', async () => { + await payroll.initialize(finance.address, USD, priceFeed.address, RATE_EXPIRATION_TIME, { from: owner }) }) context('when the sender has permissions', () => { @@ -37,13 +31,13 @@ contract('Payroll allowed tokens,', ([owner, employee, anyone]) => { context('when it does not reach the maximum amount allowed', () => { it('can allow a token', async () => { - const receipt = await payroll.addAllowedToken(denominationToken.address, { from }) + const receipt = await payroll.addAllowedToken(DAI.address, { from }) const event = getEvent(receipt, 'AddAllowedToken') - assert.equal(event.token, denominationToken.address, 'denomination token address should match') + assert.equal(event.token, DAI.address, 'denomination token address should match') assert.equal(await payroll.getAllowedTokensArrayLength(), 1, 'allowed tokens length does not match') - assert(await payroll.isTokenAllowed(denominationToken.address), 'denomination token should be allowed') + assert(await payroll.isTokenAllowed(DAI.address), 'denomination token should be allowed') }) it('can allow a the zero address', async () => { @@ -57,15 +51,15 @@ contract('Payroll allowed tokens,', ([owner, employee, anyone]) => { }) it('can allow multiple tokens', async () => { - const erc20Token1 = await deployErc20TokenAndDeposit(owner, finance, 'Token 1', 18) - const erc20Token2 = await deployErc20TokenAndDeposit(owner, finance, 'Token 2', 16) + const erc20Token1 = await deployTokenAndDeposit(owner, finance, 'Token 1', 18) + const erc20Token2 = await deployTokenAndDeposit(owner, finance, 'Token 2', 16) - await payroll.addAllowedToken(denominationToken.address, { from }) + await payroll.addAllowedToken(DAI.address, { from }) await payroll.addAllowedToken(erc20Token1.address, { from }) await payroll.addAllowedToken(erc20Token2.address, { from }) assert.equal(await payroll.getAllowedTokensArrayLength(), 3, 'allowed tokens length does not match') - assert(await payroll.isTokenAllowed(denominationToken.address), 'denomination token should be allowed') + assert(await payroll.isTokenAllowed(DAI.address), 'denomination token should be allowed') assert(await payroll.isTokenAllowed(erc20Token1.address), 'ERC20 token 1 should be allowed') assert(await payroll.isTokenAllowed(erc20Token2.address), 'ERC20 token 2 should be allowed') }) @@ -77,7 +71,7 @@ contract('Payroll allowed tokens,', ([owner, employee, anyone]) => { 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, `Token ${i}`, 18); + const token = await deployTokenAndDeposit(owner, finance, `Token ${i}`, 18); tokenAddresses.push(token.address) } }) @@ -87,13 +81,13 @@ contract('Payroll allowed tokens,', ([owner, employee, anyone]) => { 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 setTokenRates(priceFeed, USD, 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, 'Extra token', 18) + const erc20Token = await deployTokenAndDeposit(owner, finance, 'Extra token', 18) await assertRevert(payroll.addAllowedToken(erc20Token.address), 'PAYROLL_MAX_ALLOWED_TOKENS') }) @@ -114,38 +108,38 @@ contract('Payroll allowed tokens,', ([owner, employee, anyone]) => { const from = anyone it('reverts', async () => { - await assertRevert(payroll.addAllowedToken(denominationToken.address, { from }), 'APP_AUTH_FAILED') + await assertRevert(payroll.addAllowedToken(DAI.address, { from }), 'APP_AUTH_FAILED') }) }) }) context('when it has not been initialized yet', function () { it('reverts', async () => { - await assertRevert(payroll.addAllowedToken(denominationToken.address, { from: owner }), 'APP_AUTH_FAILED') + await assertRevert(payroll.addAllowedToken(DAI.address, { from: owner }), 'APP_AUTH_FAILED') }) }) }) describe('isTokenAllowed', () => { context('when it has already been initialized', function () { - beforeEach('initialize payroll app', async () => { - await payroll.initialize(finance.address, denominationToken.address, priceFeed.address, RATE_EXPIRATION_TIME, { from: owner }) + beforeEach('initialize payroll app using USD as denomination token', async () => { + await payroll.initialize(finance.address, USD, priceFeed.address, RATE_EXPIRATION_TIME, { from: owner }) }) context('when the given token is not the zero address', () => { context('when the requested token was allowed', () => { beforeEach('allow denomination token', async () => { - await payroll.addAllowedToken(denominationToken.address, { from: owner }) + await payroll.addAllowedToken(DAI.address, { from: owner }) }) it('returns true', async () => { - assert(await payroll.isTokenAllowed(denominationToken.address), 'token should be allowed') + assert(await payroll.isTokenAllowed(DAI.address), 'token should be allowed') }) }) context('when the requested token was not allowed yet', () => { it('returns false', async () => { - assert.isFalse(await payroll.isTokenAllowed(denominationToken.address), 'token should not be allowed') + assert.isFalse(await payroll.isTokenAllowed(DAI.address), 'token should not be allowed') }) }) }) @@ -159,7 +153,7 @@ contract('Payroll allowed tokens,', ([owner, employee, anyone]) => { context('when it has not been initialized yet', function () { it('reverts', async () => { - await assertRevert(payroll.isTokenAllowed(denominationToken.address), 'INIT_NOT_INITIALIZED') + await assertRevert(payroll.isTokenAllowed(DAI.address), 'INIT_NOT_INITIALIZED') }) }) }) diff --git a/future-apps/payroll/test/contracts/Payroll_bonuses.test.js b/future-apps/payroll/test/contracts/Payroll_bonuses.test.js index c2a42728a8..ba1710f322 100644 --- a/future-apps/payroll/test/contracts/Payroll_bonuses.test.js +++ b/future-apps/payroll/test/contracts/Payroll_bonuses.test.js @@ -1,19 +1,13 @@ const PAYMENT_TYPES = require('../helpers/payment_types') -const setTokenRates = require('../helpers/set_token_rates')(web3) +const { MAX_UINT256 } = require('../helpers/numbers')(web3) const { assertRevert } = require('@aragon/test-helpers/assertThrow') -const { bn, maxUint256 } = require('../helpers/numbers')(web3) const { getEvents, getEventArgument } = require('../helpers/events') -const { deployErc20TokenAndDeposit, deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy.js')(artifacts, web3) +const { NOW, ONE_MONTH, RATE_EXPIRATION_TIME } = require('../helpers/time') +const { deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy')(artifacts, web3) +const { USD, deployDAI, deployANT, DAI_RATE, ANT_RATE, setTokenRates } = require('../helpers/tokens.js')(artifacts, web3) contract('Payroll bonuses', ([owner, employee, anyone]) => { - 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 TOKEN_DECIMALS = 18 + let dao, payroll, payrollBase, finance, vault, priceFeed, DAI, ANT const increaseTime = async seconds => { await payroll.mockIncreaseTime(seconds) @@ -22,8 +16,8 @@ contract('Payroll bonuses', ([owner, employee, anyone]) => { 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) + ANT = await deployANT(owner, finance) + DAI = await deployDAI(owner, finance) }) beforeEach('create payroll and price feed instance', async () => { @@ -32,8 +26,8 @@ contract('Payroll bonuses', ([owner, employee, anyone]) => { describe('addBonus', () => { context('when it has already been initialized', function () { - beforeEach('initialize payroll app', async () => { - await payroll.initialize(finance.address, denominationToken.address, priceFeed.address, RATE_EXPIRATION_TIME, { from: owner }) + beforeEach('initialize payroll app using USD as denomination token', async () => { + await payroll.initialize(finance.address, USD, priceFeed.address, RATE_EXPIRATION_TIME, { from: owner }) }) context('when the sender has permissions', () => { @@ -91,7 +85,7 @@ contract('Payroll bonuses', ([owner, employee, anyone]) => { }) context('when the given bonus way greater than zero', () => { - const amount = maxUint256() + const amount = MAX_UINT256 it('reverts', async () => { await payroll.addBonus(employeeId, 1, { from }) @@ -145,13 +139,12 @@ contract('Payroll bonuses', ([owner, employee, anyone]) => { describe('bonus payday', () => { context('when it has already been initialized', function () { - beforeEach('initialize payroll app', async () => { - await payroll.initialize(finance.address, denominationToken.address, priceFeed.address, RATE_EXPIRATION_TIME, { from: owner }) + beforeEach('initialize payroll app using USD as denomination token', async () => { + await payroll.initialize(finance.address, USD, priceFeed.address, RATE_EXPIRATION_TIME, { from: owner }) }) beforeEach('set token rates', async () => { - anotherTokenRate = bn(5) - await setTokenRates(priceFeed, denominationToken, [anotherToken], [anotherTokenRate]) + await setTokenRates(priceFeed, USD, [DAI, ANT], [DAI_RATE, ANT_RATE]) }) context('when the sender is an employee', () => { @@ -166,13 +159,13 @@ contract('Payroll bonuses', ([owner, employee, anyone]) => { }) context('when the employee has already set some token allocations', () => { - const denominationTokenAllocation = 80 - const anotherTokenAllocation = 20 + const allocationDAI = 80 + const allocationANT = 20 beforeEach('set tokens allocation', async () => { - await payroll.addAllowedToken(anotherToken.address, { from: owner }) - await payroll.addAllowedToken(denominationToken.address, { from: owner }) - await payroll.determineAllocation([denominationToken.address, anotherToken.address], [denominationTokenAllocation, anotherTokenAllocation], { from }) + await payroll.addAllowedToken(ANT.address, { from: owner }) + await payroll.addAllowedToken(DAI.address, { from: owner }) + await payroll.determineAllocation([DAI.address, ANT.address], [allocationDAI, allocationANT], { from }) }) context('when the employee has a pending bonus', () => { @@ -184,22 +177,22 @@ contract('Payroll bonuses', ([owner, employee, anyone]) => { }) const assertTransferredAmounts = (requestedAmount, expectedRequestedAmount = requestedAmount) => { - const requestedDenominationTokenAmount = parseInt(expectedRequestedAmount * denominationTokenAllocation / 100) - const requestedAnotherTokenAmount = expectedRequestedAmount * anotherTokenAllocation / 100 + const requestedDAI = DAI_RATE.mul(expectedRequestedAmount * allocationDAI / 100).trunc() + const requestedANT = ANT_RATE.mul(expectedRequestedAmount * allocationANT / 100).trunc() it('transfers all the pending bonus', async () => { - const previousDenominationTokenBalance = await denominationToken.balanceOf(employee) - const previousAnotherTokenBalance = await anotherToken.balanceOf(employee) + const previousDAI = await DAI.balanceOf(employee) + const previousANT = await ANT.balanceOf(employee) await payroll.payday(PAYMENT_TYPES.BONUS, requestedAmount, { from }) - const currentDenominationTokenBalance = await denominationToken.balanceOf(employee) - const expectedDenominationTokenBalance = previousDenominationTokenBalance.plus(requestedDenominationTokenAmount); - assert.equal(currentDenominationTokenBalance.toString(), expectedDenominationTokenBalance.toString(), 'current denomination token balance does not match') + const currentDAI = await DAI.balanceOf(employee) + const expectedDAI = previousDAI.plus(requestedDAI) + assert.equal(currentDAI.toString(), expectedDAI.toString(), 'current DAI balance does not match') - const currentAnotherTokenBalance = await anotherToken.balanceOf(employee) - const expectedAnotherTokenBalance = anotherTokenRate.mul(requestedAnotherTokenAmount).plus(previousAnotherTokenBalance).trunc() - assert.equal(currentAnotherTokenBalance.toString(), expectedAnotherTokenBalance.toString(), 'current token balance does not match') + const currentANT = await ANT.balanceOf(employee) + const expectedANT = previousANT.plus(requestedANT) + assert.equal(currentANT.toString(), expectedANT.toString(), 'current ANT balance does not match') }) it('emits one event per allocated token', async () => { @@ -208,17 +201,17 @@ contract('Payroll bonuses', ([owner, employee, anyone]) => { const events = receipt.logs.filter(l => l.event === 'SendPayment') assert.equal(events.length, 2, 'should have emitted two events') - const denominationTokenEvent = events.find(e => e.args.token === denominationToken.address).args - assert.equal(denominationTokenEvent.employee, employee, 'employee address does not match') - assert.equal(denominationTokenEvent.token, denominationToken.address, 'denomination token address does not match') - assert.equal(denominationTokenEvent.amount.toString(), requestedDenominationTokenAmount, 'payment amount does not match') - assert.equal(denominationTokenEvent.paymentReference, 'Bonus', 'payment reference does not match') - - 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') - assert.equal(anotherTokenEvent.amount.div(anotherTokenRate).trunc().toString(), parseInt(requestedAnotherTokenAmount), 'payment amount does not match') - assert.equal(anotherTokenEvent.paymentReference, 'Bonus', 'payment reference does not match') + const eventDAI = events.find(e => e.args.token === DAI.address).args + assert.equal(eventDAI.employee, employee, 'employee address does not match') + assert.equal(eventDAI.token, DAI.address, 'DAI address does not match') + assert.equal(eventDAI.amount.toString(), requestedDAI, 'payment amount does not match') + assert.equal(eventDAI.paymentReference, 'Bonus', 'payment reference does not match') + + const eventANT = events.find(e => e.args.token === ANT.address).args + assert.equal(eventANT.employee, employee, 'employee address does not match') + assert.equal(eventANT.token, ANT.address, 'token address does not match') + assert.equal(eventANT.amount.toString(), requestedANT, 'payment amount does not match') + assert.equal(eventANT.paymentReference, 'Bonus', 'payment reference does not match') }) } @@ -244,7 +237,7 @@ contract('Payroll bonuses', ([owner, employee, anyone]) => { context('when exchange rates are expired', () => { beforeEach('expire exchange rates', async () => { const expiredTimestamp = (await payroll.getTimestampPublic()).sub(RATE_EXPIRATION_TIME + 1) - await setTokenRates(priceFeed, denominationToken, [anotherToken], [anotherTokenRate], expiredTimestamp) + await setTokenRates(priceFeed, USD, [DAI, ANT], [DAI_RATE, ANT_RATE], expiredTimestamp) }) it('reverts', async () => { @@ -297,7 +290,7 @@ contract('Payroll bonuses', ([owner, employee, anyone]) => { context('when exchange rates are expired', () => { beforeEach('expire exchange rates', async () => { const expiredTimestamp = (await payroll.getTimestampPublic()).sub(RATE_EXPIRATION_TIME + 1) - await setTokenRates(priceFeed, denominationToken, [anotherToken], [anotherTokenRate], expiredTimestamp) + await setTokenRates(priceFeed, USD, [DAI, ANT], [DAI_RATE, ANT_RATE], expiredTimestamp) }) it('reverts', async () => { @@ -388,7 +381,7 @@ contract('Payroll bonuses', ([owner, employee, anyone]) => { context('when exchange rates are expired', () => { beforeEach('expire exchange rates', async () => { const expiredTimestamp = (await payroll.getTimestampPublic()).sub(RATE_EXPIRATION_TIME + 1) - await setTokenRates(priceFeed, denominationToken, [anotherToken], [anotherTokenRate], expiredTimestamp) + await setTokenRates(priceFeed, USD, [DAI, ANT], [DAI_RATE, ANT_RATE], 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 8f185ad8f9..e83ac0e8f0 100644 --- a/future-apps/payroll/test/contracts/Payroll_forwarding.test.js +++ b/future-apps/payroll/test/contracts/Payroll_forwarding.test.js @@ -1,23 +1,18 @@ const { assertRevert } = require('@aragon/test-helpers/assertThrow') -const { encodeCallScript } = require('@aragon/test-helpers/evmScript') const { getEventArgument } = require('../helpers/events') -const { deployErc20TokenAndDeposit, deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy.js')(artifacts, web3) +const { encodeCallScript } = require('@aragon/test-helpers/evmScript') +const { USD, deployDAI } = require('../helpers/tokens.js')(artifacts, web3) +const { NOW, ONE_MONTH, RATE_EXPIRATION_TIME } = require('../helpers/time') +const { deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy')(artifacts, web3) const ExecutionTarget = artifacts.require('ExecutionTarget') 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 - const TWO_MONTHS = ONE_MONTH * 2 - const RATE_EXPIRATION_TIME = TWO_MONTHS - - const TOKEN_DECIMALS = 18 + let dao, payroll, payrollBase, finance, vault, priceFeed, DAI before('deploy base apps and tokens', async () => { ({ dao, finance, vault, payrollBase } = await deployContracts(owner)) - denominationToken = await deployErc20TokenAndDeposit(owner, finance, 'Denomination Token', TOKEN_DECIMALS) + DAI = await deployDAI(owner, finance) }) beforeEach('create payroll and price feed instance', async () => { @@ -26,8 +21,8 @@ contract('Payroll forwarding,', ([owner, employee, anyone]) => { describe('isForwarder', () => { context('when it has already been initialized', function () { - beforeEach('initialize payroll app', async () => { - await payroll.initialize(finance.address, denominationToken.address, priceFeed.address, RATE_EXPIRATION_TIME, { from: owner }) + beforeEach('initialize payroll app using USD as denomination token', async () => { + await payroll.initialize(finance.address, USD, priceFeed.address, RATE_EXPIRATION_TIME, { from: owner }) }) it('returns true', async () => { @@ -44,8 +39,8 @@ contract('Payroll forwarding,', ([owner, employee, anyone]) => { describe('canForward', () => { context('when it has already been initialized', function () { - beforeEach('initialize payroll app', async () => { - await payroll.initialize(finance.address, denominationToken.address, priceFeed.address, RATE_EXPIRATION_TIME, { from: owner }) + beforeEach('initialize payroll app using USD as denomination token', async () => { + await payroll.initialize(finance.address, USD, priceFeed.address, RATE_EXPIRATION_TIME, { from: owner }) }) context('when the sender is an employee', () => { @@ -101,8 +96,8 @@ contract('Payroll forwarding,', ([owner, employee, anyone]) => { }) context('when it has already been initialized', function () { - beforeEach('initialize payroll app', async () => { - await payroll.initialize(finance.address, denominationToken.address, priceFeed.address, RATE_EXPIRATION_TIME, { from: owner }) + beforeEach('initialize payroll app using USD as denomination token', async () => { + await payroll.initialize(finance.address, USD, priceFeed.address, RATE_EXPIRATION_TIME, { from: owner }) }) context('when the sender is an employee', () => { 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 f1c64be636..8b9035282c 100644 --- a/future-apps/payroll/test/contracts/Payroll_gas_costs.test.js +++ b/future-apps/payroll/test/contracts/Payroll_gas_costs.test.js @@ -1,21 +1,16 @@ const PAYMENT_TYPES = require('../helpers/payment_types') -const setTokenRates = require('../helpers/set_token_rates')(web3) const { annualSalaryPerSecond } = require('../helpers/numbers')(web3) -const { deployErc20TokenAndDeposit, deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy.js')(artifacts, web3) +const { NOW, ONE_MONTH, RATE_EXPIRATION_TIME } = require('../helpers/time') +const { deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy')(artifacts, web3) +const { USD, deployDAI, deployANT, DAI_RATE, ANT_RATE, setTokenRates } = require('../helpers/tokens.js')(artifacts, web3) contract('Payroll gas costs', ([owner, employee, anotherEmployee]) => { - let dao, payroll, payrollBase, finance, vault, priceFeed, denominationToken - - 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 TOKEN_DECIMALS = 18 + let dao, payroll, payrollBase, finance, vault, priceFeed, DAI, ANT before('deploy base apps and tokens', async () => { ({ dao, finance, vault, payrollBase } = await deployContracts(owner)) - denominationToken = await deployErc20TokenAndDeposit(owner, finance, 'Denomination Token', TOKEN_DECIMALS) + DAI = await deployDAI(owner, finance) + ANT = await deployANT(owner, finance) }) beforeEach('create payroll and price feed instance', async () => { @@ -23,52 +18,42 @@ contract('Payroll gas costs', ([owner, employee, anotherEmployee]) => { }) describe('gas costs', () => { - let erc20Token1, erc20Token2 - - 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 () => { - await payroll.initialize(finance.address, denominationToken.address, priceFeed.address, RATE_EXPIRATION_TIME, { from: owner }) + beforeEach('initialize payroll app using USD as denomination token', async () => { + await payroll.initialize(finance.address, USD, priceFeed.address, RATE_EXPIRATION_TIME, { from: owner }) const startDate = NOW - ONE_MONTH - const salary = annualSalaryPerSecond(100000, TOKEN_DECIMALS) + const salary = annualSalaryPerSecond(100000) await payroll.addEmployee(employee, salary, 'Boss', startDate) await payroll.addEmployee(anotherEmployee, salary, 'Manager', startDate) }) - context('when there are not allowed tokens yet', function () { - it('expends ~330k gas for a single allowed token', async () => { - await payroll.addAllowedToken(denominationToken.address) - await payroll.determineAllocation([denominationToken.address], [100], { from: employee }) + beforeEach('allow tokens and set rates', async () => { + await payroll.addAllowedToken(DAI.address, { from: owner }) + await payroll.addAllowedToken(ANT.address, { from: owner }) - const { receipt: { cumulativeGasUsed } } = await payroll.payday(PAYMENT_TYPES.PAYROLL, 0, { from: employee }) - - assert.isBelow(cumulativeGasUsed, 330000, 'payout gas cost for a single allowed token should be ~314k') - }) + await setTokenRates(priceFeed, USD, [DAI, ANT], [DAI_RATE, ANT_RATE]) }) - context('when there are some allowed tokens', function () { - const erc20Token1Rate = 2, erc20Token2Rate = 5 + context('when there is only one allowed token', function () { + it('expends ~335k gas for a single allowed token', async () => { + await payroll.determineAllocation([DAI.address], [100], { from: employee }) - 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]) + const { receipt: { cumulativeGasUsed } } = await payroll.payday(PAYMENT_TYPES.PAYROLL, 0, { from: employee }) + + assert.isBelow(cumulativeGasUsed, 335000, 'payout gas cost for a single allowed token should be ~335k') }) + }) - it('expends ~270k gas per allowed token', async () => { - await payroll.determineAllocation([denominationToken.address, erc20Token1.address], [60, 40], { from: employee }) + context('when there are two allowed token', function () { + it('expends ~295k gas per allowed token', async () => { + await payroll.determineAllocation([DAI.address], [100], { from: employee }) const { receipt: { cumulativeGasUsed: employeePayoutGasUsed } } = await payroll.payday(PAYMENT_TYPES.PAYROLL, 0, { from: employee }) - await payroll.determineAllocation([denominationToken.address, erc20Token1.address, erc20Token2.address], [65, 25, 10], { from: anotherEmployee }) + await payroll.determineAllocation([DAI.address, ANT.address], [60, 40], { from: anotherEmployee }) const { receipt: { cumulativeGasUsed: anotherEmployeePayoutGasUsed } } = await payroll.payday(PAYMENT_TYPES.PAYROLL, 0, { from: anotherEmployee }) const gasPerAllowedToken = anotherEmployeePayoutGasUsed - employeePayoutGasUsed - assert.isBelow(gasPerAllowedToken, 280000, 'payout gas cost increment per allowed token should be ~270k') + assert.isBelow(gasPerAllowedToken, 295000, 'payout gas cost increment per allowed token should be ~295k') }) }) }) 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 c344c0c287..03c7ffaeb1 100644 --- a/future-apps/payroll/test/contracts/Payroll_get_employee.test.js +++ b/future-apps/payroll/test/contracts/Payroll_get_employee.test.js @@ -1,23 +1,18 @@ -const { maxUint64 } = require('../helpers/numbers')(web3) +const { MAX_UINT64 } = require('../helpers/numbers')(web3) const { assertRevert } = require('@aragon/test-helpers/assertThrow') const { getEventArgument } = require('../helpers/events') -const { deployErc20TokenAndDeposit, deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy.js')(artifacts, web3) +const { USD, deployDAI } = require('../helpers/tokens.js')(artifacts, web3) +const { NOW, RATE_EXPIRATION_TIME } = require('../helpers/time') +const { deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy')(artifacts, web3) contract('Payroll employee getters', ([owner, employee]) => { - let dao, payroll, payrollBase, finance, vault, priceFeed, denominationToken - - 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 TOKEN_DECIMALS = 18 + let dao, payroll, payrollBase, finance, vault, priceFeed, DAI const currentTimestamp = async () => payroll.getTimestampPublic() before('deploy base apps and tokens', async () => { ({ dao, finance, vault, payrollBase } = await deployContracts(owner)) - denominationToken = await deployErc20TokenAndDeposit(owner, finance, 'Denomination Token', TOKEN_DECIMALS) + DAI = await deployDAI(owner, finance) }) beforeEach('create payroll and price feed instance', async () => { @@ -26,8 +21,8 @@ contract('Payroll employee getters', ([owner, employee]) => { describe('getEmployee', () => { context('when it has already been initialized', function () { - beforeEach('initialize payroll app', async () => { - await payroll.initialize(finance.address, denominationToken.address, priceFeed.address, RATE_EXPIRATION_TIME, { from: owner }) + beforeEach('initialize payroll app using USD as denomination token', async () => { + await payroll.initialize(finance.address, USD, priceFeed.address, RATE_EXPIRATION_TIME, { from: owner }) }) context('when the given id exists', () => { @@ -47,7 +42,7 @@ contract('Payroll employee getters', ([owner, employee]) => { assert.equal(accruedSalary, 0, 'employee accrued salary does not match') assert.equal(salary.toString(), 1000, 'employee salary does not match') assert.equal(lastPayroll.toString(), (await currentTimestamp()).toString(), 'employee last payroll does not match') - assert.equal(endDate.toString(), maxUint64(), 'employee end date does not match') + assert.equal(endDate.toString(), MAX_UINT64, 'employee end date does not match') }) }) @@ -71,8 +66,8 @@ contract('Payroll employee getters', ([owner, employee]) => { describe('getEmployeeByAddress', () => { context('when it has already been initialized', function () { - beforeEach('initialize payroll app', async () => { - await payroll.initialize(finance.address, denominationToken.address, priceFeed.address, RATE_EXPIRATION_TIME, { from: owner }) + beforeEach('initialize payroll app using USD as denomination token', async () => { + await payroll.initialize(finance.address, USD, priceFeed.address, RATE_EXPIRATION_TIME, { from: owner }) }) context('when the given address exists', () => { @@ -93,7 +88,7 @@ contract('Payroll employee getters', ([owner, employee]) => { assert.equal(reimbursements.toString(), 0, 'employee reimbursements does not match') assert.equal(accruedSalary.toString(), 0, 'employee accrued salary does not match') assert.equal(lastPayroll.toString(), (await currentTimestamp()).toString(), 'employee last payroll does not match') - assert.equal(endDate.toString(), maxUint64(), 'employee end date does not match') + assert.equal(endDate.toString(), MAX_UINT64, 'employee end date does not match') }) }) diff --git a/future-apps/payroll/test/contracts/Payroll_initialize.test.js b/future-apps/payroll/test/contracts/Payroll_initialize.test.js index 95ab36b032..a9c9c5a5dc 100644 --- a/future-apps/payroll/test/contracts/Payroll_initialize.test.js +++ b/future-apps/payroll/test/contracts/Payroll_initialize.test.js @@ -1,21 +1,15 @@ +const { USD } = require('../helpers/tokens.js')(artifacts, web3) const { assertRevert } = require('@aragon/test-helpers/assertThrow') -const { deployErc20TokenAndDeposit, deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy.js')(artifacts, web3) +const { NOW, RATE_EXPIRATION_TIME } = require('../helpers/time') +const { deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy')(artifacts, web3) const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' contract('Payroll initialization', ([owner]) => { - let dao, payroll, payrollBase, finance, vault, priceFeed, denominationToken - - 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 TOKEN_DECIMALS = 18 + let dao, payroll, payrollBase, finance, vault, priceFeed before('deploy base apps and tokens', async () => { ({ dao, finance, vault, payrollBase } = await deployContracts(owner)) - denominationToken = await deployErc20TokenAndDeposit(owner, finance, 'Denomination Token', TOKEN_DECIMALS) }) beforeEach('create payroll and price feed instance', async () => { @@ -27,7 +21,7 @@ contract('Payroll initialization', ([owner]) => { it('cannot initialize the base app', async () => { assert(await payrollBase.isPetrified(), 'base payroll app should be petrified') - await assertRevert(payrollBase.initialize(finance.address, denominationToken.address, priceFeed.address, RATE_EXPIRATION_TIME, { from }), 'INIT_ALREADY_INITIALIZED') + await assertRevert(payrollBase.initialize(finance.address, USD, priceFeed.address, RATE_EXPIRATION_TIME, { from }), 'INIT_ALREADY_INITIALIZED') }) context('when it has not been initialized yet', function () { @@ -38,32 +32,32 @@ contract('Payroll initialization', ([owner]) => { it('reverts when passing an expiration time lower than or equal to a minute', async () => { const ONE_MINUTE = 60 - await assertRevert(payroll.initialize(finance.address, denominationToken.address, priceFeed.address, ONE_MINUTE, { from }), 'PAYROLL_EXPIRY_TIME_TOO_SHORT') + await assertRevert(payroll.initialize(finance.address, USD, priceFeed.address, ONE_MINUTE, { from }), 'PAYROLL_EXPIRY_TIME_TOO_SHORT') }) it('reverts when passing an invalid finance instance', async () => { - await assertRevert(payroll.initialize(ZERO_ADDRESS, denominationToken.address, priceFeed.address, RATE_EXPIRATION_TIME, { from }), 'PAYROLL_FINANCE_NOT_CONTRACT') + await assertRevert(payroll.initialize(ZERO_ADDRESS, USD, priceFeed.address, RATE_EXPIRATION_TIME, { from }), 'PAYROLL_FINANCE_NOT_CONTRACT') }) it('reverts when passing an invalid feed instance', async () => { - await assertRevert(payroll.initialize(finance.address, denominationToken.address, ZERO_ADDRESS, RATE_EXPIRATION_TIME, { from }), 'PAYROLL_FEED_NOT_CONTRACT') + await assertRevert(payroll.initialize(finance.address, USD, ZERO_ADDRESS, RATE_EXPIRATION_TIME, { from }), 'PAYROLL_FEED_NOT_CONTRACT') }) }) context('when it has already been initialized', function () { beforeEach('initialize payroll app', async () => { - await payroll.initialize(finance.address, denominationToken.address, priceFeed.address, RATE_EXPIRATION_TIME, { from }) + await payroll.initialize(finance.address, USD, priceFeed.address, RATE_EXPIRATION_TIME, { from }) }) it('cannot be initialized again', async () => { - await assertRevert(payroll.initialize(finance.address, denominationToken.address, priceFeed.address, RATE_EXPIRATION_TIME, { from }), 'INIT_ALREADY_INITIALIZED') + await assertRevert(payroll.initialize(finance.address, USD, priceFeed.address, RATE_EXPIRATION_TIME, { from }), 'INIT_ALREADY_INITIALIZED') }) it('has a price feed instance, a finance instance, a denomination token and a rate expiration time', async () => { assert.equal(await payroll.feed(), priceFeed.address, 'feed address does not match') assert.equal(await payroll.finance(), finance.address, 'finance address should match') - assert.equal(await payroll.denominationToken(), denominationToken.address, 'denomination token does not match') assert.equal(await payroll.rateExpiryTime(), RATE_EXPIRATION_TIME, 'rate expiration time does not match') + assert.equal(web3.toChecksumAddress(await payroll.denominationToken()), USD, 'denomination token address does not match') }) }) }) 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 37dceca2ba..9aca9f0a6f 100644 --- a/future-apps/payroll/test/contracts/Payroll_modify_employee.test.js +++ b/future-apps/payroll/test/contracts/Payroll_modify_employee.test.js @@ -1,19 +1,14 @@ +const { USD } = require('../helpers/tokens.js')(artifacts, web3) const { assertRevert } = require('@aragon/test-helpers/assertThrow') const { getEvents, getEventArgument } = require('../helpers/events') -const { maxUint256, annualSalaryPerSecond } = require('../helpers/numbers')(web3) -const { deployErc20TokenAndDeposit, deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy.js')(artifacts, web3) +const { NOW, ONE_MONTH, RATE_EXPIRATION_TIME } = require('../helpers/time') +const { MAX_UINT256, annualSalaryPerSecond } = require('../helpers/numbers')(web3) +const { deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy')(artifacts, web3) const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' contract('Payroll employees modification', ([owner, employee, anotherEmployee, anyone]) => { - let dao, payroll, payrollBase, finance, vault, priceFeed, denominationToken - - 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 TOKEN_DECIMALS = 18 + let dao, payroll, payrollBase, finance, vault, priceFeed const increaseTime = async seconds => { await payroll.mockIncreaseTime(seconds) @@ -22,7 +17,6 @@ contract('Payroll employees modification', ([owner, employee, anotherEmployee, a before('deploy base apps and tokens', async () => { ({ dao, finance, vault, payrollBase } = await deployContracts(owner)) - denominationToken = await deployErc20TokenAndDeposit(owner, finance, 'Denomination Token', TOKEN_DECIMALS) }) beforeEach('create payroll and price feed instance', async () => { @@ -31,8 +25,8 @@ contract('Payroll employees modification', ([owner, employee, anotherEmployee, a describe('setEmployeeSalary', () => { context('when it has already been initialized', function () { - beforeEach('initialize payroll app', async () => { - await payroll.initialize(finance.address, denominationToken.address, priceFeed.address, RATE_EXPIRATION_TIME, { from: owner }) + beforeEach('initialize payroll app using USD as denomination token', async () => { + await payroll.initialize(finance.address, USD, priceFeed.address, RATE_EXPIRATION_TIME, { from: owner }) }) context('when the sender has permissions', () => { @@ -40,7 +34,7 @@ contract('Payroll employees modification', ([owner, employee, anotherEmployee, a context('when the given employee exists', () => { let employeeId - const previousSalary = annualSalaryPerSecond(100000, TOKEN_DECIMALS) + const previousSalary = annualSalaryPerSecond(100000) beforeEach('add employee', async () => { const receipt = await payroll.addEmployee(employee, previousSalary, 'Boss', await payroll.getTimestampPublic()) @@ -103,7 +97,7 @@ contract('Payroll employees modification', ([owner, employee, anotherEmployee, a context('when the employee is owed a huge salary amount', () => { beforeEach('accrued a huge salary amount', async () => { - await payroll.setEmployeeSalary(employeeId, maxUint256(), { from }) + await payroll.setEmployeeSalary(employeeId, MAX_UINT256, { from }) await increaseTime(ONE_MONTH) }) @@ -162,8 +156,8 @@ contract('Payroll employees modification', ([owner, employee, anotherEmployee, a describe('changeAddressByEmployee', () => { context('when it has already been initialized', function () { - beforeEach('initialize payroll app', async () => { - await payroll.initialize(finance.address, denominationToken.address, priceFeed.address, RATE_EXPIRATION_TIME, { from: owner }) + beforeEach('initialize payroll app using USD as denomination token', async () => { + await payroll.initialize(finance.address, USD, priceFeed.address, RATE_EXPIRATION_TIME, { from: owner }) }) context('when the sender is an employee', () => { diff --git a/future-apps/payroll/test/contracts/Payroll_payday.test.js b/future-apps/payroll/test/contracts/Payroll_payday.test.js index 0b0b58f7b6..ad683893b3 100644 --- a/future-apps/payroll/test/contracts/Payroll_payday.test.js +++ b/future-apps/payroll/test/contracts/Payroll_payday.test.js @@ -1,19 +1,13 @@ 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 { MAX_UINT256 } = require('../helpers/numbers')(web3) const { getEventArgument } = require('../helpers/events') -const { deployErc20TokenAndDeposit, deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy.js')(artifacts, web3) +const { NOW, ONE_MONTH, TWO_MONTHS, RATE_EXPIRATION_TIME } = require('../helpers/time') +const { deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy')(artifacts, web3) +const { USD, deployDAI, deployANT, DAI_RATE, ANT_RATE, setTokenRates } = require('../helpers/tokens.js')(artifacts, web3) contract('Payroll payday', ([owner, employee, anyone]) => { - 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 TOKEN_DECIMALS = 18 + let dao, payroll, payrollBase, finance, vault, priceFeed, DAI, ANT const increaseTime = async seconds => { await payroll.mockIncreaseTime(seconds) @@ -22,8 +16,8 @@ contract('Payroll payday', ([owner, employee, anyone]) => { 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) + ANT = await deployANT(owner, finance) + DAI = await deployDAI(owner, finance) }) beforeEach('create payroll and price feed instance', async () => { @@ -32,13 +26,12 @@ contract('Payroll payday', ([owner, employee, anyone]) => { describe('payroll payday', () => { context('when it has already been initialized', function () { - beforeEach('initialize payroll app', async () => { - await payroll.initialize(finance.address, denominationToken.address, priceFeed.address, RATE_EXPIRATION_TIME, { from: owner }) + beforeEach('initialize payroll app using USD as denomination token', async () => { + await payroll.initialize(finance.address, USD, priceFeed.address, RATE_EXPIRATION_TIME, { from: owner }) }) beforeEach('set token rates', async () => { - anotherTokenRate = bn(5) - await setTokenRates(priceFeed, denominationToken, [anotherToken], [anotherTokenRate]) + await setTokenRates(priceFeed, USD, [DAI, ANT], [DAI_RATE, ANT_RATE]) }) context('when the sender is an employee', () => { @@ -54,32 +47,32 @@ contract('Payroll payday', ([owner, employee, anyone]) => { }) context('when the employee has already set some token allocations', () => { - const denominationTokenAllocation = 80 - const anotherTokenAllocation = 20 + const allocationDAI = 80 + const allocationANT = 20 beforeEach('set tokens allocation', async () => { - await payroll.addAllowedToken(anotherToken.address, { from: owner }) - await payroll.addAllowedToken(denominationToken.address, { from: owner }) - await payroll.determineAllocation([denominationToken.address, anotherToken.address], [denominationTokenAllocation, anotherTokenAllocation], { from }) + await payroll.addAllowedToken(ANT.address, { from: owner }) + await payroll.addAllowedToken(DAI.address, { from: owner }) + await payroll.determineAllocation([DAI.address, ANT.address], [allocationDAI, allocationANT], { from }) }) const assertTransferredAmounts = (requestedAmount, expectedRequestedAmount = requestedAmount) => { - const requestedDenominationTokenAmount = Math.round(expectedRequestedAmount * denominationTokenAllocation / 100) - const requestedAnotherTokenAmount = Math.round(expectedRequestedAmount * anotherTokenAllocation / 100) + const requestedDAI = DAI_RATE.mul(expectedRequestedAmount * allocationDAI / 100).trunc() + const requestedANT = ANT_RATE.mul(expectedRequestedAmount * allocationANT / 100).trunc() it('transfers the requested salary amount', async () => { - const previousDenominationTokenBalance = await denominationToken.balanceOf(employee) - const previousAnotherTokenBalance = await anotherToken.balanceOf(employee) + const previousDAI = await DAI.balanceOf(employee) + const previousANT = await ANT.balanceOf(employee) await payroll.payday(PAYMENT_TYPES.PAYROLL, requestedAmount, { from }) - const currentDenominationTokenBalance = await denominationToken.balanceOf(employee) - const expectedDenominationTokenBalance = previousDenominationTokenBalance.plus(requestedDenominationTokenAmount); - assert.equal(currentDenominationTokenBalance.toString(), expectedDenominationTokenBalance.toString(), 'current denomination token balance does not match') + const currentDAI = await DAI.balanceOf(employee) + const expectedDAI = previousDAI.plus(requestedDAI); + assert.equal(currentDAI.toString(), expectedDAI.toString(), 'current DAI balance does not match') - const currentAnotherTokenBalance = await anotherToken.balanceOf(employee) - const expectedAnotherTokenBalance = anotherTokenRate.mul(requestedAnotherTokenAmount).plus(previousAnotherTokenBalance).trunc() - assert.equal(currentAnotherTokenBalance.toString(), expectedAnotherTokenBalance.toString(), 'current token balance does not match') + const currentANT = await ANT.balanceOf(employee) + const expectedANT = previousANT.plus(requestedANT) + assert.equal(currentANT.toString(), expectedANT.toString(), 'current ANT balance does not match') }) it('emits one event per allocated token', async () => { @@ -88,43 +81,43 @@ contract('Payroll payday', ([owner, employee, anyone]) => { const events = receipt.logs.filter(l => l.event === 'SendPayment') assert.equal(events.length, 2, 'should have emitted two events') - const denominationTokenEvent = events.find(e => e.args.token === denominationToken.address).args - assert.equal(denominationTokenEvent.employee, employee, 'employee address does not match') - assert.equal(denominationTokenEvent.token, denominationToken.address, 'denomination token address does not match') - assert.equal(denominationTokenEvent.amount.toString(), requestedDenominationTokenAmount, 'payment amount does not match') - assert.equal(denominationTokenEvent.paymentReference, 'Payroll', 'payment reference does not match') + const eventDAI = events.find(e => e.args.token === DAI.address).args + assert.equal(eventDAI.employee, employee, 'employee address does not match') + assert.equal(eventDAI.token, DAI.address, 'DAI address does not match') + assert.equal(eventDAI.amount.toString(), requestedDAI, 'payment amount does not match') + assert.equal(eventDAI.paymentReference, 'Payroll', 'payment reference does not match') - 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') - assert.equal(anotherTokenEvent.amount.div(anotherTokenRate).trunc().toString(), requestedAnotherTokenAmount, 'payment amount does not match') - assert.equal(anotherTokenEvent.paymentReference, 'Payroll', 'payment reference does not match') + const eventANT = events.find(e => e.args.token === ANT.address).args + assert.equal(eventANT.employee, employee, 'employee address does not match') + assert.equal(eventANT.token, ANT.address, 'ANT address does not match') + assert.equal(eventANT.amount.toString(), requestedANT, 'payment amount does not match') + assert.equal(eventANT.paymentReference, 'Payroll', 'payment reference does not match') }) it('can be called multiple times between periods of time', async () => { // terminate employee in the future to ensure we can request payroll multiple times await payroll.terminateEmployee(employeeId, NOW + TWO_MONTHS + TWO_MONTHS, { from: owner }) - const previousDenominationTokenBalance = await denominationToken.balanceOf(employee) - const previousAnotherTokenBalance = await anotherToken.balanceOf(employee) + const previousDAI = await DAI.balanceOf(employee) + const previousANT = await ANT.balanceOf(employee) await payroll.payday(PAYMENT_TYPES.PAYROLL, requestedAmount, { from }) const newOwedAmount = salary * ONE_MONTH - const newDenominationTokenOwedAmount = Math.round(newOwedAmount * denominationTokenAllocation / 100) - const newAnotherTokenOwedAmount = Math.round(newOwedAmount * anotherTokenAllocation / 100) + const newDAIAmount = DAI_RATE.mul(newOwedAmount * allocationDAI / 100).trunc() + const newANTAmount = ANT_RATE.mul(newOwedAmount * allocationANT / 100).trunc() await increaseTime(ONE_MONTH) - await setTokenRates(priceFeed, denominationToken, [anotherToken], [anotherTokenRate]) + await setTokenRates(priceFeed, USD, [DAI, ANT], [DAI_RATE, ANT_RATE]) await payroll.payday(PAYMENT_TYPES.PAYROLL, newOwedAmount, { from }) - const currentDenominationTokenBalance = await denominationToken.balanceOf(employee) - const expectedDenominationTokenBalance = previousDenominationTokenBalance.plus(requestedDenominationTokenAmount + newDenominationTokenOwedAmount) - assert.equal(currentDenominationTokenBalance.toString(), expectedDenominationTokenBalance.toString(), 'current denomination token balance does not match') + const currentDAI = await DAI.balanceOf(employee) + const expectedDAI = previousDAI.plus(requestedDAI).plus(newDAIAmount) + assert.equal(currentDAI.toString(), expectedDAI.toString(), 'current DAI balance does not match') - const currentAnotherTokenBalance = await anotherToken.balanceOf(employee) - const expectedAnotherTokenBalance = anotherTokenRate.mul(requestedAnotherTokenAmount + newAnotherTokenOwedAmount).plus(previousAnotherTokenBalance) - assert.equal(currentAnotherTokenBalance.toString(), expectedAnotherTokenBalance.toString(), 'current token balance does not match') + const currentANT = await ANT.balanceOf(employee) + const expectedANT = previousANT.plus(requestedANT).plus(newANTAmount) + assert.equal(currentANT.toString(), expectedANT.toString(), 'current ANT balance does not match') }) } @@ -168,7 +161,7 @@ contract('Payroll payday', ([owner, employee, anyone]) => { context('when exchange rates are expired', () => { beforeEach('expire exchange rates', async () => { const expiredTimestamp = (await payroll.getTimestampPublic()).sub(RATE_EXPIRATION_TIME + 1) - await setTokenRates(priceFeed, denominationToken, [anotherToken], [anotherTokenRate], expiredTimestamp) + await setTokenRates(priceFeed, USD, [DAI, ANT], [DAI_RATE, ANT_RATE], expiredTimestamp) }) it('reverts', async () => { @@ -261,7 +254,7 @@ contract('Payroll payday', ([owner, employee, anyone]) => { context('when exchange rates are expired', () => { beforeEach('expire exchange rates', async () => { const expiredTimestamp = (await payroll.getTimestampPublic()).sub(RATE_EXPIRATION_TIME + 1) - await setTokenRates(priceFeed, denominationToken, [anotherToken], [anotherTokenRate], expiredTimestamp) + await setTokenRates(priceFeed, USD, [DAI, ANT], [DAI_RATE, ANT_RATE], expiredTimestamp) }) it('reverts', async () => { @@ -360,7 +353,7 @@ contract('Payroll payday', ([owner, employee, anyone]) => { beforeEach('accumulate some pending salary and renew token rates', async () => { await increaseTime(ONE_MONTH) - await setTokenRates(priceFeed, denominationToken, [anotherToken], [anotherTokenRate]) + await setTokenRates(priceFeed, USD, [DAI, ANT], [DAI_RATE, ANT_RATE]) }) context('when the requested amount is zero', () => { @@ -486,7 +479,7 @@ contract('Payroll payday', ([owner, employee, anyone]) => { context('when exchange rates are expired', () => { beforeEach('expire exchange rates', async () => { const expiredTimestamp = (await payroll.getTimestampPublic()).sub(RATE_EXPIRATION_TIME + 1) - await setTokenRates(priceFeed, denominationToken, [anotherToken], [anotherTokenRate], expiredTimestamp) + await setTokenRates(priceFeed, USD, [DAI, ANT], [DAI_RATE, ANT_RATE], expiredTimestamp) }) itReverts(requestedAmount, expiredRatesReason) @@ -570,13 +563,13 @@ contract('Payroll payday', ([owner, employee, anyone]) => { } context('when the employee has already set some token allocations', () => { - const denominationTokenAllocation = 80 - const anotherTokenAllocation = 20 + const allocationDAI = 80 + const allocationANT = 20 beforeEach('set tokens allocation', async () => { - await payroll.addAllowedToken(anotherToken.address, {from: owner}) - await payroll.addAllowedToken(denominationToken.address, {from: owner}) - await payroll.determineAllocation([denominationToken.address, anotherToken.address], [denominationTokenAllocation, anotherTokenAllocation], {from}) + await payroll.addAllowedToken(ANT.address, { from: owner }) + await payroll.addAllowedToken(DAI.address, { from: owner }) + await payroll.determineAllocation([DAI.address, ANT.address], [allocationDAI, allocationANT], { from }) }) itRevertsAnyAttemptToWithdrawPartialPayroll() @@ -588,7 +581,7 @@ contract('Payroll payday', ([owner, employee, anyone]) => { }) context('when the employee has a huge salary', () => { - const salary = maxUint256() + const salary = MAX_UINT256 beforeEach('add employee', async () => { const receipt = await payroll.addEmployee(employee, salary, 'Boss', await payroll.getTimestampPublic()) @@ -596,17 +589,17 @@ contract('Payroll payday', ([owner, employee, anyone]) => { }) context('when the employee has already set some token allocations', () => { - const denominationTokenAllocation = 80 - const anotherTokenAllocation = 20 + const allocationDAI = 80 + const allocationANT = 20 beforeEach('set tokens allocation', async () => { - await payroll.addAllowedToken(anotherToken.address, {from: owner}) - await payroll.addAllowedToken(denominationToken.address, {from: owner}) - await payroll.determineAllocation([denominationToken.address, anotherToken.address], [denominationTokenAllocation, anotherTokenAllocation], {from}) + await payroll.addAllowedToken(ANT.address, { from: owner }) + await payroll.addAllowedToken(DAI.address, { from: owner }) + await payroll.determineAllocation([DAI.address, ANT.address], [allocationDAI, allocationANT], { from }) }) context('when the employee has some pending salary', () => { - const owedSalary = maxUint256() + const owedSalary = MAX_UINT256 beforeEach('accumulate some pending salary', async () => { await increaseTime(ONE_MONTH) @@ -622,22 +615,22 @@ contract('Payroll payday', ([owner, employee, anyone]) => { const requestedAmount = 10000 const assertTransferredAmounts = requestedAmount => { - const requestedDenominationTokenAmount = Math.round(requestedAmount * denominationTokenAllocation / 100) - const requestedAnotherTokenAmount = Math.round(requestedAmount * anotherTokenAllocation / 100) + const requestedDAI = DAI_RATE.mul(requestedAmount * allocationDAI / 100).trunc() + const requestedANT = ANT_RATE.mul(requestedAmount * allocationANT / 100).trunc() it('transfers the requested salary amount', async () => { - const previousDenominationTokenBalance = await denominationToken.balanceOf(employee) - const previousAnotherTokenBalance = await anotherToken.balanceOf(employee) + const previousDAI = await DAI.balanceOf(employee) + const previousANT = await ANT.balanceOf(employee) await payroll.payday(PAYMENT_TYPES.PAYROLL, requestedAmount, { from }) - const currentDenominationTokenBalance = await denominationToken.balanceOf(employee) - const expectedDenominationTokenBalance = previousDenominationTokenBalance.plus(requestedDenominationTokenAmount); - assert.equal(currentDenominationTokenBalance.toString(), expectedDenominationTokenBalance.toString(), 'current denomination token balance does not match') + const currentDAI = await DAI.balanceOf(employee) + const expectedDAI = previousDAI.plus(requestedDAI) + assert.equal(currentDAI.toString(), expectedDAI.toString(), 'current DAI balance does not match') - const currentAnotherTokenBalance = await anotherToken.balanceOf(employee) - const expectedAnotherTokenBalance = anotherTokenRate.mul(requestedAnotherTokenAmount).plus(previousAnotherTokenBalance).trunc() - assert.equal(currentAnotherTokenBalance.toString(), expectedAnotherTokenBalance.toString(), 'current token balance does not match') + const currentANT = await ANT.balanceOf(employee) + const expectedANT = previousANT.plus(requestedANT) + assert.equal(currentANT.toString(), expectedANT.toString(), 'current ANT balance does not match') }) it('emits one event per allocated token', async () => { @@ -646,39 +639,39 @@ contract('Payroll payday', ([owner, employee, anyone]) => { const events = receipt.logs.filter(l => l.event === 'SendPayment') assert.equal(events.length, 2, 'should have emitted two events') - const denominationTokenEvent = events.find(e => e.args.token === denominationToken.address).args - assert.equal(denominationTokenEvent.employee, employee, 'employee address does not match') - assert.equal(denominationTokenEvent.token, denominationToken.address, 'denomination token address does not match') - assert.equal(denominationTokenEvent.amount.toString(), requestedDenominationTokenAmount, 'payment amount does not match') - assert.equal(denominationTokenEvent.paymentReference, 'Payroll', 'payment reference does not match') - - 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') - assert.equal(anotherTokenEvent.amount.div(anotherTokenRate).trunc().toString(), requestedAnotherTokenAmount, 'payment amount does not match') - assert.equal(anotherTokenEvent.paymentReference, 'Payroll', 'payment reference does not match') + const eventDAI = events.find(e => e.args.token === DAI.address).args + assert.equal(eventDAI.employee, employee, 'employee address does not match') + assert.equal(eventDAI.token, DAI.address, 'DAI address does not match') + assert.equal(eventDAI.amount.toString(), requestedDAI, 'payment amount does not match') + assert.equal(eventDAI.paymentReference, 'Payroll', 'payment reference does not match') + + const eventANT = events.find(e => e.args.token === ANT.address).args + assert.equal(eventANT.employee, employee, 'employee address does not match') + assert.equal(eventANT.token, ANT.address, 'ANT address does not match') + assert.equal(eventANT.amount.toString(), requestedANT, 'payment amount does not match') + assert.equal(eventANT.paymentReference, 'Payroll', 'payment reference does not match') }) it('can be called multiple times between periods of time', async () => { // terminate employee in the future to ensure we can request payroll multiple times await payroll.terminateEmployee(employeeId, NOW + TWO_MONTHS + TWO_MONTHS, { from: owner }) - const previousDenominationTokenBalance = await denominationToken.balanceOf(employee) - const previousAnotherTokenBalance = await anotherToken.balanceOf(employee) + const previousDAI = await DAI.balanceOf(employee) + const previousANT = await ANT.balanceOf(employee) await payroll.payday(PAYMENT_TYPES.PAYROLL, requestedAmount, { from }) await increaseTime(ONE_MONTH) - await setTokenRates(priceFeed, denominationToken, [anotherToken], [anotherTokenRate]) + await setTokenRates(priceFeed, USD, [DAI, ANT], [DAI_RATE, ANT_RATE]) await payroll.payday(PAYMENT_TYPES.PAYROLL, requestedAmount, { from }) - const currentDenominationTokenBalance = await denominationToken.balanceOf(employee) - const expectedDenominationTokenBalance = previousDenominationTokenBalance.plus(requestedDenominationTokenAmount * 2) - assert.equal(currentDenominationTokenBalance.toString(), expectedDenominationTokenBalance.toString(), 'current denomination token balance does not match') + const currentDAI = await DAI.balanceOf(employee) + const expectedDAI = previousDAI.plus(requestedDAI * 2) + assert.equal(currentDAI.toString(), expectedDAI.toString(), 'current DAI balance does not match') - const currentAnotherTokenBalance = await anotherToken.balanceOf(employee) - const expectedAnotherTokenBalance = anotherTokenRate.mul(requestedAnotherTokenAmount * 2).plus(previousAnotherTokenBalance) - assert.equal(currentAnotherTokenBalance.toString(), expectedAnotherTokenBalance.toString(), 'current token balance does not match') + const currentANT = await ANT.balanceOf(employee) + const expectedANT = previousANT.plus(requestedANT * 2) + assert.equal(currentANT.toString(), expectedANT.toString(), 'current ANT balance does not match') }) } @@ -713,7 +706,7 @@ contract('Payroll payday', ([owner, employee, anyone]) => { context('when exchange rates are expired', () => { beforeEach('expire exchange rates', async () => { const expiredTimestamp = (await payroll.getTimestampPublic()).sub(RATE_EXPIRATION_TIME + 1) - await setTokenRates(priceFeed, denominationToken, [anotherToken], [anotherTokenRate], expiredTimestamp) + await setTokenRates(priceFeed, USD, [DAI, ANT], [DAI_RATE, ANT_RATE], expiredTimestamp) }) it('reverts', async () => { @@ -779,7 +772,7 @@ contract('Payroll payday', ([owner, employee, anyone]) => { context('when the employee did not set any token allocations yet', () => { context('when the employee has some pending salary', () => { - const owedSalary = maxUint256() + const owedSalary = MAX_UINT256 beforeEach('accumulate some pending salary', async () => { await increaseTime(ONE_MONTH) diff --git a/future-apps/payroll/test/contracts/Payroll_rates.test.js b/future-apps/payroll/test/contracts/Payroll_rates.test.js index 3403fd18b2..f75f88f9f3 100644 --- a/future-apps/payroll/test/contracts/Payroll_rates.test.js +++ b/future-apps/payroll/test/contracts/Payroll_rates.test.js @@ -1,22 +1,12 @@ const PAYMENT_TYPES = require('../helpers/payment_types') -const { bigExp, bn } = require('../helpers/numbers')(web3) +const { bn } = require('../helpers/numbers')(web3) const { getEventArgument } = require('../helpers/events') -const { deployErc20TokenAndDeposit, deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy.js')(artifacts, web3) +const { NOW, TWO_MINUTES, RATE_EXPIRATION_TIME } = require('../helpers/time') +const { deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy')(artifacts, web3) +const { USD, ETH, ETH_RATE, deployDAI, DAI_RATE, deployANT, ANT_RATE, setTokenRate } = require('../helpers/tokens.js')(artifacts, web3) contract('Payroll rates handling,', ([owner, employee]) => { - let USD, ETH, DAI, ANT - let dao, payroll, payrollBase, finance, vault, priceFeed, employeeId - - const NOW = 1553703809 // random fixed timestamp in seconds - const TWO_MINUTES = 60 * 2 - const ONE_MONTH = 60 * 60 * 24 * 31 - const RATE_EXPIRATION_TIME = ONE_MONTH - - const TOKEN_DECIMALS = 18 - const ONE = bigExp(1, 18) - const SIG = '00'.repeat(65) // sig full of 0s - - const formatRate = (n) => bn(n.toFixed(18)).times(ONE) + let dao, payroll, payrollBase, finance, vault, priceFeed, employeeId, DAI, ANT const increaseTime = async seconds => { await payroll.mockIncreaseTime(seconds) @@ -26,12 +16,9 @@ contract('Payroll rates handling,', ([owner, employee]) => { before('deploy contracts and tokens', async () => { ({ dao, finance, vault, payrollBase } = await deployContracts(owner)) - USD = '0x0000000000000000000000000000000000000001' - ETH = '0x0000000000000000000000000000000000000000' + DAI = await deployDAI(owner, finance) + ANT = await deployANT(owner, finance) await finance.deposit(ETH, bn(50, 18), 'Initial ETH deposit', { from: owner, value: bn(50, 18) }) - - DAI = await deployErc20TokenAndDeposit(owner, finance, 'DAI', TOKEN_DECIMALS) - ANT = await deployErc20TokenAndDeposit(owner, finance, 'ANT', TOKEN_DECIMALS) }) beforeEach('initialize payroll app with USD as denomination token', async () => { @@ -41,13 +28,9 @@ contract('Payroll rates handling,', ([owner, employee]) => { describe('rates', () => { beforeEach('set rates and allow tokens', async () => { - const DAI_rate = bn(1) - const ANT_rate = bn(0.5) - const ETH_rate = bn(20) - - await priceFeed.update(ETH, USD, formatRate(ETH_rate), NOW, SIG) - await priceFeed.update(DAI.address, USD, formatRate(DAI_rate), NOW, SIG) - await priceFeed.update(ANT.address, USD, formatRate(ANT_rate), NOW, SIG) + await setTokenRate(priceFeed, ETH, USD, ETH_RATE) + await setTokenRate(priceFeed, DAI, USD, DAI_RATE) + await setTokenRate(priceFeed, ANT, USD, ANT_RATE) await payroll.addAllowedToken(ETH, { from: owner }) await payroll.addAllowedToken(DAI.address, { from: owner }) diff --git a/future-apps/payroll/test/contracts/Payroll_reentrancy.test.js b/future-apps/payroll/test/contracts/Payroll_reentrancy.test.js index b210c5dd50..39f1d54f9c 100644 --- a/future-apps/payroll/test/contracts/Payroll_reentrancy.test.js +++ b/future-apps/payroll/test/contracts/Payroll_reentrancy.test.js @@ -1,6 +1,7 @@ const { bigExp } = require('../helpers/numbers')(web3) const { assertRevert } = require('@aragon/test-helpers/assertThrow') -const { deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy.js')(artifacts, web3) +const { NOW, ONE_MONTH, RATE_EXPIRATION_TIME } = require('../helpers/time') +const { deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy')(artifacts, web3) const MaliciousERC20 = artifacts.require('MaliciousERC20') const MaliciousEmployee = artifacts.require('MaliciousEmployee') @@ -8,11 +9,6 @@ const MaliciousEmployee = artifacts.require('MaliciousEmployee') contract('Payroll reentrancy guards', ([owner]) => { let dao, payroll, payrollBase, finance, vault, priceFeed, maliciousToken, employee - 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 REENTRANCY_ACTIONS = { PAYDAY: 0, CHANGE_ADDRESS: 1, SET_ALLOCATION: 2 } const increaseTime = async seconds => { diff --git a/future-apps/payroll/test/contracts/Payroll_reimbursements.test.js b/future-apps/payroll/test/contracts/Payroll_reimbursements.test.js index 0674e39709..ec9c36fda7 100644 --- a/future-apps/payroll/test/contracts/Payroll_reimbursements.test.js +++ b/future-apps/payroll/test/contracts/Payroll_reimbursements.test.js @@ -1,19 +1,13 @@ 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 { MAX_UINT256 } = require('../helpers/numbers')(web3) const { getEvents, getEventArgument } = require('../helpers/events') -const { deployErc20TokenAndDeposit, deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy.js')(artifacts, web3) +const { NOW, ONE_MONTH, RATE_EXPIRATION_TIME } = require('../helpers/time') +const { deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy')(artifacts, web3) +const { USD, deployDAI, deployANT, DAI_RATE, ANT_RATE, setTokenRates } = require('../helpers/tokens.js')(artifacts, web3) contract('Payroll reimbursements', ([owner, employee, anyone]) => { - 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 TOKEN_DECIMALS = 18 + let dao, payroll, payrollBase, finance, vault, priceFeed, DAI, ANT const increaseTime = async seconds => { await payroll.mockIncreaseTime(seconds) @@ -22,8 +16,8 @@ contract('Payroll reimbursements', ([owner, employee, anyone]) => { 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) + ANT = await deployANT(owner, finance) + DAI = await deployDAI(owner, finance) }) beforeEach('create payroll and price feed instance', async () => { @@ -33,7 +27,7 @@ contract('Payroll reimbursements', ([owner, employee, anyone]) => { describe('addReimbursement', () => { context('when it has already been initialized', function () { beforeEach('initialize payroll app', async () => { - await payroll.initialize(finance.address, denominationToken.address, priceFeed.address, RATE_EXPIRATION_TIME, { from: owner }) + await payroll.initialize(finance.address, USD, priceFeed.address, RATE_EXPIRATION_TIME, { from: owner }) }) context('when the sender has permissions', () => { @@ -80,7 +74,7 @@ contract('Payroll reimbursements', ([owner, employee, anyone]) => { }) context('when the given value way greater than zero', () => { - const value = maxUint256() + const value = MAX_UINT256 it('reverts', async () => { await payroll.addReimbursement(employeeId, 1, { from }) @@ -135,12 +129,11 @@ contract('Payroll reimbursements', ([owner, employee, anyone]) => { describe('reimbursements payday', () => { context('when it has already been initialized', function () { beforeEach('initialize payroll app', async () => { - await payroll.initialize(finance.address, denominationToken.address, priceFeed.address, RATE_EXPIRATION_TIME, { from: owner }) + await payroll.initialize(finance.address, USD, priceFeed.address, RATE_EXPIRATION_TIME, { from: owner }) }) beforeEach('set token rates', async () => { - anotherTokenRate = bn(5) - await setTokenRates(priceFeed, denominationToken, [anotherToken], [anotherTokenRate]) + await setTokenRates(priceFeed, USD, [DAI, ANT], [DAI_RATE, ANT_RATE]) }) context('when the sender is an employee', () => { @@ -155,13 +148,13 @@ contract('Payroll reimbursements', ([owner, employee, anyone]) => { }) context('when the employee has already set some token allocations', () => { - const denominationTokenAllocation = 80 - const anotherTokenAllocation = 20 + const allocationDAI = 80 + const allocationANT = 20 beforeEach('set tokens allocation', async () => { - await payroll.addAllowedToken(anotherToken.address, { from: owner }) - await payroll.addAllowedToken(denominationToken.address, { from: owner }) - await payroll.determineAllocation([denominationToken.address, anotherToken.address], [denominationTokenAllocation, anotherTokenAllocation], { from }) + await payroll.addAllowedToken(ANT.address, { from: owner }) + await payroll.addAllowedToken(DAI.address, { from: owner }) + await payroll.determineAllocation([DAI.address, ANT.address], [allocationDAI, allocationANT], { from }) }) context('when the employee has some pending reimbursements', () => { @@ -173,22 +166,22 @@ contract('Payroll reimbursements', ([owner, employee, anyone]) => { }) const assertTransferredAmounts = (requestedAmount, expectedRequestedAmount = requestedAmount) => { - const requestedDenominationTokenAmount = parseInt(expectedRequestedAmount * denominationTokenAllocation / 100) - const requestedAnotherTokenAmount = expectedRequestedAmount * anotherTokenAllocation / 100 + const requestedDAI = DAI_RATE.mul(expectedRequestedAmount * allocationDAI / 100).trunc() + const requestedANT = ANT_RATE.mul(expectedRequestedAmount * allocationANT / 100).trunc() it('transfers all the pending reimbursements', async () => { - const previousDenominationTokenBalance = await denominationToken.balanceOf(employee) - const previousAnotherTokenBalance = await anotherToken.balanceOf(employee) + const previousDAI = await DAI.balanceOf(employee) + const previousANT = await ANT.balanceOf(employee) await payroll.payday(PAYMENT_TYPES.REIMBURSEMENT, requestedAmount, { from }) - const currentDenominationTokenBalance = await denominationToken.balanceOf(employee) - const expectedDenominationTokenBalance = previousDenominationTokenBalance.plus(requestedDenominationTokenAmount); - assert.equal(currentDenominationTokenBalance.toString(), expectedDenominationTokenBalance.toString(), 'current denomination token balance does not match') + const currentDAI = await DAI.balanceOf(employee) + const expectedDAI = previousDAI.plus(requestedDAI); + assert.equal(currentDAI.toString(), expectedDAI.toString(), 'current DAI balance does not match') - const currentAnotherTokenBalance = await anotherToken.balanceOf(employee) - const expectedAnotherTokenBalance = anotherTokenRate.mul(requestedAnotherTokenAmount).plus(previousAnotherTokenBalance).trunc() - assert.equal(currentAnotherTokenBalance.toString(), expectedAnotherTokenBalance.toString(), 'current token balance does not match') + const currentANT = await ANT.balanceOf(employee) + const expectedANT = previousANT.plus(requestedANT) + assert.equal(currentANT.toString(), expectedANT.toString(), 'current ANT balance does not match') }) it('emits one event per allocated token', async () => { @@ -197,17 +190,17 @@ contract('Payroll reimbursements', ([owner, employee, anyone]) => { const events = receipt.logs.filter(l => l.event === 'SendPayment') assert.equal(events.length, 2, 'should have emitted two events') - const denominationTokenEvent = events.find(e => e.args.token === denominationToken.address).args - assert.equal(denominationTokenEvent.employee, employee, 'employee address does not match') - assert.equal(denominationTokenEvent.token, denominationToken.address, 'denomination token address does not match') - assert.equal(denominationTokenEvent.amount.toString(), requestedDenominationTokenAmount, 'payment amount does not match') - assert.equal(denominationTokenEvent.paymentReference, 'Reimbursement', 'payment reference does not match') - - 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') - assert.equal(anotherTokenEvent.amount.div(anotherTokenRate).trunc().toString(), parseInt(requestedAnotherTokenAmount), 'payment amount does not match') - assert.equal(anotherTokenEvent.paymentReference, 'Reimbursement', 'payment reference does not match') + const eventDAI = events.find(e => e.args.token === DAI.address).args + assert.equal(eventDAI.employee, employee, 'employee address does not match') + assert.equal(eventDAI.token, DAI.address, 'DAI address does not match') + assert.equal(eventDAI.amount.toString(), requestedDAI, 'payment amount does not match') + assert.equal(eventDAI.paymentReference, 'Reimbursement', 'payment reference does not match') + + const eventANT = events.find(e => e.args.token === ANT.address).args + assert.equal(eventANT.employee, employee, 'employee address does not match') + assert.equal(eventANT.token, ANT.address, 'token address does not match') + assert.equal(eventANT.amount.toString(), requestedANT, 'payment amount does not match') + assert.equal(eventANT.paymentReference, 'Reimbursement', 'payment reference does not match') }) } @@ -233,7 +226,7 @@ contract('Payroll reimbursements', ([owner, employee, anyone]) => { context('when exchange rates are expired', () => { beforeEach('expire exchange rates', async () => { const expiredTimestamp = (await payroll.getTimestampPublic()).sub(RATE_EXPIRATION_TIME + 1) - await setTokenRates(priceFeed, denominationToken, [anotherToken], [anotherTokenRate], expiredTimestamp) + await setTokenRates(priceFeed, USD, [DAI, ANT], [DAI_RATE, ANT_RATE], expiredTimestamp) }) it('reverts', async () => { @@ -286,7 +279,7 @@ contract('Payroll reimbursements', ([owner, employee, anyone]) => { context('when exchange rates are expired', () => { beforeEach('expire exchange rates', async () => { const expiredTimestamp = (await payroll.getTimestampPublic()).sub(RATE_EXPIRATION_TIME + 1) - await setTokenRates(priceFeed, denominationToken, [anotherToken], [anotherTokenRate], expiredTimestamp) + await setTokenRates(priceFeed, USD, [DAI, ANT], [DAI_RATE, ANT_RATE], expiredTimestamp) }) it('reverts', async () => { @@ -377,7 +370,7 @@ contract('Payroll reimbursements', ([owner, employee, anyone]) => { context('when exchange rates are expired', () => { beforeEach('expire exchange rates', async () => { const expiredTimestamp = (await payroll.getTimestampPublic()).sub(RATE_EXPIRATION_TIME + 1) - await setTokenRates(priceFeed, denominationToken, [anotherToken], [anotherTokenRate], expiredTimestamp) + await setTokenRates(priceFeed, USD, [DAI, ANT], [DAI_RATE, ANT_RATE], 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 43cb3ac8c8..37604128b5 100644 --- a/future-apps/payroll/test/contracts/Payroll_settings.test.js +++ b/future-apps/payroll/test/contracts/Payroll_settings.test.js @@ -1,23 +1,18 @@ +const { USD } = require('../helpers/tokens')(artifacts, web3) const { getEvents } = require('../helpers/events') const { assertRevert } = require('@aragon/test-helpers/assertThrow') -const { deployErc20TokenAndDeposit, deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy.js')(artifacts, web3) +const { NOW, RATE_EXPIRATION_TIME } = require('../helpers/time') +const { deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy')(artifacts, web3) const PriceFeed = artifacts.require('PriceFeedMock') const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' contract('Payroll settings', ([owner, anyone]) => { - let dao, payroll, payrollBase, finance, vault, priceFeed, denominationToken + let dao, payroll, payrollBase, finance, vault, priceFeed - 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 TOKEN_DECIMALS = 18 before('deploy base apps and tokens', async () => { ({ dao, finance, vault, payrollBase } = await deployContracts(owner)) - denominationToken = await deployErc20TokenAndDeposit(owner, finance, 'Denomination Token', TOKEN_DECIMALS) }) beforeEach('create payroll and price feed instance', async () => { @@ -32,8 +27,8 @@ contract('Payroll settings', ([owner, anyone]) => { }) context('when it has already been initialized', function () { - beforeEach('initialize payroll app', async () => { - await payroll.initialize(finance.address, denominationToken.address, priceFeed.address, RATE_EXPIRATION_TIME, { from: owner }) + beforeEach('initialize payroll app using USD as denomination token', async () => { + await payroll.initialize(finance.address, USD, priceFeed.address, RATE_EXPIRATION_TIME, { from: owner }) }) context('when the sender has permissions', async () => { @@ -86,8 +81,8 @@ contract('Payroll settings', ([owner, anyone]) => { describe('setRateExpiryTime', () => { context('when it has already been initialized', function () { - beforeEach('initialize payroll app', async () => { - await payroll.initialize(finance.address, denominationToken.address, priceFeed.address, RATE_EXPIRATION_TIME, { from: owner }) + beforeEach('initialize payroll app using USD as denomination token', async () => { + await payroll.initialize(finance.address, USD, priceFeed.address, RATE_EXPIRATION_TIME, { from: owner }) }) context('when the sender has permissions', async () => { 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 89fcce510d..e468001e33 100644 --- a/future-apps/payroll/test/contracts/Payroll_terminate_employee.test.js +++ b/future-apps/payroll/test/contracts/Payroll_terminate_employee.test.js @@ -1,18 +1,13 @@ 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, createPayrollAndPriceFeed } = require('../helpers/deploy.js')(artifacts, web3) +const { NOW, ONE_MONTH, RATE_EXPIRATION_TIME } = require('../helpers/time') +const { bn, MAX_UINT64, annualSalaryPerSecond } = require('../helpers/numbers')(web3) +const { USD, DAI_RATE, deployDAI, setTokenRate } = require('../helpers/tokens.js')(artifacts, web3) +const { deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy')(artifacts, web3) contract('Payroll employees termination', ([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 - const TWO_MONTHS = ONE_MONTH * 2 - const RATE_EXPIRATION_TIME = TWO_MONTHS - - const TOKEN_DECIMALS = 18 + let dao, payroll, payrollBase, finance, vault, priceFeed, DAI const currentTimestamp = async () => payroll.getTimestampPublic() @@ -23,7 +18,7 @@ contract('Payroll employees termination', ([owner, employee, anyone]) => { before('deploy base apps and tokens', async () => { ({ dao, finance, vault, payrollBase } = await deployContracts(owner)) - denominationToken = await deployErc20TokenAndDeposit(owner, finance, 'Denomination Token', TOKEN_DECIMALS) + DAI = await deployDAI(owner, finance) }) beforeEach('create payroll and price feed instance', async () => { @@ -32,13 +27,13 @@ contract('Payroll employees termination', ([owner, employee, anyone]) => { describe('terminateEmployee', () => { context('when it has already been initialized', function () { - beforeEach('initialize payroll app', async () => { - await payroll.initialize(finance.address, denominationToken.address, priceFeed.address, RATE_EXPIRATION_TIME, { from: owner }) + beforeEach('initialize payroll app using USD as denomination token', async () => { + await payroll.initialize(finance.address, USD, priceFeed.address, RATE_EXPIRATION_TIME, { from: owner }) }) context('when the given employee id exists', () => { let employeeId - const salary = annualSalaryPerSecond(100000, TOKEN_DECIMALS) + const salary = annualSalaryPerSecond(100000) beforeEach('add employee', async () => { const receipt = await payroll.addEmployee(employee, salary, 'Boss', await payroll.getTimestampPublic(), { from: owner }) @@ -51,8 +46,9 @@ contract('Payroll employees termination', ([owner, employee, anyone]) => { context('when the employee was not terminated', () => { let endDate - beforeEach('allowed denomination token', async () => { - await payroll.addAllowedToken(denominationToken.address, { from: owner }) + beforeEach('allow DAI and set rate', async () => { + await setTokenRate(priceFeed, USD, DAI, DAI_RATE) + await payroll.addAllowedToken(DAI.address, { from: owner }) }) context('when the given end date is in the future ', () => { @@ -80,8 +76,8 @@ contract('Payroll employees termination', ([owner, employee, anyone]) => { }) it('does not reset the owed salary nor the reimbursements of the employee', async () => { - const previousBalance = await denominationToken.balanceOf(employee) - await payroll.determineAllocation([denominationToken.address], [100], { from: employee }) + const previousDAI = await DAI.balanceOf(employee) + await payroll.determineAllocation([DAI.address], [100], { from: employee }) // Accrue some salary and extras await increaseTime(ONE_MONTH) @@ -91,25 +87,25 @@ contract('Payroll employees termination', ([owner, employee, anyone]) => { // Terminate employee and travel some time in the future await payroll.terminateEmployee(employeeId, endDate, { from }) - await increaseTime(ONE_MONTH) + await increaseTime(ONE_MONTH - 1) // to avoid expire rates // Request owed money await payroll.payday(PAYMENT_TYPES.PAYROLL, 0, { from: employee }) await payroll.payday(PAYMENT_TYPES.REIMBURSEMENT, 0, { from: employee }) await assertRevert(payroll.getEmployee(employeeId), 'PAYROLL_EMPLOYEE_DOESNT_EXIST') - const currentBalance = await denominationToken.balanceOf(employee) - const expectedCurrentBalance = previousBalance.plus(owedSalary).plus(reimbursement) - assert.equal(currentBalance.toString(), expectedCurrentBalance.toString(), 'current balance does not match') + const currentDAI = await DAI.balanceOf(employee) + const expectedDAI = previousDAI.plus(owedSalary).plus(reimbursement) + assert.equal(currentDAI.toString(), expectedDAI.toString(), 'current balance does not match') }) it('can re-add a removed employee', async () => { - await payroll.determineAllocation([denominationToken.address], [100], { from: employee }) + await payroll.determineAllocation([DAI.address], [100], { from: employee }) await increaseTime(ONE_MONTH) // Terminate employee and travel some time in the future await payroll.terminateEmployee(employeeId, endDate, { from }) - await increaseTime(ONE_MONTH) + await increaseTime(ONE_MONTH - 1) // to avoid expire rates // Request owed money await payroll.payday(PAYMENT_TYPES.PAYROLL, 0, { from: employee }) @@ -126,7 +122,7 @@ contract('Payroll employees termination', ([owner, employee, anyone]) => { assert.equal(bonus.toString(), 0, 'employee bonus does not match') assert.equal(reimbursements.toString(), 0, 'employee reimbursements does not match') assert.equal(accruedSalary.toString(), 0, 'employee accrued salary does not match') - assert.equal(date.toString(), maxUint64(), 'employee end date does not match') + assert.equal(date.toString(), MAX_UINT64, 'employee end date does not match') }) }) 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 906b7104a8..2412a74c8f 100644 --- a/future-apps/payroll/test/contracts/Payroll_token_allocations.test.js +++ b/future-apps/payroll/test/contracts/Payroll_token_allocations.test.js @@ -1,22 +1,17 @@ const { assertRevert } = require('@aragon/test-helpers/assertThrow') const { getEvents, getEventArgument } = require('../helpers/events') -const { deployErc20TokenAndDeposit, deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy.js')(artifacts, web3) +const { NOW, ONE_MONTH, RATE_EXPIRATION_TIME } = require('../helpers/time') +const { USD, deployDAI, deployTokenAndDeposit } = require('../helpers/tokens.js')(artifacts, web3) +const { deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy')(artifacts, web3) const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' 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 - const TWO_MONTHS = ONE_MONTH * 2 - const RATE_EXPIRATION_TIME = TWO_MONTHS - - const TOKEN_DECIMALS = 18 + let dao, payroll, payrollBase, finance, vault, priceFeed, DAI before('deploy base apps and tokens', async () => { ({ dao, finance, vault, payrollBase } = await deployContracts(owner)) - denominationToken = await deployErc20TokenAndDeposit(owner, finance, 'Denomination Token', TOKEN_DECIMALS) + DAI = await deployDAI(owner, finance) }) beforeEach('create payroll and price feed instance', async () => { @@ -27,15 +22,15 @@ contract('Payroll token allocations', ([owner, employee, anyone]) => { const tokenAddresses = [] before('deploy some tokens', async () => { - 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) + const token1 = await deployTokenAndDeposit(owner, finance, 'Token 1', 14) + const token2 = await deployTokenAndDeposit(owner, finance, 'Token 2', 14) + const token3 = await deployTokenAndDeposit(owner, finance, 'Token 3', 14) tokenAddresses.push(token1.address, token2.address, token3.address) }) context('when it has already been initialized', function () { - beforeEach('initialize payroll app', async () => { - await payroll.initialize(finance.address, denominationToken.address, priceFeed.address, RATE_EXPIRATION_TIME, { from: owner }) + beforeEach('initialize payroll app using USD as denomination token', async () => { + await payroll.initialize(finance.address, USD, priceFeed.address, RATE_EXPIRATION_TIME, { from: owner }) }) beforeEach('allow multiple tokens', async () => { @@ -80,7 +75,7 @@ contract('Payroll token allocations', ([owner, employee, anyone]) => { let token beforeEach('submit previous allocation', async () => { - token = await deployErc20TokenAndDeposit(owner, finance, 'Previous Token', 18) + token = await deployTokenAndDeposit(owner, finance, 'Previous Token', 18) await payroll.addAllowedToken(token.address, { from: owner }) await payroll.determineAllocation([token.address], [100], { from }) @@ -138,7 +133,7 @@ contract('Payroll token allocations', ([owner, employee, anyone]) => { let notAllowedToken beforeEach('deploy new token', async () => { - notAllowedToken = await deployErc20TokenAndDeposit(owner, finance, 'Not-allowed token', 14) + notAllowedToken = await deployTokenAndDeposit(owner, finance, 'Not-allowed token', 14) }) it('reverts', async () => { @@ -201,8 +196,8 @@ contract('Payroll token allocations', ([owner, employee, anyone]) => { describe('getAllocation', () => { context('when it has already been initialized', function () { - beforeEach('initialize payroll app', async () => { - await payroll.initialize(finance.address, denominationToken.address, priceFeed.address, RATE_EXPIRATION_TIME, { from: owner }) + beforeEach('initialize payroll app using USD as denomination token', async () => { + await payroll.initialize(finance.address, USD, priceFeed.address, RATE_EXPIRATION_TIME, { from: owner }) }) context('when the employee exists', () => { @@ -217,23 +212,23 @@ contract('Payroll token allocations', ([owner, employee, anyone]) => { context('when the given token is not the zero address', () => { context('when the given token was allowed', () => { beforeEach('allow denomination token', async () => { - await payroll.addAllowedToken(denominationToken.address, { from: owner }) + await payroll.addAllowedToken(DAI.address, { from: owner }) }) context('when the given token was picked by the employee', () => { beforeEach('determine allocation', async () => { - await payroll.determineAllocation([denominationToken.address], [100], { from: employee }) + await payroll.determineAllocation([DAI.address], [100], { from: employee }) }) it('tells its corresponding allocation', async () => { - const allocation = await payroll.getAllocation(employeeId, denominationToken.address) + const allocation = await payroll.getAllocation(employeeId, DAI.address) assert.equal(allocation.toString(), 100, 'token allocation does not match') }) }) context('when the given token was not picked by the employee', () => { it('returns 0', async () => { - const allocation = await payroll.getAllocation(employeeId, denominationToken.address) + const allocation = await payroll.getAllocation(employeeId, DAI.address) assert.equal(allocation.toString(), 0, 'token allocation should be zero') }) }) @@ -241,7 +236,7 @@ contract('Payroll token allocations', ([owner, employee, anyone]) => { context('when the given token was not allowed', () => { it('returns 0', async () => { - const allocation = await payroll.getAllocation(employeeId, denominationToken.address) + const allocation = await payroll.getAllocation(employeeId, DAI.address) assert.equal(allocation.toString(), 0, 'token allocation should be zero') }) }) @@ -301,7 +296,7 @@ contract('Payroll token allocations', ([owner, employee, anyone]) => { const employeeId = 0 it('reverts', async () => { - await assertRevert(payroll.getAllocation(employeeId, denominationToken.address), 'PAYROLL_EMPLOYEE_DOESNT_EXIST') + await assertRevert(payroll.getAllocation(employeeId, DAI.address), 'PAYROLL_EMPLOYEE_DOESNT_EXIST') }) }) }) @@ -310,7 +305,7 @@ contract('Payroll token allocations', ([owner, employee, anyone]) => { const employeeId = 0 it('reverts', async () => { - await assertRevert(payroll.getAllocation(employeeId, denominationToken.address), 'PAYROLL_EMPLOYEE_DOESNT_EXIST') + await assertRevert(payroll.getAllocation(employeeId, DAI.address), 'PAYROLL_EMPLOYEE_DOESNT_EXIST') }) }) }) diff --git a/future-apps/payroll/test/helpers/deploy.js b/future-apps/payroll/test/helpers/deploy.js index 68b8c5d7a9..f1083399ec 100644 --- a/future-apps/payroll/test/helpers/deploy.js +++ b/future-apps/payroll/test/helpers/deploy.js @@ -1,6 +1,8 @@ module.exports = (artifacts, web3) => { const { bigExp } = require('./numbers')(web3) const { getEventArgument } = require('./events') + const { SECONDS_IN_A_YEAR } = require('./time') + const getContract = name => artifacts.require(name) const ACL = getContract('ACL') @@ -59,7 +61,6 @@ module.exports = (artifacts, web3) => { await acl.createPermission(finance.address, vault.address, TRANSFER_ROLE, owner, { from: owner }) await vault.initialize() - const SECONDS_IN_A_YEAR = 31557600 // 365.25 days await finance.initialize(vault.address, SECONDS_IN_A_YEAR) // more than one day const payrollBase = await Payroll.new() diff --git a/future-apps/payroll/test/helpers/numbers.js b/future-apps/payroll/test/helpers/numbers.js index ff2b331a20..f672d96bef 100644 --- a/future-apps/payroll/test/helpers/numbers.js +++ b/future-apps/payroll/test/helpers/numbers.js @@ -1,20 +1,21 @@ -const SECONDS_IN_A_YEAR = 31557600 // 365.25 days +const { SECONDS_IN_A_YEAR } = require('./time') module.exports = web3 => { const bn = x => new web3.BigNumber(x) const bigExp = (x, y) => bn(x).mul(bn(10).pow(bn(y))) - const maxUint = (e) => bn(2).pow(bn(e)).sub(bn(1)) - const maxUint64 = () => maxUint(64) - const maxUint256 = () => maxUint(256) + const annualSalaryPerSecond = (amount, decimals = 18) => bigExp(amount, decimals).dividedToIntegerBy(SECONDS_IN_A_YEAR) - const annualSalaryPerSecond = (amount, decimals) => bigExp(amount, decimals).dividedToIntegerBy(SECONDS_IN_A_YEAR) + const ONE = bigExp(1, 18) + const MAX_UINT64 = maxUint(64) + const MAX_UINT256 = maxUint(256) return { bn, bigExp, - maxUint64, - maxUint256, - annualSalaryPerSecond + annualSalaryPerSecond, + ONE, + MAX_UINT64, + MAX_UINT256, } } diff --git a/future-apps/payroll/test/helpers/set_token_rates.js b/future-apps/payroll/test/helpers/set_token_rates.js deleted file mode 100644 index 61c7f3b41c..0000000000 --- a/future-apps/payroll/test/helpers/set_token_rates.js +++ /dev/null @@ -1,23 +0,0 @@ -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 quotes = tokens.map(token => typeof(token) === 'object' ? token.address : token) - const bases = 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/future-apps/payroll/test/helpers/time.js b/future-apps/payroll/test/helpers/time.js new file mode 100644 index 0000000000..7b4c2df9b0 --- /dev/null +++ b/future-apps/payroll/test/helpers/time.js @@ -0,0 +1,18 @@ +const NOW = 1553703809 // random fixed timestamp in seconds +const ONE_MINUTE = 60 +const TWO_MINUTES = ONE_MINUTE * 2 +const ONE_MONTH = 60 * 60 * 24 * 31 +const TWO_MONTHS = ONE_MONTH * 2 +const SECONDS_IN_A_YEAR = 31557600 // 365.25 days + +const RATE_EXPIRATION_TIME = TWO_MONTHS + +module.exports = { + NOW, + ONE_MINUTE, + TWO_MINUTES, + ONE_MONTH, + TWO_MONTHS, + RATE_EXPIRATION_TIME, + SECONDS_IN_A_YEAR +} diff --git a/future-apps/payroll/test/helpers/tokens.js b/future-apps/payroll/test/helpers/tokens.js new file mode 100644 index 0000000000..9066555cf4 --- /dev/null +++ b/future-apps/payroll/test/helpers/tokens.js @@ -0,0 +1,62 @@ +const SIG = '00'.repeat(65) // sig full of 0s + +const ETH = '0x0000000000000000000000000000000000000000' +const USD = '0xFFFfFfffffFFFFFFFfffFFFFFffffFfFfFAAaCbB' // USD identifier: https://github.com/aragon/ppf#tickers-and-token-addresses + +module.exports = (artifacts, web3) => { + const { ONE, bn, bigExp } = require('./numbers')(web3) + + const formatRate = n => bn(n.toFixed(18)).times(ONE) + + const ETH_RATE = bn(20) // 1 ETH = 20 USD + const DAI_RATE = bn(1) // 1 DAI = 1 USD + const ANT_RATE = bn(0.5) // 1 ANT = 0.5 USD + + const deployANT = async (sender, finance) => deployTokenAndDeposit(sender, finance, 'ANT') + const deployDAI = async (sender, finance) => deployTokenAndDeposit(sender, finance, 'DAI') + + async function deployTokenAndDeposit(sender, finance, name = 'ERC20Token', decimals = 18) { + const MiniMeToken = artifacts.require('MiniMeToken') + const token = await MiniMeToken.new('0x0', '0x0', 0, name, decimals, 'E20', true) // dummy parameters for minime + const amount = bigExp(1e18, decimals) + await token.generateTokens(sender, amount) + await token.approve(finance.address, amount, { from: sender }) + await finance.deposit(token.address, amount, `Initial ${name} deposit`, { from: sender }) + return token + } + + async function setTokenRate(feed, denominationToken, token, rate, when = undefined) { + if (!when) when = await feed.getTimestampPublic() + + const base = typeof(token) === 'object' ? token.address : token + const quote = typeof(denominationToken) === 'object' ? denominationToken.address : denominationToken + const xrt = formatRate(rate) + + return feed.update(base, quote, xrt, when, SIG) + } + + 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) + } + + return { + ETH, + USD, + ETH_RATE, + DAI_RATE, + ANT_RATE, + deployANT, + deployDAI, + deployTokenAndDeposit, + setTokenRate, + setTokenRates + } +} From 065139ca7b3442593bef3b6e68034dd69ce05baf Mon Sep 17 00:00:00 2001 From: Facundo Spagnuolo Date: Thu, 25 Apr 2019 23:23:36 -0300 Subject: [PATCH 3/6] Payroll: Add more rates unit tests with different denomination tokens --- .../test/contracts/Payroll_rates.test.js | 372 +++++++++++++++++- 1 file changed, 353 insertions(+), 19 deletions(-) diff --git a/future-apps/payroll/test/contracts/Payroll_rates.test.js b/future-apps/payroll/test/contracts/Payroll_rates.test.js index f75f88f9f3..7d7eecdc20 100644 --- a/future-apps/payroll/test/contracts/Payroll_rates.test.js +++ b/future-apps/payroll/test/contracts/Payroll_rates.test.js @@ -1,12 +1,11 @@ const PAYMENT_TYPES = require('../helpers/payment_types') -const { bn } = require('../helpers/numbers')(web3) -const { getEventArgument } = require('../helpers/events') +const { bigExp } = require('../helpers/numbers')(web3) const { NOW, TWO_MINUTES, RATE_EXPIRATION_TIME } = require('../helpers/time') const { deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy')(artifacts, web3) const { USD, ETH, ETH_RATE, deployDAI, DAI_RATE, deployANT, ANT_RATE, setTokenRate } = require('../helpers/tokens.js')(artifacts, web3) -contract('Payroll rates handling,', ([owner, employee]) => { - let dao, payroll, payrollBase, finance, vault, priceFeed, employeeId, DAI, ANT +contract('Payroll rates handling,', ([owner, employee, anyone]) => { + let dao, payroll, payrollBase, finance, vault, priceFeed, DAI, ANT const increaseTime = async seconds => { await payroll.mockIncreaseTime(seconds) @@ -18,15 +17,15 @@ contract('Payroll rates handling,', ([owner, employee]) => { DAI = await deployDAI(owner, finance) ANT = await deployANT(owner, finance) - await finance.deposit(ETH, bn(50, 18), 'Initial ETH deposit', { from: owner, value: bn(50, 18) }) + await finance.deposit(ETH, bigExp(50, 18), 'Initial ETH deposit', { from: anyone, value: bigExp(50, 18) }) }) - beforeEach('initialize payroll app with USD as denomination token', async () => { - ({ payroll, priceFeed } = await createPayrollAndPriceFeed(dao, payrollBase, owner, NOW)) - await payroll.initialize(finance.address, USD, priceFeed.address, RATE_EXPIRATION_TIME, { from: owner }) - }) + context('when the denomination token is USD', async () => { + beforeEach('initialize payroll app', async () => { + ({ payroll, priceFeed } = await createPayrollAndPriceFeed(dao, payrollBase, owner, NOW)) + await payroll.initialize(finance.address, USD, priceFeed.address, RATE_EXPIRATION_TIME, { from: owner }) + }) - describe('rates', () => { beforeEach('set rates and allow tokens', async () => { await setTokenRate(priceFeed, ETH, USD, ETH_RATE) await setTokenRate(priceFeed, DAI, USD, DAI_RATE) @@ -38,12 +37,338 @@ contract('Payroll rates handling,', ([owner, employee]) => { }) beforeEach('add employee with salary 1 USD per second', async () => { - const salary = 1 - const receipt = await payroll.addEmployee(employee, salary, 'Boss', await payroll.getTimestampPublic()) - employeeId = getEventArgument(receipt, 'AddEmployee', 'employeeId') + const salary = bigExp(1, 18) + await payroll.addEmployee(employee, salary, 'Boss', await payroll.getTimestampPublic()) + }) + + beforeEach('accrue two minutes of salary', async () => { + await increaseTime(TWO_MINUTES) + }) + + context('when the employee requests only ETH', () => { + beforeEach('set token allocation', async () => { + await payroll.determineAllocation([ETH], [100], { from: employee }) + }) + + it('receives the expected amount of ETH', async () => { + const previousETH = await web3.eth.getBalance(employee) + const previousDAI = await DAI.balanceOf(employee) + const previousANT = await ANT.balanceOf(employee) + + const { tx, receipt } = await payroll.payday(PAYMENT_TYPES.PAYROLL, 0, { from: employee }) + const { gasPrice } = await web3.eth.getTransaction(tx) + const txCost = gasPrice.mul(receipt.gasUsed) + + // expected an income of 6 ETH since we accrued 2 minutes of salary at 1 USD per second, and the ETH rate is 20 USD + const currentETH = await web3.eth.getBalance(employee) + assert.equal(currentETH.minus(previousETH).plus(txCost).toString(), bigExp(6, 18).toString(), 'expected current ETH amount does not match') + + // no DAI income expected + const currentDAI = await DAI.balanceOf(employee) + assert.equal(currentDAI.toString(), previousDAI.toString(), 'expected current DAI amount does not match') + + // no ANT income expected + const currentANT = await ANT.balanceOf(employee) + assert.equal(currentANT.toString(), previousANT.toString(), 'expected current ANT amount does not match') + }) + }) + + context('when the employee requests only ANT', () => { + beforeEach('set token allocation', async () => { + await payroll.determineAllocation([ANT.address], [100], { from: employee }) + }) + + it('receives the expected amount of ANT', async () => { + const previousETH = await web3.eth.getBalance(employee) + const previousDAI = await DAI.balanceOf(employee) + const previousANT = await ANT.balanceOf(employee) + + const { tx, receipt } = await payroll.payday(PAYMENT_TYPES.PAYROLL, 0, { from: employee }) + const { gasPrice } = await web3.eth.getTransaction(tx) + const txCost = gasPrice.mul(receipt.gasUsed) + + // no ETH income expected + const currentETH = await web3.eth.getBalance(employee) + assert.equal(currentETH.plus(txCost).toString(), previousETH.toString(), 'expected current ETH amount does not match') + + // no DAI income expected + const currentDAI = await DAI.balanceOf(employee) + assert.equal(currentDAI.toString(), previousDAI.toString(), 'expected current DAI amount does not match') + + // expected an income of 240 ANT since we accrued 2 minutes of salary at 1 USD per second, and the ANT rate is 0.5 USD + const currentANT = await ANT.balanceOf(employee) + assert.equal(currentANT.minus(previousANT).toString(), bigExp(240, 18).toString(), 'expected current ANT amount does not match') + }) + }) + + context('when the employee requests multiple tokens', () => { + beforeEach('set token allocations', async () => { + await payroll.determineAllocation([ETH, DAI.address, ANT.address], [50, 25, 25], { from: employee }) + }) + + it('receives the expected amount of tokens', async () => { + const previousETH = await web3.eth.getBalance(employee) + const previousDAI = await DAI.balanceOf(employee) + const previousANT = await ANT.balanceOf(employee) + + const { tx, receipt } = await payroll.payday(PAYMENT_TYPES.PAYROLL, 0, { from: employee }) + const { gasPrice } = await web3.eth.getTransaction(tx) + const txCost = gasPrice.mul(receipt.gasUsed) + + // expected an income of 3 ETH having 50% allocated, since we accrued 2 minutes of salary at 1 USD per second, and the ETH rate is 20 USD + const currentETH = await web3.eth.getBalance(employee) + assert.equal(currentETH.minus(previousETH).plus(txCost).toString(), bigExp(3, 18).toString(), 'expected current ETH amount does not match') + + // expected an income of 30 DAI having 25% allocated, since we accrued 2 minutes of salary at 1 USD per second, and the DAI rate is 1 USD + const currentDAI = await DAI.balanceOf(employee) + assert.equal(currentDAI.minus(previousDAI).toString(), bigExp(30, 18).toString(), 'expected current DAI amount does not match') + + // expected an income of 60 ANT having 25% allocated, since we accrued 2 minutes of salary at 1 USD per second, and the ANT rate is 0.5 USD + const currentANT = await ANT.balanceOf(employee) + assert.equal(currentANT.minus(previousANT).toString(), bigExp(60, 18).toString(), 'expected current ANT amount does not match') + }) + }) + }) + + context('when the denomination token is ETH', async () => { + beforeEach('initialize payroll app', async () => { + ({ payroll, priceFeed } = await createPayrollAndPriceFeed(dao, payrollBase, owner, NOW)) + await payroll.initialize(finance.address, ETH, priceFeed.address, RATE_EXPIRATION_TIME, { from: owner }) + }) + + beforeEach('set rates and allow tokens', async () => { + await setTokenRate(priceFeed, DAI, ETH, DAI_RATE.div(ETH_RATE)) // 0.050 ETH + await setTokenRate(priceFeed, ANT, ETH, ANT_RATE.div(ETH_RATE)) // 0.025 ETH + + await payroll.addAllowedToken(ETH, { from: owner }) + await payroll.addAllowedToken(DAI.address, { from: owner }) + await payroll.addAllowedToken(ANT.address, { from: owner }) + }) + + beforeEach('add employee with salary 0.1 ETH per second', async () => { + const salary = bigExp(0.1, 18) + await payroll.addEmployee(employee, salary, 'Boss', await payroll.getTimestampPublic()) + }) + + beforeEach('accrue two minutes of salary', async () => { + await increaseTime(TWO_MINUTES) + }) + + context('when the employee requests only ETH', () => { + beforeEach('set token allocation', async () => { + await payroll.determineAllocation([ETH], [100], { from: employee }) + }) + + it('receives the expected amount of ETH', async () => { + const previousETH = await web3.eth.getBalance(employee) + const previousDAI = await DAI.balanceOf(employee) + const previousANT = await ANT.balanceOf(employee) + + const { tx, receipt } = await payroll.payday(PAYMENT_TYPES.PAYROLL, 0, { from: employee }) + const { gasPrice } = await web3.eth.getTransaction(tx) + const txCost = gasPrice.mul(receipt.gasUsed) + + // expected an income of 12 ETH since we accrued 2 minutes of salary at 0.1 ETH per second, and the denomination token is ETH + const currentETH = await web3.eth.getBalance(employee) + assert.equal(currentETH.minus(previousETH).plus(txCost).toString(), bigExp(12, 18).toString(), 'expected current ETH amount does not match') + + // no DAI income expected + const currentDAI = await DAI.balanceOf(employee) + assert.equal(currentDAI.toString(), previousDAI.toString(), 'expected current DAI amount does not match') + + // no ANT income expected + const currentANT = await ANT.balanceOf(employee) + assert.equal(currentANT.toString(), previousANT.toString(), 'expected current ANT amount does not match') + }) + }) + + context('when the employee requests only ANT', () => { + beforeEach('set token allocation', async () => { + await payroll.determineAllocation([ANT.address], [100], { from: employee }) + }) + + it('receives the expected amount of ANT', async () => { + const previousETH = await web3.eth.getBalance(employee) + const previousDAI = await DAI.balanceOf(employee) + const previousANT = await ANT.balanceOf(employee) + + const { tx, receipt } = await payroll.payday(PAYMENT_TYPES.PAYROLL, 0, { from: employee }) + const { gasPrice } = await web3.eth.getTransaction(tx) + const txCost = gasPrice.mul(receipt.gasUsed) + + // no ETH income expected + const currentETH = await web3.eth.getBalance(employee) + assert.equal(currentETH.plus(txCost).toString(), previousETH.toString(), 'expected current ETH amount does not match') + + // no DAI income expected + const currentDAI = await DAI.balanceOf(employee) + assert.equal(currentDAI.toString(), previousDAI.toString(), 'expected current DAI amount does not match') + + // expected an income of 480 ANT since we accrued 2 minutes of salary at 0.1 ETH per second, and the ANT rate is 0.025 ETH + const currentANT = await ANT.balanceOf(employee) + assert.equal(currentANT.minus(previousANT).toString(), bigExp(480, 18).toString(), 'expected current ANT amount does not match') + }) + }) + + context('when the employee requests multiple tokens', () => { + beforeEach('set token allocations', async () => { + await payroll.determineAllocation([ETH, DAI.address, ANT.address], [50, 25, 25], { from: employee }) + }) + + it('receives the expected amount of tokens', async () => { + const previousETH = await web3.eth.getBalance(employee) + const previousDAI = await DAI.balanceOf(employee) + const previousANT = await ANT.balanceOf(employee) + + const { tx, receipt } = await payroll.payday(PAYMENT_TYPES.PAYROLL, 0, { from: employee }) + const { gasPrice } = await web3.eth.getTransaction(tx) + const txCost = gasPrice.mul(receipt.gasUsed) + + // expected an income of 6 ETH having 50% allocated, since we accrued 2 minutes of salary at 0.1 ETH per second, and the denomination token is ETH + const currentETH = await web3.eth.getBalance(employee) + assert.equal(currentETH.minus(previousETH).plus(txCost).toString(), bigExp(6, 18).toString(), 'expected current ETH amount does not match') + + // expected an income of 60 DAI having 25% allocated, since we accrued 2 minutes of salary at 0.1 ETH per second, and the DAI rate is 0.05 ETH + const currentDAI = await DAI.balanceOf(employee) + assert.equal(currentDAI.minus(previousDAI).toString(), bigExp(60, 18).toString(), 'expected current DAI amount does not match') + + // expected an income of 120 ANT having 25% allocated, since we accrued 2 minutes of salary at 0.1 ETH per second, and the ANT rate is 0.025 ETH + const currentANT = await ANT.balanceOf(employee) + assert.equal(currentANT.minus(previousANT).toString(), bigExp(120, 18).toString(), 'expected current ANT amount does not match') + }) + }) + }) + + context('when the denomination token is DAI', async () => { + beforeEach('initialize payroll app', async () => { + ({ payroll, priceFeed } = await createPayrollAndPriceFeed(dao, payrollBase, owner, NOW)) + await payroll.initialize(finance.address, DAI.address, priceFeed.address, RATE_EXPIRATION_TIME, { from: owner }) + }) + + beforeEach('set rates and allow tokens', async () => { + await setTokenRate(priceFeed, ETH, DAI, ETH_RATE.div(DAI_RATE)) // 20 DAI + await setTokenRate(priceFeed, ANT, DAI, ANT_RATE.div(DAI_RATE)) // 0.5 DAI + + await payroll.addAllowedToken(ETH, { from: owner }) + await payroll.addAllowedToken(DAI.address, { from: owner }) + await payroll.addAllowedToken(ANT.address, { from: owner }) + }) + + beforeEach('add employee with salary 1 DAI per second', async () => { + const salary = bigExp(1, 18) + await payroll.addEmployee(employee, salary, 'Boss', await payroll.getTimestampPublic()) + }) + + beforeEach('accrue two minutes of salary', async () => { + await increaseTime(TWO_MINUTES) + }) + + context('when the employee requests only ETH', () => { + beforeEach('set token allocation', async () => { + await payroll.determineAllocation([ETH], [100], { from: employee }) + }) + + it('receives the expected amount of ETH', async () => { + const previousETH = await web3.eth.getBalance(employee) + const previousDAI = await DAI.balanceOf(employee) + const previousANT = await ANT.balanceOf(employee) + + const { tx, receipt } = await payroll.payday(PAYMENT_TYPES.PAYROLL, 0, { from: employee }) + const { gasPrice } = await web3.eth.getTransaction(tx) + const txCost = gasPrice.mul(receipt.gasUsed) + + // expected an income of 6 ETH since we accrued 2 minutes of salary at 1 DAI per second, and the ETH rate is 20 DAI + const currentETH = await web3.eth.getBalance(employee) + assert.equal(currentETH.minus(previousETH).plus(txCost).toString(), bigExp(6, 18).toString(), 'expected current ETH amount does not match') + + // no DAI income expected + const currentDAI = await DAI.balanceOf(employee) + assert.equal(currentDAI.toString(), previousDAI.toString(), 'expected current DAI amount does not match') + + // no ANT income expected + const currentANT = await ANT.balanceOf(employee) + assert.equal(currentANT.toString(), previousANT.toString(), 'expected current ANT amount does not match') + }) + }) + + context('when the employee requests only ANT', () => { + beforeEach('set token allocation', async () => { + await payroll.determineAllocation([ANT.address], [100], { from: employee }) + }) + + it('receives the expected amount of ANT', async () => { + const previousETH = await web3.eth.getBalance(employee) + const previousDAI = await DAI.balanceOf(employee) + const previousANT = await ANT.balanceOf(employee) + + const { tx, receipt } = await payroll.payday(PAYMENT_TYPES.PAYROLL, 0, { from: employee }) + const { gasPrice } = await web3.eth.getTransaction(tx) + const txCost = gasPrice.mul(receipt.gasUsed) + + // no ETH income expected + const currentETH = await web3.eth.getBalance(employee) + assert.equal(currentETH.plus(txCost).toString(), previousETH.toString(), 'expected current ETH amount does not match') + + // no DAI income expected + const currentDAI = await DAI.balanceOf(employee) + assert.equal(currentDAI.toString(), previousDAI.toString(), 'expected current DAI amount does not match') + + // expected an income of 240 ANT since we accrued 2 minutes of salary at 1 DAI per second, and the ANT rate is 0.5 DAI + const currentANT = await ANT.balanceOf(employee) + assert.equal(currentANT.minus(previousANT).toString(), bigExp(240, 18).toString(), 'expected current ANT amount does not match') + }) + }) + + context('when the employee requests multiple tokens', () => { + beforeEach('set token allocations', async () => { + await payroll.determineAllocation([ETH, DAI.address, ANT.address], [50, 25, 25], { from: employee }) + }) + + it('receives the expected amount of tokens', async () => { + const previousETH = await web3.eth.getBalance(employee) + const previousDAI = await DAI.balanceOf(employee) + const previousANT = await ANT.balanceOf(employee) + + const { tx, receipt } = await payroll.payday(PAYMENT_TYPES.PAYROLL, 0, { from: employee }) + const { gasPrice } = await web3.eth.getTransaction(tx) + const txCost = gasPrice.mul(receipt.gasUsed) + + // expected an income of 3 ETH having 50% allocated, since we accrued 2 minutes of salary at 1 DAI per second, and the ETH rate is 20 DAI + const currentETH = await web3.eth.getBalance(employee) + assert.equal(currentETH.minus(previousETH).plus(txCost).toString(), bigExp(3, 18).toString(), 'expected current ETH amount does not match') + + // expected an income of 30 DAI having 25% allocated, since we accrued 2 minutes of salary at 1 DAI per second, and the denomination token is DAI + const currentDAI = await DAI.balanceOf(employee) + assert.equal(currentDAI.minus(previousDAI).toString(), bigExp(30, 18).toString(), 'expected current DAI amount does not match') + + // expected an income of 60 ANT having 25% allocated, since we accrued 2 minutes of salary at 1 DAI per second, and the ANT rate is 0.5 DAI + const currentANT = await ANT.balanceOf(employee) + assert.equal(currentANT.minus(previousANT).toString(), bigExp(60, 18).toString(), 'expected current ANT amount does not match') + }) + }) + }) + + context('when the denomination token is ANT', async () => { + beforeEach('initialize payroll app', async () => { + ({ payroll, priceFeed } = await createPayrollAndPriceFeed(dao, payrollBase, owner, NOW)) + await payroll.initialize(finance.address, ANT.address, priceFeed.address, RATE_EXPIRATION_TIME, { from: owner }) + }) + + beforeEach('set rates and allow tokens', async () => { + await setTokenRate(priceFeed, ETH, ANT, ETH_RATE.div(ANT_RATE)) // 40 ANT + await setTokenRate(priceFeed, DAI, ANT, DAI_RATE.div(ANT_RATE)) // 2 ANT + + await payroll.addAllowedToken(ETH, { from: owner }) + await payroll.addAllowedToken(DAI.address, { from: owner }) + await payroll.addAllowedToken(ANT.address, { from: owner }) + }) + + beforeEach('add employee with salary 1 ANT per second', async () => { + const salary = bigExp(1, 18) + await payroll.addEmployee(employee, salary, 'Boss', await payroll.getTimestampPublic()) }) - beforeEach('increase time two minutes', async () => { + beforeEach('accrue two minutes of salary', async () => { await increaseTime(TWO_MINUTES) }) @@ -61,12 +386,15 @@ contract('Payroll rates handling,', ([owner, employee]) => { const { gasPrice } = await web3.eth.getTransaction(tx) const txCost = gasPrice.mul(receipt.gasUsed) + // expected an income of 3 ETH since we accrued 2 minutes of salary at 1 ANT per second, and the ETH rate is 40 ANT const currentETH = await web3.eth.getBalance(employee) - assert.equal(currentETH.minus(previousETH).plus(txCost).toString(), 6, 'expected current ETH amount does not match') + assert.equal(currentETH.minus(previousETH).plus(txCost).toString(), bigExp(3, 18).toString(), 'expected current ETH amount does not match') + // no DAI income expected const currentDAI = await DAI.balanceOf(employee) assert.equal(currentDAI.toString(), previousDAI.toString(), 'expected current DAI amount does not match') + // no ANT income expected const currentANT = await ANT.balanceOf(employee) assert.equal(currentANT.toString(), previousANT.toString(), 'expected current ANT amount does not match') }) @@ -86,14 +414,17 @@ contract('Payroll rates handling,', ([owner, employee]) => { const { gasPrice } = await web3.eth.getTransaction(tx) const txCost = gasPrice.mul(receipt.gasUsed) + // no ETH income expected const currentETH = await web3.eth.getBalance(employee) assert.equal(currentETH.plus(txCost).toString(), previousETH.toString(), 'expected current ETH amount does not match') + // no DAI income expected const currentDAI = await DAI.balanceOf(employee) assert.equal(currentDAI.toString(), previousDAI.toString(), 'expected current DAI amount does not match') + // expected an income of 120 ANT since we accrued 2 minutes of salary at 1 ANT per second, and the denomination token is ANT const currentANT = await ANT.balanceOf(employee) - assert.equal(currentANT.minus(previousANT).toString(), 240, 'expected current ANT amount does not match') + assert.equal(currentANT.minus(previousANT).toString(), bigExp(120, 18).toString(), 'expected current ANT amount does not match') }) }) @@ -111,14 +442,17 @@ contract('Payroll rates handling,', ([owner, employee]) => { const { gasPrice } = await web3.eth.getTransaction(tx) const txCost = gasPrice.mul(receipt.gasUsed) + // expected an income of 1.5 ETH having 50% allocated, since we accrued 2 minutes of salary at 1 ANT per second, and the ETH rate is 40 ANT const currentETH = await web3.eth.getBalance(employee) - assert.equal(currentETH.minus(previousETH).plus(txCost).toString(), 3, 'expected current ETH amount does not match') + assert.equal(currentETH.minus(previousETH).plus(txCost).toString(), bigExp(1.5, 18).toString(), 'expected current ETH amount does not match') + // expected an income of 15 DAI having 25% allocated, since we accrued 2 minutes of salary at 1 ANT per second, and the DAI rate is 2 ANT const currentDAI = await DAI.balanceOf(employee) - assert.equal(currentDAI.minus(previousDAI).toString(), 30, 'expected current DAI amount does not match') + assert.equal(currentDAI.minus(previousDAI).toString(), bigExp(15, 18).toString(), 'expected current DAI amount does not match') + // expected an income of 30 ANT having 25% allocated, since we accrued 2 minutes of salary at 1 ANT per second, and the denomination token is ANT const currentANT = await ANT.balanceOf(employee) - assert.equal(currentANT.minus(previousANT).toString(), 60, 'expected current ANT amount does not match') + assert.equal(currentANT.minus(previousANT).toString(), bigExp(30, 18).toString(), 'expected current ANT amount does not match') }) }) }) From a9f3a0701900cbbd9b72faed4be48740b5b71583 Mon Sep 17 00:00:00 2001 From: Facundo Spagnuolo Date: Fri, 26 Apr 2019 03:14:10 -0300 Subject: [PATCH 4/6] Payroll: Fix tokens transfer formula --- future-apps/payroll/contracts/Payroll.sol | 6 +- .../contracts/Payroll_add_employee.test.js | 4 +- .../contracts/Payroll_allowed_tokens.test.js | 7 +- .../test/contracts/Payroll_bonuses.test.js | 64 +++++----- .../test/contracts/Payroll_forwarding.test.js | 7 +- .../test/contracts/Payroll_gas_costs.test.js | 2 +- .../contracts/Payroll_get_employee.test.js | 17 ++- .../test/contracts/Payroll_initialize.test.js | 2 +- .../contracts/Payroll_modify_employee.test.js | 28 ++-- .../test/contracts/Payroll_payday.test.js | 120 +++++++++--------- .../test/contracts/Payroll_rates.test.js | 29 +++-- .../test/contracts/Payroll_reentrancy.test.js | 4 +- .../contracts/Payroll_reimbursements.test.js | 88 ++++++------- .../Payroll_terminate_employee.test.js | 9 +- .../Payroll_token_allocations.test.js | 7 +- future-apps/payroll/test/helpers/tokens.js | 15 +-- 16 files changed, 211 insertions(+), 198 deletions(-) diff --git a/future-apps/payroll/contracts/Payroll.sol b/future-apps/payroll/contracts/Payroll.sol index bd20d595cd..b53bbf05a4 100644 --- a/future-apps/payroll/contracts/Payroll.sol +++ b/future-apps/payroll/contracts/Payroll.sol @@ -692,12 +692,10 @@ contract Payroll is EtherTokenConstant, IForwarder, IsContract, AragonApp { address token = allowedTokensArray[i]; uint256 tokenAllocation = employee.allocation[token]; if (tokenAllocation != uint256(0)) { - uint128 exchangeRate = _getExchangeRate(token); + uint256 exchangeRate = uint256(_getExchangeRate(token)); require(exchangeRate > 0, ERROR_EXCHANGE_RATE_ZERO); + uint256 tokenAmount = _totalAmount.mul(tokenAllocation).div(exchangeRate).mul(ONE / 100); // Salary converted to token and applied allocation percentage - uint256 tokenAmount = _totalAmount.mul(exchangeRate).mul(tokenAllocation); - // Divide by 100 for the allocation and by ONE for the exchange rate - tokenAmount = tokenAmount / (100 * ONE); finance.newImmediatePayment(token, employeeAddress, tokenAmount, paymentReference); emit SendPayment(employeeAddress, token, tokenAmount, paymentReference); 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 9101ce2450..de8801bcdd 100644 --- a/future-apps/payroll/test/contracts/Payroll_add_employee.test.js +++ b/future-apps/payroll/test/contracts/Payroll_add_employee.test.js @@ -1,8 +1,8 @@ const { assertRevert } = require('@aragon/test-helpers/assertThrow') const { getEvents, getEventArgument } = require('../helpers/events') -const { MAX_UINT64, annualSalaryPerSecond } = require('../helpers/numbers')(web3) -const { USD, deployDAI } = require('../helpers/tokens.js')(artifacts, web3) +const { USD, deployDAI } = require('../helpers/tokens')(artifacts, web3) const { NOW, TWO_MONTHS, RATE_EXPIRATION_TIME } = require('../helpers/time') +const { MAX_UINT64, annualSalaryPerSecond } = require('../helpers/numbers')(web3) const { deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy')(artifacts, web3) const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' 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 4587770d58..d18a1f5d6a 100644 --- a/future-apps/payroll/test/contracts/Payroll_allowed_tokens.test.js +++ b/future-apps/payroll/test/contracts/Payroll_allowed_tokens.test.js @@ -1,9 +1,10 @@ const PAYMENT_TYPES = require('../helpers/payment_types') const { getEvent } = require('../helpers/events') const { assertRevert } = require('@aragon/test-helpers/assertThrow') +const { annualSalaryPerSecond } = require('../helpers/numbers')(web3) const { NOW, ONE_MONTH, RATE_EXPIRATION_TIME } = require('../helpers/time') const { deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy')(artifacts, web3) -const { USD, deployDAI, deployTokenAndDeposit, setTokenRates } = require('../helpers/tokens.js')(artifacts, web3) +const { USD, deployDAI, deployTokenAndDeposit, setTokenRates, formatRate } = require('../helpers/tokens')(artifacts, web3) const MAX_GAS_USED = 6.5e6 const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' @@ -80,10 +81,10 @@ contract('Payroll allowed tokens,', ([owner, employee, anyone]) => { 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) + const rates = tokenAddresses.map(() => formatRate(5)) await setTokenRates(priceFeed, USD, tokenAddresses, rates) - await payroll.addEmployee(employee, 100000, 'Boss', NOW - ONE_MONTH, { from: owner }) + await payroll.addEmployee(employee, annualSalaryPerSecond(100000), 'Boss', NOW - ONE_MONTH, { from: owner }) }) it('can not add one more token', async () => { diff --git a/future-apps/payroll/test/contracts/Payroll_bonuses.test.js b/future-apps/payroll/test/contracts/Payroll_bonuses.test.js index ba1710f322..74f23bcc04 100644 --- a/future-apps/payroll/test/contracts/Payroll_bonuses.test.js +++ b/future-apps/payroll/test/contracts/Payroll_bonuses.test.js @@ -1,10 +1,10 @@ const PAYMENT_TYPES = require('../helpers/payment_types') -const { MAX_UINT256 } = require('../helpers/numbers')(web3) const { assertRevert } = require('@aragon/test-helpers/assertThrow') const { getEvents, getEventArgument } = require('../helpers/events') const { NOW, ONE_MONTH, RATE_EXPIRATION_TIME } = require('../helpers/time') +const { bn, bigExp, annualSalaryPerSecond, ONE, MAX_UINT256 } = require('../helpers/numbers')(web3) const { deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy')(artifacts, web3) -const { USD, deployDAI, deployANT, DAI_RATE, ANT_RATE, setTokenRates } = require('../helpers/tokens.js')(artifacts, web3) +const { USD, deployDAI, deployANT, DAI_RATE, ANT_RATE, setTokenRates } = require('../helpers/tokens')(artifacts, web3) contract('Payroll bonuses', ([owner, employee, anyone]) => { let dao, payroll, payrollBase, finance, vault, priceFeed, DAI, ANT @@ -37,7 +37,7 @@ contract('Payroll bonuses', ([owner, employee, anyone]) => { let employeeId beforeEach('add employee', async () => { - const receipt = await payroll.addEmployee(employee, 1000, 'Boss', await payroll.getTimestampPublic()) + const receipt = await payroll.addEmployee(employee, annualSalaryPerSecond(100000), 'Boss', await payroll.getTimestampPublic()) employeeId = getEventArgument(receipt, 'AddEmployee', 'employeeId') }) @@ -63,7 +63,7 @@ contract('Payroll bonuses', ([owner, employee, anyone]) => { } context('when the given bonus greater than zero', () => { - const amount = 1000 + const amount = bigExp(1000, 18) context('when there was no previous bonus', () => { itAddsBonusesSuccessfully(amount) @@ -79,7 +79,7 @@ contract('Payroll bonuses', ([owner, employee, anyone]) => { }) context('when the given bonus is zero', () => { - const amount = 0 + const amount = bn(0) itAddsBonusesSuccessfully(amount) }) @@ -102,7 +102,7 @@ contract('Payroll bonuses', ([owner, employee, anyone]) => { }) it('reverts', async () => { - await assertRevert(payroll.addBonus(employeeId, 1000, { from }), 'PAYROLL_NON_ACTIVE_EMPLOYEE') + await assertRevert(payroll.addBonus(employeeId, bigExp(1000, 18), { from }), 'PAYROLL_NON_ACTIVE_EMPLOYEE') }) }) }) @@ -111,14 +111,14 @@ contract('Payroll bonuses', ([owner, employee, anyone]) => { const employeeId = 0 it('reverts', async () => { - await assertRevert(payroll.addBonus(employeeId, 1000, { from }), 'PAYROLL_NON_ACTIVE_EMPLOYEE') + await assertRevert(payroll.addBonus(employeeId, bigExp(1000, 18), { from }), 'PAYROLL_NON_ACTIVE_EMPLOYEE') }) }) }) context('when the sender does not have permissions', () => { const from = anyone - const amount = 1000 + const amount = bigExp(1000, 18) const employeeId = 0 it('reverts', async () => { @@ -128,7 +128,7 @@ contract('Payroll bonuses', ([owner, employee, anyone]) => { }) context('when it has not been initialized yet', function () { - const amount = 10000 + const amount = bigExp(1000, 18) const employeeId = 0 it('reverts', async () => { @@ -149,7 +149,7 @@ contract('Payroll bonuses', ([owner, employee, anyone]) => { context('when the sender is an employee', () => { const from = employee - let employeeId, salary = 1000 + let employeeId, salary = annualSalaryPerSecond(100000) beforeEach('add employee and accumulate some salary', async () => { const receipt = await payroll.addEmployee(employee, salary, 'Boss', await payroll.getTimestampPublic()) @@ -169,16 +169,16 @@ contract('Payroll bonuses', ([owner, employee, anyone]) => { }) context('when the employee has a pending bonus', () => { - const bonusAmount = 100 + const bonusAmount = bigExp(100, 18) beforeEach('add bonus', async () => { - await payroll.addBonus(employeeId, bonusAmount / 2, { from: owner }) - await payroll.addBonus(employeeId, bonusAmount / 2, { from: owner }) + await payroll.addBonus(employeeId, bonusAmount.div(2), { from: owner }) + await payroll.addBonus(employeeId, bonusAmount.div(2), { from: owner }) }) const assertTransferredAmounts = (requestedAmount, expectedRequestedAmount = requestedAmount) => { - const requestedDAI = DAI_RATE.mul(expectedRequestedAmount * allocationDAI / 100).trunc() - const requestedANT = ANT_RATE.mul(expectedRequestedAmount * allocationANT / 100).trunc() + const requestedDAI = expectedRequestedAmount.div(DAI_RATE).mul(allocationDAI).mul(ONE.div(100)).trunc() + const requestedANT = expectedRequestedAmount.div(ANT_RATE).mul(allocationANT).mul(ONE.div(100)).trunc() it('transfers all the pending bonus', async () => { const previousDAI = await DAI.balanceOf(employee) @@ -223,7 +223,7 @@ contract('Payroll bonuses', ([owner, employee, anyone]) => { const [address, employeeSalary, bonus] = await payroll.getEmployee(employeeId) assert.equal(address, employee, 'employee address does not match') - assert.equal(employeeSalary, salary, 'employee salary does not match') + assert.equal(employeeSalary.toString(), salary.toString(), 'employee salary does not match') assert.equal(previousBonus.minus(expectedRequestedAmount).toString(), bonus.toString(), 'employee bonus does not match') }) } @@ -247,7 +247,7 @@ contract('Payroll bonuses', ([owner, employee, anyone]) => { } context('when the requested amount is zero', () => { - const requestedAmount = 0 + const requestedAmount = bn(0) context('when the employee has some pending salary', () => { context('when the employee is not terminated', () => { @@ -302,7 +302,7 @@ contract('Payroll bonuses', ([owner, employee, anyone]) => { }) context('when the requested amount is less than the total bonus amount', () => { - const requestedAmount = bonusAmount - 1 + const requestedAmount = bonusAmount.div(2) context('when the employee has some pending salary', () => { context('when the employee is not terminated', () => { @@ -393,7 +393,7 @@ contract('Payroll bonuses', ([owner, employee, anyone]) => { }) context('when the requested amount is greater than the total bonus amount', () => { - const requestedAmount = bonusAmount + 1 + const requestedAmount = bonusAmount.plus(1) it('reverts', async () => { await assertRevert(payroll.payday(PAYMENT_TYPES.BONUS, requestedAmount, { from }), 'PAYROLL_INVALID_REQUESTED_AMT') @@ -403,7 +403,7 @@ contract('Payroll bonuses', ([owner, employee, anyone]) => { context('when the employee does not have pending reimbursements', () => { context('when the requested amount is greater than zero', () => { - const requestedAmount = 100 + const requestedAmount = bigExp(100, 18) it('reverts', async () => { await assertRevert(payroll.payday(PAYMENT_TYPES.BONUS, requestedAmount, { from }), 'PAYROLL_NOTHING_PAID') @@ -411,7 +411,7 @@ contract('Payroll bonuses', ([owner, employee, anyone]) => { }) context('when the requested amount is zero', () => { - const requestedAmount = 0 + const requestedAmount = bn(0) it('reverts', async () => { await assertRevert(payroll.payday(PAYMENT_TYPES.BONUS, requestedAmount, { from }), 'PAYROLL_NOTHING_PAID') @@ -422,15 +422,15 @@ contract('Payroll bonuses', ([owner, employee, anyone]) => { context('when the employee did not set any token allocations yet', () => { context('when the employee has a pending bonus', () => { - const bonusAmount = 100 + const bonusAmount = bigExp(100, 18) beforeEach('add bonus', async () => { - await payroll.addBonus(employeeId, bonusAmount / 2, { from: owner }) - await payroll.addBonus(employeeId, bonusAmount / 2, { from: owner }) + await payroll.addBonus(employeeId, bonusAmount.div(2), { from: owner }) + await payroll.addBonus(employeeId, bonusAmount.div(2), { from: owner }) }) context('when the requested amount is zero', () => { - const requestedAmount = 0 + const requestedAmount = bn(0) it('reverts', async () => { await assertRevert(payroll.payday(PAYMENT_TYPES.BONUS, requestedAmount, { from }), 'PAYROLL_NOTHING_PAID') @@ -438,7 +438,7 @@ contract('Payroll bonuses', ([owner, employee, anyone]) => { }) context('when the requested amount is less than the total bonus amount', () => { - const requestedAmount = bonusAmount - 1 + const requestedAmount = bonusAmount.minus(1) it('reverts', async () => { await assertRevert(payroll.payday(PAYMENT_TYPES.BONUS, requestedAmount, { from }), 'PAYROLL_NOTHING_PAID') @@ -454,7 +454,7 @@ contract('Payroll bonuses', ([owner, employee, anyone]) => { }) context('when the requested amount is greater than the total bonus amount', () => { - const requestedAmount = bonusAmount + 1 + const requestedAmount = bonusAmount.plus(1) it('reverts', async () => { await assertRevert(payroll.payday(PAYMENT_TYPES.BONUS, requestedAmount, { from }), 'PAYROLL_INVALID_REQUESTED_AMT') @@ -464,7 +464,7 @@ contract('Payroll bonuses', ([owner, employee, anyone]) => { context('when the employee does not have pending reimbursements', () => { context('when the requested amount is greater than zero', () => { - const requestedAmount = 100 + const requestedAmount = bigExp(100, 18) it('reverts', async () => { await assertRevert(payroll.payday(PAYMENT_TYPES.BONUS, requestedAmount, { from }), 'PAYROLL_NOTHING_PAID') @@ -472,7 +472,7 @@ contract('Payroll bonuses', ([owner, employee, anyone]) => { }) context('when the requested amount is zero', () => { - const requestedAmount = 0 + const requestedAmount = bn(0) it('reverts', async () => { await assertRevert(payroll.payday(PAYMENT_TYPES.BONUS, requestedAmount, { from }), 'PAYROLL_NOTHING_PAID') @@ -486,7 +486,7 @@ contract('Payroll bonuses', ([owner, employee, anyone]) => { const from = anyone context('when the requested amount is greater than zero', () => { - const requestedAmount = 100 + const requestedAmount = bigExp(100, 18) it('reverts', async () => { await assertRevert(payroll.payday(PAYMENT_TYPES.BONUS, requestedAmount, { from }), 'PAYROLL_EMPLOYEE_DOES_NOT_MATCH') @@ -494,7 +494,7 @@ contract('Payroll bonuses', ([owner, employee, anyone]) => { }) context('when the requested amount is zero', () => { - const requestedAmount = 0 + const requestedAmount = bn(0) it('reverts', async () => { await assertRevert(payroll.payday(PAYMENT_TYPES.BONUS, requestedAmount, { from }), 'PAYROLL_EMPLOYEE_DOES_NOT_MATCH') @@ -504,7 +504,7 @@ contract('Payroll bonuses', ([owner, employee, anyone]) => { }) context('when it has not been initialized yet', function () { - const requestedAmount = 0 + const requestedAmount = bn(0) it('reverts', async () => { await assertRevert(payroll.payday(PAYMENT_TYPES.BONUS, requestedAmount, { from: employee }), 'PAYROLL_EMPLOYEE_DOES_NOT_MATCH') diff --git a/future-apps/payroll/test/contracts/Payroll_forwarding.test.js b/future-apps/payroll/test/contracts/Payroll_forwarding.test.js index e83ac0e8f0..4deff90898 100644 --- a/future-apps/payroll/test/contracts/Payroll_forwarding.test.js +++ b/future-apps/payroll/test/contracts/Payroll_forwarding.test.js @@ -1,7 +1,8 @@ const { assertRevert } = require('@aragon/test-helpers/assertThrow') const { getEventArgument } = require('../helpers/events') const { encodeCallScript } = require('@aragon/test-helpers/evmScript') -const { USD, deployDAI } = require('../helpers/tokens.js')(artifacts, web3) +const { annualSalaryPerSecond } = require('../helpers/numbers')(web3) +const { USD, deployDAI } = require('../helpers/tokens')(artifacts, web3) const { NOW, ONE_MONTH, RATE_EXPIRATION_TIME } = require('../helpers/time') const { deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy')(artifacts, web3) @@ -48,7 +49,7 @@ contract('Payroll forwarding,', ([owner, employee, anyone]) => { const sender = employee beforeEach('add employee', async () => { - const receipt = await payroll.addEmployee(employee, 100000, 'Boss', await payroll.getTimestampPublic(), { from: owner }) + const receipt = await payroll.addEmployee(employee, annualSalaryPerSecond(100000), 'Boss', await payroll.getTimestampPublic(), { from: owner }) employeeId = getEventArgument(receipt, 'AddEmployee', 'employeeId').toString() }) @@ -105,7 +106,7 @@ contract('Payroll forwarding,', ([owner, employee, anyone]) => { const from = employee beforeEach('add employee', async () => { - const receipt = await payroll.addEmployee(employee, 100000, 'Boss', await payroll.getTimestampPublic(), { from: owner }) + const receipt = await payroll.addEmployee(employee, annualSalaryPerSecond(100000), 'Boss', await payroll.getTimestampPublic(), { from: owner }) employeeId = getEventArgument(receipt, 'AddEmployee', 'employeeId').toString() }) 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 8b9035282c..6449aa94be 100644 --- a/future-apps/payroll/test/contracts/Payroll_gas_costs.test.js +++ b/future-apps/payroll/test/contracts/Payroll_gas_costs.test.js @@ -2,7 +2,7 @@ const PAYMENT_TYPES = require('../helpers/payment_types') const { annualSalaryPerSecond } = require('../helpers/numbers')(web3) const { NOW, ONE_MONTH, RATE_EXPIRATION_TIME } = require('../helpers/time') const { deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy')(artifacts, web3) -const { USD, deployDAI, deployANT, DAI_RATE, ANT_RATE, setTokenRates } = require('../helpers/tokens.js')(artifacts, web3) +const { USD, deployDAI, deployANT, DAI_RATE, ANT_RATE, setTokenRates } = require('../helpers/tokens')(artifacts, web3) contract('Payroll gas costs', ([owner, employee, anotherEmployee]) => { let dao, payroll, payrollBase, finance, vault, priceFeed, DAI, ANT 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 03c7ffaeb1..fbbab737a4 100644 --- a/future-apps/payroll/test/contracts/Payroll_get_employee.test.js +++ b/future-apps/payroll/test/contracts/Payroll_get_employee.test.js @@ -1,8 +1,9 @@ const { MAX_UINT64 } = require('../helpers/numbers')(web3) const { assertRevert } = require('@aragon/test-helpers/assertThrow') const { getEventArgument } = require('../helpers/events') -const { USD, deployDAI } = require('../helpers/tokens.js')(artifacts, web3) const { NOW, RATE_EXPIRATION_TIME } = require('../helpers/time') +const { annualSalaryPerSecond } = require('../helpers/numbers')(web3) +const { USD, deployDAI } = require('../helpers/tokens')(artifacts, web3) const { deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy')(artifacts, web3) contract('Payroll employee getters', ([owner, employee]) => { @@ -27,20 +28,21 @@ contract('Payroll employee getters', ([owner, employee]) => { context('when the given id exists', () => { let employeeId + const salary = annualSalaryPerSecond(100000) beforeEach('add employee', async () => { - const receipt = await payroll.addEmployee(employee, 1000, 'Boss', await payroll.getTimestampPublic(), { from: owner }) + const receipt = await payroll.addEmployee(employee, salary, 'Boss', await payroll.getTimestampPublic(), { from: owner }) employeeId = getEventArgument(receipt, 'AddEmployee', 'employeeId').toString() }) it('adds a new employee', async () => { - const [address, salary, bonus, reimbursements, accruedSalary, lastPayroll, endDate] = await payroll.getEmployee(employeeId) + const [address, employeeSalary, bonus, reimbursements, accruedSalary, lastPayroll, endDate] = await payroll.getEmployee(employeeId) assert.equal(address, employee, 'employee address does not match') assert.equal(bonus.toString(), 0, 'employee bonus does not match') assert.equal(reimbursements, 0, 'employee reimbursements does not match') assert.equal(accruedSalary, 0, 'employee accrued salary does not match') - assert.equal(salary.toString(), 1000, 'employee salary does not match') + assert.equal(employeeSalary.toString(), salary.toString(), 'employee salary does not match') assert.equal(lastPayroll.toString(), (await currentTimestamp()).toString(), 'employee last payroll does not match') assert.equal(endDate.toString(), MAX_UINT64, 'employee end date does not match') }) @@ -73,17 +75,18 @@ contract('Payroll employee getters', ([owner, employee]) => { context('when the given address exists', () => { let employeeId const address = employee + const salary = annualSalaryPerSecond(100000) beforeEach('add employee', async () => { - const receipt = await payroll.addEmployee(employee, 1000, 'Boss', await payroll.getTimestampPublic(), { from: owner }) + const receipt = await payroll.addEmployee(employee, salary, 'Boss', await payroll.getTimestampPublic(), { from: owner }) employeeId = getEventArgument(receipt, 'AddEmployee', 'employeeId') }) it('adds a new employee', async () => { - const [id, salary, bonus, reimbursements, accruedSalary, lastPayroll, endDate] = await payroll.getEmployeeByAddress(address) + const [id, employeeSalary, bonus, reimbursements, accruedSalary, lastPayroll, endDate] = await payroll.getEmployeeByAddress(address) assert.equal(id.toString(), employeeId.toString(), 'employee id does not match') - assert.equal(salary.toString(), 1000, 'employee salary does not match') + assert.equal(employeeSalary.toString(), salary.toString(), 'employee salary does not match') assert.equal(bonus.toString(), 0, 'employee bonus does not match') assert.equal(reimbursements.toString(), 0, 'employee reimbursements does not match') assert.equal(accruedSalary.toString(), 0, 'employee accrued salary does not match') diff --git a/future-apps/payroll/test/contracts/Payroll_initialize.test.js b/future-apps/payroll/test/contracts/Payroll_initialize.test.js index a9c9c5a5dc..1162e32d94 100644 --- a/future-apps/payroll/test/contracts/Payroll_initialize.test.js +++ b/future-apps/payroll/test/contracts/Payroll_initialize.test.js @@ -1,4 +1,4 @@ -const { USD } = require('../helpers/tokens.js')(artifacts, web3) +const { USD } = require('../helpers/tokens')(artifacts, web3) const { assertRevert } = require('@aragon/test-helpers/assertThrow') const { NOW, RATE_EXPIRATION_TIME } = require('../helpers/time') const { deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy')(artifacts, web3) 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 9aca9f0a6f..ff825669ed 100644 --- a/future-apps/payroll/test/contracts/Payroll_modify_employee.test.js +++ b/future-apps/payroll/test/contracts/Payroll_modify_employee.test.js @@ -1,8 +1,8 @@ -const { USD } = require('../helpers/tokens.js')(artifacts, web3) +const { USD } = require('../helpers/tokens')(artifacts, web3) const { assertRevert } = require('@aragon/test-helpers/assertThrow') const { getEvents, getEventArgument } = require('../helpers/events') const { NOW, ONE_MONTH, RATE_EXPIRATION_TIME } = require('../helpers/time') -const { MAX_UINT256, annualSalaryPerSecond } = require('../helpers/numbers')(web3) +const { bn, MAX_UINT256, annualSalaryPerSecond } = require('../helpers/numbers')(web3) const { deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy')(artifacts, web3) const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' @@ -48,7 +48,7 @@ contract('Payroll employees modification', ([owner, employee, anotherEmployee, a await payroll.setEmployeeSalary(employeeId, newSalary, { from }) const salary = (await payroll.getEmployee(employeeId))[1] - assert.equal(salary.toString(), newSalary, 'accrued salary does not match') + assert.equal(salary.toString(), newSalary.toString(), 'accrued salary does not match') }) it('adds previous owed salary to the accrued salary', async () => { @@ -58,24 +58,24 @@ contract('Payroll employees modification', ([owner, employee, anotherEmployee, a await increaseTime(ONE_MONTH) const accruedSalary = (await payroll.getEmployee(employeeId))[4] - const expectedAccruedSalary = previousSalary * ONE_MONTH - assert.equal(accruedSalary.toString(), expectedAccruedSalary, 'accrued salary does not match') + const expectedAccruedSalary = previousSalary.mul(ONE_MONTH) + assert.equal(accruedSalary.toString(), expectedAccruedSalary.toString(), 'accrued salary does not match') const events = getEvents(receipt, 'AddEmployeeAccruedSalary') assert.equal(events.length, 1, 'number of AddEmployeeAccruedSalary emitted events does not match') assert.equal(events[0].args.employeeId.toString(), employeeId, 'employee id does not match') - assert.equal(events[0].args.amount.toString(), expectedAccruedSalary, 'accrued salary does not match') + assert.equal(events[0].args.amount.toString(), expectedAccruedSalary.toString(), 'accrued salary does not match') }) it('accrues all previous owed salary as accrued salary', async () => { await increaseTime(ONE_MONTH) await payroll.setEmployeeSalary(employeeId, newSalary, { from }) await increaseTime(ONE_MONTH) - await payroll.setEmployeeSalary(employeeId, newSalary * 2, { from }) + await payroll.setEmployeeSalary(employeeId, newSalary.mul(2), { from }) const accruedSalary = (await payroll.getEmployee(employeeId))[4] - const expectedAccruedSalary = previousSalary * ONE_MONTH + newSalary * ONE_MONTH - assert.equal(accruedSalary.toString(), expectedAccruedSalary, 'accrued salary does not match') + const expectedAccruedSalary = previousSalary.mul(ONE_MONTH).plus(newSalary.mul(ONE_MONTH)) + assert.equal(accruedSalary.toString(), expectedAccruedSalary.toString(), 'accrued salary does not match') }) it('emits a SetEmployeeSalary event', async () => { @@ -84,12 +84,12 @@ contract('Payroll employees modification', ([owner, employee, anotherEmployee, a const events = getEvents(receipt, 'SetEmployeeSalary') assert.equal(events.length, 1, 'number of SetEmployeeSalary emitted events does not match') assert.equal(events[0].args.employeeId.toString(), employeeId, 'employee id does not match') - assert.equal(events[0].args.denominationSalary.toString(), newSalary, 'salary does not match') + assert.equal(events[0].args.denominationSalary.toString(), newSalary.toString(), 'salary does not match') }) } context('when the given value greater than zero', () => { - const newSalary = 1000 + const newSalary = previousSalary.mul(2) context('when the employee is not owed a huge salary amount', () => { itSetsSalarySuccessfully(newSalary) @@ -108,7 +108,7 @@ contract('Payroll employees modification', ([owner, employee, anotherEmployee, a }) context('when the given value is zero', () => { - const newSalary = 0 + const newSalary = bn(0) itSetsSalarySuccessfully(newSalary) }) @@ -165,7 +165,7 @@ contract('Payroll employees modification', ([owner, employee, anotherEmployee, a let employeeId beforeEach('add employee', async () => { - const receipt = await payroll.addEmployee(employee, 1000, 'Boss', await payroll.getTimestampPublic(), { from: owner }) + const receipt = await payroll.addEmployee(employee, annualSalaryPerSecond(100000), 'Boss', await payroll.getTimestampPublic(), { from: owner }) employeeId = getEventArgument(receipt, 'AddEmployee', 'employeeId') }) @@ -201,7 +201,7 @@ contract('Payroll employees modification', ([owner, employee, anotherEmployee, a context('when the given address belongs to another employee', () => { beforeEach('add another employee', async () => { - await payroll.addEmployee(anotherEmployee, 1000, 'Boss', await payroll.getTimestampPublic(), { from: owner }) + await payroll.addEmployee(anotherEmployee, annualSalaryPerSecond(100000), 'Boss', await payroll.getTimestampPublic(), { from: owner }) }) it('reverts', async () => { diff --git a/future-apps/payroll/test/contracts/Payroll_payday.test.js b/future-apps/payroll/test/contracts/Payroll_payday.test.js index ad683893b3..485d9e7af9 100644 --- a/future-apps/payroll/test/contracts/Payroll_payday.test.js +++ b/future-apps/payroll/test/contracts/Payroll_payday.test.js @@ -1,10 +1,10 @@ const PAYMENT_TYPES = require('../helpers/payment_types') const { assertRevert } = require('@aragon/test-helpers/assertThrow') -const { MAX_UINT256 } = require('../helpers/numbers')(web3) const { getEventArgument } = require('../helpers/events') +const { bn, ONE, MAX_UINT256, bigExp } = require('../helpers/numbers')(web3) const { NOW, ONE_MONTH, TWO_MONTHS, RATE_EXPIRATION_TIME } = require('../helpers/time') const { deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy')(artifacts, web3) -const { USD, deployDAI, deployANT, DAI_RATE, ANT_RATE, setTokenRates } = require('../helpers/tokens.js')(artifacts, web3) +const { USD, deployDAI, deployANT, DAI_RATE, ANT_RATE, setTokenRates } = require('../helpers/tokens')(artifacts, web3) contract('Payroll payday', ([owner, employee, anyone]) => { let dao, payroll, payrollBase, finance, vault, priceFeed, DAI, ANT @@ -39,7 +39,7 @@ contract('Payroll payday', ([owner, employee, anyone]) => { const from = employee context('when the employee has a reasonable salary', () => { - const salary = 100000 + const salary = bigExp(1, 18) // using 1 USD per second to simplify incomes in tests beforeEach('add employee', async () => { const receipt = await payroll.addEmployee(employee, salary, 'Boss', await payroll.getTimestampPublic()) @@ -57,8 +57,8 @@ contract('Payroll payday', ([owner, employee, anyone]) => { }) const assertTransferredAmounts = (requestedAmount, expectedRequestedAmount = requestedAmount) => { - const requestedDAI = DAI_RATE.mul(expectedRequestedAmount * allocationDAI / 100).trunc() - const requestedANT = ANT_RATE.mul(expectedRequestedAmount * allocationANT / 100).trunc() + const requestedDAI = expectedRequestedAmount.div(DAI_RATE).mul(allocationDAI).trunc().mul(ONE.div(100)) + const requestedANT = expectedRequestedAmount.div(ANT_RATE).mul(allocationANT).trunc().mul(ONE.div(100)) it('transfers the requested salary amount', async () => { const previousDAI = await DAI.balanceOf(employee) @@ -84,13 +84,13 @@ contract('Payroll payday', ([owner, employee, anyone]) => { const eventDAI = events.find(e => e.args.token === DAI.address).args assert.equal(eventDAI.employee, employee, 'employee address does not match') assert.equal(eventDAI.token, DAI.address, 'DAI address does not match') - assert.equal(eventDAI.amount.toString(), requestedDAI, 'payment amount does not match') + assert.equal(eventDAI.amount.toString(), requestedDAI.toString(), 'payment amount does not match') assert.equal(eventDAI.paymentReference, 'Payroll', 'payment reference does not match') const eventANT = events.find(e => e.args.token === ANT.address).args assert.equal(eventANT.employee, employee, 'employee address does not match') assert.equal(eventANT.token, ANT.address, 'ANT address does not match') - assert.equal(eventANT.amount.toString(), requestedANT, 'payment amount does not match') + assert.equal(eventANT.amount.toString(), requestedANT.toString(), 'payment amount does not match') assert.equal(eventANT.paymentReference, 'Payroll', 'payment reference does not match') }) @@ -103,9 +103,9 @@ contract('Payroll payday', ([owner, employee, anyone]) => { await payroll.payday(PAYMENT_TYPES.PAYROLL, requestedAmount, { from }) - const newOwedAmount = salary * ONE_MONTH - const newDAIAmount = DAI_RATE.mul(newOwedAmount * allocationDAI / 100).trunc() - const newANTAmount = ANT_RATE.mul(newOwedAmount * allocationANT / 100).trunc() + const newOwedAmount = salary.mul(ONE_MONTH) + const newDAIAmount = newOwedAmount.div(DAI_RATE).mul(allocationDAI).trunc().mul(ONE.div(100)) + const newANTAmount = newOwedAmount.div(ANT_RATE).mul(allocationANT).trunc().mul(ONE.div(100)) await increaseTime(ONE_MONTH) await setTokenRates(priceFeed, USD, [DAI, ANT], [DAI_RATE, ANT_RATE]) @@ -127,9 +127,9 @@ contract('Payroll payday', ([owner, employee, anyone]) => { const [previousAccruedSalary, previousPayrollDate] = (await payroll.getEmployee(employeeId)).slice(4, 6) if (expectedRequestedAmount >= previousAccruedSalary) { - expectedAccruedSalary = 0 - const remainder = expectedRequestedAmount - previousAccruedSalary - expectedLastPayrollDate = previousPayrollDate.plus(Math.ceil(remainder / salary)) + expectedAccruedSalary = bn(0) + const remainder = expectedRequestedAmount.minus(previousAccruedSalary) + expectedLastPayrollDate = previousPayrollDate.plus(remainder.div(salary).ceil()) } else { expectedAccruedSalary = previousAccruedSalary.minus(expectedRequestedAmount).toString() expectedLastPayrollDate = previousPayrollDate @@ -138,7 +138,7 @@ contract('Payroll payday', ([owner, employee, anyone]) => { await payroll.payday(PAYMENT_TYPES.PAYROLL, requestedAmount, { from }) const [accruedSalary, lastPayrollDate] = (await payroll.getEmployee(employeeId)).slice(4, 6) - assert.equal(accruedSalary.toString(), expectedAccruedSalary, 'accrued salary does not match') + assert.equal(accruedSalary.toString(), expectedAccruedSalary.toString(), 'accrued salary does not match') assert.equal(lastPayrollDate.toString(), expectedLastPayrollDate.toString(), 'last payroll date does not match') }) @@ -148,7 +148,7 @@ contract('Payroll payday', ([owner, employee, anyone]) => { const [address, employeeSalary] = await payroll.getEmployee(employeeId) assert.equal(address, employee, 'employee address does not match') - assert.equal(employeeSalary, salary, 'employee salary does not match') + assert.equal(employeeSalary.toString(), salary.toString(), 'employee salary does not match') }) } @@ -171,7 +171,7 @@ contract('Payroll payday', ([owner, employee, anyone]) => { } const itHandlesPayrollProperlyNeverthelessExtrasOwedAmounts = (requestedAmount, totalOwedAmount) => { - const expectedRequestedAmount = requestedAmount === 0 ? totalOwedAmount : requestedAmount + const expectedRequestedAmount = requestedAmount.eq(0) ? totalOwedAmount : requestedAmount context('when the employee has some pending reimbursements', () => { beforeEach('add reimbursement', async () => { @@ -240,7 +240,7 @@ contract('Payroll payday', ([owner, employee, anyone]) => { await payroll.terminateEmployee(employeeId, await payroll.getTimestampPublic(), { from: owner }) }) - if (requestedAmount === 0 || requestedAmount === totalOwedAmount) { + if (requestedAmount.eq(0) || requestedAmount === totalOwedAmount) { context('when exchange rates are not expired', () => { assertTransferredAmounts(requestedAmount, expectedRequestedAmount) @@ -270,21 +270,21 @@ contract('Payroll payday', ([owner, employee, anyone]) => { context('when the employee does not have accrued salary', () => { context('when the employee has some pending salary', () => { - const currentOwedSalary = salary * ONE_MONTH + const currentOwedSalary = salary.mul(ONE_MONTH) beforeEach('accumulate some pending salary', async () => { await increaseTime(ONE_MONTH) }) context('when the requested amount is zero', () => { - const requestedAmount = 0 + const requestedAmount = bn(0) itHandlesPayrollProperlyNeverthelessExtrasOwedAmounts(requestedAmount, currentOwedSalary) }) context('when the requested amount is lower than the total owed salary', () => { context('when the requested amount represents less than a second of the earnings', () => { - const requestedAmount = salary / 2 + const requestedAmount = salary.div(2) it('updates the last payroll date by one second', async () => { const previousLastPayrollDate = (await payroll.getEmployee(employeeId))[5] @@ -297,7 +297,7 @@ contract('Payroll payday', ([owner, employee, anyone]) => { }) context('when the requested amount represents more than a second of the earnings', () => { - const requestedAmount = currentOwedSalary / 2 + const requestedAmount = currentOwedSalary.div(2) itHandlesPayrollProperlyNeverthelessExtrasOwedAmounts(requestedAmount, currentOwedSalary) }) @@ -310,7 +310,7 @@ contract('Payroll payday', ([owner, employee, anyone]) => { }) context('when the requested amount is greater than the total owed salary', () => { - const requestedAmount = currentOwedSalary + 1 + const requestedAmount = currentOwedSalary.plus(1) it('reverts', async () => { await assertRevert(payroll.payday(PAYMENT_TYPES.PAYROLL, requestedAmount, { from }), 'PAYROLL_INVALID_REQUESTED_AMT') @@ -320,7 +320,7 @@ contract('Payroll payday', ([owner, employee, anyone]) => { context('when the employee does not have pending salary', () => { context('when the requested amount is greater than zero', () => { - const requestedAmount = 100 + const requestedAmount = bigExp(100, 18) it('reverts', async () => { await assertRevert(payroll.payday(PAYMENT_TYPES.PAYROLL, requestedAmount, { from }), 'PAYROLL_NOTHING_PAID') @@ -328,7 +328,7 @@ contract('Payroll payday', ([owner, employee, anyone]) => { }) context('when the requested amount is zero', () => { - const requestedAmount = 0 + const requestedAmount = bn(0) it('reverts', async () => { await assertRevert(payroll.payday(PAYMENT_TYPES.PAYROLL, requestedAmount, { from }), 'PAYROLL_NOTHING_PAID') @@ -338,8 +338,8 @@ contract('Payroll payday', ([owner, employee, anyone]) => { }) context('when the employee has some accrued salary', () => { - const previousSalary = 10 - const previousOwedSalary = ONE_MONTH * previousSalary + const previousSalary = salary.mul(2) + const previousOwedSalary = previousSalary.mul(ONE_MONTH) beforeEach('accrue some salary', async () => { await payroll.setEmployeeSalary(employeeId, previousSalary, { from: owner }) @@ -348,8 +348,8 @@ contract('Payroll payday', ([owner, employee, anyone]) => { }) context('when the employee has some pending salary', () => { - const currentOwedSalary = salary * ONE_MONTH - const totalOwedSalary = previousOwedSalary + currentOwedSalary + const currentOwedSalary = salary.mul(ONE_MONTH) + const totalOwedSalary = previousOwedSalary.plus(currentOwedSalary) beforeEach('accumulate some pending salary and renew token rates', async () => { await increaseTime(ONE_MONTH) @@ -357,19 +357,19 @@ contract('Payroll payday', ([owner, employee, anyone]) => { }) context('when the requested amount is zero', () => { - const requestedAmount = 0 + const requestedAmount = bn(0) itHandlesPayrollProperlyNeverthelessExtrasOwedAmounts(requestedAmount, totalOwedSalary) }) context('when the requested amount is lower than the previous owed salary', () => { - const requestedAmount = previousOwedSalary - 10 + const requestedAmount = previousOwedSalary.minus(10) itHandlesPayrollProperlyNeverthelessExtrasOwedAmounts(requestedAmount, totalOwedSalary) }) context('when the requested amount is greater than the previous owed salary but lower than the total owed', () => { - const requestedAmount = totalOwedSalary / 2 + const requestedAmount = totalOwedSalary.div(2) itHandlesPayrollProperlyNeverthelessExtrasOwedAmounts(requestedAmount, totalOwedSalary) }) @@ -381,7 +381,7 @@ contract('Payroll payday', ([owner, employee, anyone]) => { }) context('when the requested amount is greater than the total owed salary', () => { - const requestedAmount = totalOwedSalary + 1 + const requestedAmount = totalOwedSalary.plus(1) it('reverts', async () => { await assertRevert(payroll.payday(PAYMENT_TYPES.PAYROLL, requestedAmount, { from }), 'PAYROLL_INVALID_REQUESTED_AMT') @@ -391,13 +391,13 @@ contract('Payroll payday', ([owner, employee, anyone]) => { context('when the employee does not have pending salary', () => { context('when the requested amount is zero', () => { - const requestedAmount = 0 + const requestedAmount = bn(0) itHandlesPayrollProperlyNeverthelessExtrasOwedAmounts(requestedAmount, previousOwedSalary) }) context('when the requested amount is lower than the previous owed salary', () => { - const requestedAmount = previousOwedSalary - 10 + const requestedAmount = previousOwedSalary.div(2) itHandlesPayrollProperlyNeverthelessExtrasOwedAmounts(requestedAmount, previousOwedSalary) }) @@ -409,7 +409,7 @@ contract('Payroll payday', ([owner, employee, anyone]) => { }) context('when the requested amount is greater than the previous owed salary', () => { - const requestedAmount = previousOwedSalary + 1 + const requestedAmount = previousOwedSalary.plus(1) it('reverts', async () => { await assertRevert(payroll.payday(PAYMENT_TYPES.PAYROLL, requestedAmount, { from }), 'PAYROLL_INVALID_REQUESTED_AMT') @@ -421,14 +421,14 @@ contract('Payroll payday', ([owner, employee, anyone]) => { context('when the employee did not set any token allocations yet', () => { context('when the employee has some pending salary', () => { - const owedSalary = salary * ONE_MONTH + const owedSalary = salary.mul(ONE_MONTH) beforeEach('accumulate some pending salary', async () => { await increaseTime(ONE_MONTH) }) context('when the requested amount is lower than the total owed salary', () => { - const requestedAmount = owedSalary - 10 + const requestedAmount = owedSalary.div(2) it('reverts', async () => { await assertRevert(payroll.payday(PAYMENT_TYPES.PAYROLL, requestedAmount, { from }), 'PAYROLL_NOTHING_PAID') @@ -446,7 +446,7 @@ contract('Payroll payday', ([owner, employee, anyone]) => { context('when the employee does not have pending salary', () => { context('when the requested amount is greater than zero', () => { - const requestedAmount = 100 + const requestedAmount = bigExp(100, 18) it('reverts', async () => { await assertRevert(payroll.payday(PAYMENT_TYPES.PAYROLL, requestedAmount, { from }), 'PAYROLL_NOTHING_PAID') @@ -454,7 +454,7 @@ contract('Payroll payday', ([owner, employee, anyone]) => { }) context('when the requested amount is zero', () => { - const requestedAmount = 0 + const requestedAmount = bn(0) it('reverts', async () => { await assertRevert(payroll.payday(PAYMENT_TYPES.PAYROLL, requestedAmount, { from }), 'PAYROLL_NOTHING_PAID') @@ -521,7 +521,7 @@ contract('Payroll payday', ([owner, employee, anyone]) => { } context('when the employee has a zero salary', () => { - const salary = 0 + const salary = bn(0) beforeEach('add employee', async () => { const receipt = await payroll.addEmployee(employee, salary, 'Boss', await payroll.getTimestampPublic()) @@ -535,13 +535,13 @@ contract('Payroll payday', ([owner, employee, anyone]) => { }) context('when the requested amount is greater than zero', () => { - const requestedAmount = 10000 + const requestedAmount = bigExp(10000, 18) itRevertsToWithdrawPartialPayroll(requestedAmount, 'PAYROLL_NOTHING_PAID', 'PAYROLL_NOTHING_PAID') }) context('when the requested amount is zero', () => { - const requestedAmount = 0 + const requestedAmount = bn(0) itRevertsToWithdrawPartialPayroll(requestedAmount, 'PAYROLL_NOTHING_PAID', 'PAYROLL_NOTHING_PAID') }) @@ -549,13 +549,13 @@ contract('Payroll payday', ([owner, employee, anyone]) => { context('when the employee does not have pending salary', () => { context('when the requested amount is greater than zero', () => { - const requestedAmount = 1000 + const requestedAmount = bigExp(1000, 18) itRevertsToWithdrawPartialPayroll(requestedAmount, 'PAYROLL_NOTHING_PAID', 'PAYROLL_NOTHING_PAID') }) context('when the requested amount is zero', () => { - const requestedAmount = 0 + const requestedAmount = bn(0) itRevertsToWithdrawPartialPayroll(requestedAmount, 'PAYROLL_NOTHING_PAID', 'PAYROLL_NOTHING_PAID') }) @@ -606,17 +606,17 @@ contract('Payroll payday', ([owner, employee, anyone]) => { }) context('when the requested amount is zero', () => { - const requestedAmount = 0 + const requestedAmount = bn(0) itRevertsToWithdrawPartialPayroll(requestedAmount, 'MATH_MUL_OVERFLOW', 'PAYROLL_EXCHANGE_RATE_ZERO') }) context('when the requested amount is lower than the total owed salary', () => { - const requestedAmount = 10000 + const requestedAmount = bigExp(1000, 18) const assertTransferredAmounts = requestedAmount => { - const requestedDAI = DAI_RATE.mul(requestedAmount * allocationDAI / 100).trunc() - const requestedANT = ANT_RATE.mul(requestedAmount * allocationANT / 100).trunc() + const requestedDAI = requestedAmount.div(DAI_RATE).mul(allocationDAI).mul(ONE.div(100)).trunc() + const requestedANT = requestedAmount.div(ANT_RATE).mul(allocationANT).mul(ONE.div(100)).trunc() it('transfers the requested salary amount', async () => { const previousDAI = await DAI.balanceOf(employee) @@ -666,11 +666,11 @@ contract('Payroll payday', ([owner, employee, anyone]) => { await payroll.payday(PAYMENT_TYPES.PAYROLL, requestedAmount, { from }) const currentDAI = await DAI.balanceOf(employee) - const expectedDAI = previousDAI.plus(requestedDAI * 2) + const expectedDAI = previousDAI.plus(requestedDAI.mul(2)) assert.equal(currentDAI.toString(), expectedDAI.toString(), 'current DAI balance does not match') const currentANT = await ANT.balanceOf(employee) - const expectedANT = previousANT.plus(requestedANT * 2) + const expectedANT = previousANT.plus(requestedANT.mul(2)) assert.equal(currentANT.toString(), expectedANT.toString(), 'current ANT balance does not match') }) } @@ -717,7 +717,7 @@ contract('Payroll payday', ([owner, employee, anyone]) => { context('when the employee has some pending reimbursements', () => { beforeEach('add reimbursement', async () => { - await payroll.addReimbursement(employeeId, 1000, { from: owner }) + await payroll.addReimbursement(employeeId, bigExp(1000, 18), { from: owner }) }) context('when the employee is not terminated', () => { @@ -757,13 +757,13 @@ contract('Payroll payday', ([owner, employee, anyone]) => { context('when the employee does not have pending salary', () => { context('when the requested amount is greater than zero', () => { - const requestedAmount = 100 + const requestedAmount = bigExp(100, 18) itRevertsToWithdrawPartialPayroll(requestedAmount, 'PAYROLL_NOTHING_PAID', 'PAYROLL_NOTHING_PAID') }) context('when the requested amount is zero', () => { - const requestedAmount = 0 + const requestedAmount = bn(0) itRevertsToWithdrawPartialPayroll(requestedAmount, 'PAYROLL_NOTHING_PAID', 'PAYROLL_NOTHING_PAID') }) @@ -779,13 +779,13 @@ contract('Payroll payday', ([owner, employee, anyone]) => { }) context('when the requested amount is zero', () => { - const requestedAmount = 0 + const requestedAmount = bn(0) itRevertsToWithdrawPartialPayroll(requestedAmount, 'PAYROLL_NOTHING_PAID', 'PAYROLL_NOTHING_PAID') }) context('when the requested amount is lower than the total owed salary', () => { - const requestedAmount = 10000 + const requestedAmount = bigExp(1000, 18) itRevertsToWithdrawPartialPayroll(requestedAmount, 'PAYROLL_NOTHING_PAID', 'PAYROLL_NOTHING_PAID') }) @@ -799,13 +799,13 @@ contract('Payroll payday', ([owner, employee, anyone]) => { context('when the employee does not have pending salary', () => { context('when the requested amount is greater than zero', () => { - const requestedAmount = 100 + const requestedAmount = bigExp(1000, 18) itRevertsToWithdrawPartialPayroll(requestedAmount, 'PAYROLL_NOTHING_PAID', 'PAYROLL_NOTHING_PAID') }) context('when the requested amount is zero', () => { - const requestedAmount = 0 + const requestedAmount = bn(0) itRevertsToWithdrawPartialPayroll(requestedAmount, 'PAYROLL_NOTHING_PAID', 'PAYROLL_NOTHING_PAID') }) @@ -819,7 +819,7 @@ contract('Payroll payday', ([owner, employee, anyone]) => { const from = anyone context('when the requested amount is greater than zero', () => { - const requestedAmount = 100 + const requestedAmount = bigExp(1000, 18) it('reverts', async () => { await assertRevert(payroll.payday(PAYMENT_TYPES.PAYROLL, requestedAmount, { from }), 'PAYROLL_EMPLOYEE_DOES_NOT_MATCH') @@ -827,7 +827,7 @@ contract('Payroll payday', ([owner, employee, anyone]) => { }) context('when the requested amount is zero', () => { - const requestedAmount = 0 + const requestedAmount = bn(0) it('reverts', async () => { await assertRevert(payroll.payday(PAYMENT_TYPES.PAYROLL, requestedAmount, { from }), 'PAYROLL_EMPLOYEE_DOES_NOT_MATCH') @@ -837,7 +837,7 @@ contract('Payroll payday', ([owner, employee, anyone]) => { }) context('when it has not been initialized yet', function () { - const requestedAmount = 0 + const requestedAmount = bn(0) it('reverts', async () => { await assertRevert(payroll.payday(PAYMENT_TYPES.PAYROLL, requestedAmount, { from: employee }), 'PAYROLL_EMPLOYEE_DOES_NOT_MATCH') diff --git a/future-apps/payroll/test/contracts/Payroll_rates.test.js b/future-apps/payroll/test/contracts/Payroll_rates.test.js index 7d7eecdc20..d8423c43e3 100644 --- a/future-apps/payroll/test/contracts/Payroll_rates.test.js +++ b/future-apps/payroll/test/contracts/Payroll_rates.test.js @@ -2,7 +2,7 @@ const PAYMENT_TYPES = require('../helpers/payment_types') const { bigExp } = require('../helpers/numbers')(web3) const { NOW, TWO_MINUTES, RATE_EXPIRATION_TIME } = require('../helpers/time') const { deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy')(artifacts, web3) -const { USD, ETH, ETH_RATE, deployDAI, DAI_RATE, deployANT, ANT_RATE, setTokenRate } = require('../helpers/tokens.js')(artifacts, web3) +const { USD, ETH, ETH_RATE, deployDAI, DAI_RATE, deployANT, ANT_RATE, formatRate, setTokenRate } = require('../helpers/tokens')(artifacts, web3) contract('Payroll rates handling,', ([owner, employee, anyone]) => { let dao, payroll, payrollBase, finance, vault, priceFeed, DAI, ANT @@ -27,9 +27,9 @@ contract('Payroll rates handling,', ([owner, employee, anyone]) => { }) beforeEach('set rates and allow tokens', async () => { - await setTokenRate(priceFeed, ETH, USD, ETH_RATE) - await setTokenRate(priceFeed, DAI, USD, DAI_RATE) - await setTokenRate(priceFeed, ANT, USD, ANT_RATE) + await setTokenRate(priceFeed, USD, ETH, ETH_RATE) + await setTokenRate(priceFeed, USD, DAI, DAI_RATE) + await setTokenRate(priceFeed, USD, ANT, ANT_RATE) await payroll.addAllowedToken(ETH, { from: owner }) await payroll.addAllowedToken(DAI.address, { from: owner }) @@ -137,8 +137,11 @@ contract('Payroll rates handling,', ([owner, employee, anyone]) => { }) beforeEach('set rates and allow tokens', async () => { - await setTokenRate(priceFeed, DAI, ETH, DAI_RATE.div(ETH_RATE)) // 0.050 ETH - await setTokenRate(priceFeed, ANT, ETH, ANT_RATE.div(ETH_RATE)) // 0.025 ETH + const DAI_TO_ETH_RATE = formatRate(DAI_RATE.div(ETH_RATE)) // 0.050 ETH + const ANT_TO_ETH_RATE = formatRate(ANT_RATE.div(ETH_RATE)) // 0.025 ETH + + await setTokenRate(priceFeed, ETH, DAI, DAI_TO_ETH_RATE) + await setTokenRate(priceFeed, ETH, ANT, ANT_TO_ETH_RATE) await payroll.addAllowedToken(ETH, { from: owner }) await payroll.addAllowedToken(DAI.address, { from: owner }) @@ -246,8 +249,11 @@ contract('Payroll rates handling,', ([owner, employee, anyone]) => { }) beforeEach('set rates and allow tokens', async () => { - await setTokenRate(priceFeed, ETH, DAI, ETH_RATE.div(DAI_RATE)) // 20 DAI - await setTokenRate(priceFeed, ANT, DAI, ANT_RATE.div(DAI_RATE)) // 0.5 DAI + const ETH_TO_DAI_RATE = formatRate(ETH_RATE.div(DAI_RATE)) // 20 DAI + const ANT_TO_DAI_RATE = formatRate(ANT_RATE.div(DAI_RATE)) // 0.5 DAI + + await setTokenRate(priceFeed, DAI, ETH, ETH_TO_DAI_RATE) + await setTokenRate(priceFeed, DAI, ANT, ANT_TO_DAI_RATE) await payroll.addAllowedToken(ETH, { from: owner }) await payroll.addAllowedToken(DAI.address, { from: owner }) @@ -355,8 +361,11 @@ contract('Payroll rates handling,', ([owner, employee, anyone]) => { }) beforeEach('set rates and allow tokens', async () => { - await setTokenRate(priceFeed, ETH, ANT, ETH_RATE.div(ANT_RATE)) // 40 ANT - await setTokenRate(priceFeed, DAI, ANT, DAI_RATE.div(ANT_RATE)) // 2 ANT + const ETH_TO_ANT_RATE = formatRate(ETH_RATE.div(ANT_RATE)) // 40 ANT + const DAI_TO_ANT_RATE = formatRate(DAI_RATE.div(ANT_RATE)) // 2 ANT + + await setTokenRate(priceFeed, ANT, ETH, ETH_TO_ANT_RATE) + await setTokenRate(priceFeed, ANT, DAI, DAI_TO_ANT_RATE) await payroll.addAllowedToken(ETH, { from: owner }) await payroll.addAllowedToken(DAI.address, { from: owner }) diff --git a/future-apps/payroll/test/contracts/Payroll_reentrancy.test.js b/future-apps/payroll/test/contracts/Payroll_reentrancy.test.js index 39f1d54f9c..dbf6036a2b 100644 --- a/future-apps/payroll/test/contracts/Payroll_reentrancy.test.js +++ b/future-apps/payroll/test/contracts/Payroll_reentrancy.test.js @@ -1,5 +1,5 @@ -const { bigExp } = require('../helpers/numbers')(web3) const { assertRevert } = require('@aragon/test-helpers/assertThrow') +const { bigExp, annualSalaryPerSecond } = require('../helpers/numbers')(web3) const { NOW, ONE_MONTH, RATE_EXPIRATION_TIME } = require('../helpers/time') const { deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy')(artifacts, web3) @@ -39,7 +39,7 @@ contract('Payroll reentrancy guards', ([owner]) => { beforeEach('add malicious employee, set tokens allocations, and accrue some salary', async () => { await employee.setPayroll(payroll.address) - await payroll.addEmployee(employee.address, 1, 'Malicious Boss', await payroll.getTimestampPublic(), { from: owner }) + await payroll.addEmployee(employee.address, annualSalaryPerSecond(100000), 'Malicious Boss', await payroll.getTimestampPublic(), { from: owner }) await employee.determineAllocation([maliciousToken.address], [100]) await increaseTime(ONE_MONTH) diff --git a/future-apps/payroll/test/contracts/Payroll_reimbursements.test.js b/future-apps/payroll/test/contracts/Payroll_reimbursements.test.js index ec9c36fda7..eb361be65a 100644 --- a/future-apps/payroll/test/contracts/Payroll_reimbursements.test.js +++ b/future-apps/payroll/test/contracts/Payroll_reimbursements.test.js @@ -1,10 +1,10 @@ const PAYMENT_TYPES = require('../helpers/payment_types') const { assertRevert } = require('@aragon/test-helpers/assertThrow') -const { MAX_UINT256 } = require('../helpers/numbers')(web3) const { getEvents, getEventArgument } = require('../helpers/events') const { NOW, ONE_MONTH, RATE_EXPIRATION_TIME } = require('../helpers/time') const { deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy')(artifacts, web3) -const { USD, deployDAI, deployANT, DAI_RATE, ANT_RATE, setTokenRates } = require('../helpers/tokens.js')(artifacts, web3) +const { ONE, bn, bigExp, annualSalaryPerSecond, MAX_UINT256 } = require('../helpers/numbers')(web3) +const { USD, deployDAI, deployANT, DAI_RATE, ANT_RATE, setTokenRates } = require('../helpers/tokens')(artifacts, web3) contract('Payroll reimbursements', ([owner, employee, anyone]) => { let dao, payroll, payrollBase, finance, vault, priceFeed, DAI, ANT @@ -37,49 +37,49 @@ contract('Payroll reimbursements', ([owner, employee, anyone]) => { let employeeId beforeEach('add employee', async () => { - const receipt = await payroll.addEmployee(employee, 1000, 'Boss', await payroll.getTimestampPublic()) + const receipt = await payroll.addEmployee(employee, annualSalaryPerSecond(100000), 'Boss', await payroll.getTimestampPublic()) employeeId = getEventArgument(receipt, 'AddEmployee', 'employeeId') }) context('when the given employee is active', () => { - const itAddsReimbursementsSuccessfully = value => { + const itAddsReimbursementsSuccessfully = reimburse => { it('adds requested reimbursement', async () => { - await payroll.addReimbursement(employeeId, value, { from }) + await payroll.addReimbursement(employeeId, reimburse, { from }) const reimbursements = (await payroll.getEmployee(employeeId))[3] - assert.equal(reimbursements, value, 'reimbursement does not match') + assert.equal(reimbursements.toString(), reimburse.toString(), 'reimbursement does not match') }) it('emits an event', async () => { - const receipt = await payroll.addReimbursement(employeeId, value, { from }) + const receipt = await payroll.addReimbursement(employeeId, reimburse, { from }) const events = getEvents(receipt, 'AddEmployeeReimbursement') assert.equal(events.length, 1, 'number of AddEmployeeReimbursement emitted events does not match') assert.equal(events[0].args.employeeId.toString(), employeeId, 'employee id does not match') - assert.equal(events[0].args.amount.toString(), value, 'reimbursement does not match') + assert.equal(events[0].args.amount.toString(), reimburse, 'reimbursement does not match') }) } - context('when the given value greater than zero', () => { - const value = 1000 + context('when the given reimbursement greater than zero', () => { + const reimbursement = bigExp(1000, 18) - itAddsReimbursementsSuccessfully(value) + itAddsReimbursementsSuccessfully(reimbursement) }) - context('when the given value is zero', () => { - const value = 0 + context('when the given reimbursement is zero', () => { + const reimbursement = bn(0) - itAddsReimbursementsSuccessfully(value) + itAddsReimbursementsSuccessfully(reimbursement) }) - context('when the given value way greater than zero', () => { - const value = MAX_UINT256 + context('when the given reimbursement way greater than zero', () => { + const reimbursement = MAX_UINT256 it('reverts', async () => { await payroll.addReimbursement(employeeId, 1, { from }) - await assertRevert(payroll.addReimbursement(employeeId, value, { from }), 'MATH_ADD_OVERFLOW') + await assertRevert(payroll.addReimbursement(employeeId, reimbursement, { from }), 'MATH_ADD_OVERFLOW') }) }) }) @@ -91,7 +91,7 @@ contract('Payroll reimbursements', ([owner, employee, anyone]) => { }) it('reverts', async () => { - await assertRevert(payroll.addReimbursement(employeeId, 1000, { from }), 'PAYROLL_NON_ACTIVE_EMPLOYEE') + await assertRevert(payroll.addReimbursement(employeeId, bigExp(1000, 18), { from }), 'PAYROLL_NON_ACTIVE_EMPLOYEE') }) }) }) @@ -100,28 +100,28 @@ contract('Payroll reimbursements', ([owner, employee, anyone]) => { const employeeId = 0 it('reverts', async () => { - await assertRevert(payroll.addReimbursement(employeeId, 1000, { from }), 'PAYROLL_NON_ACTIVE_EMPLOYEE') + await assertRevert(payroll.addReimbursement(employeeId, bigExp(1000, 18), { from }), 'PAYROLL_NON_ACTIVE_EMPLOYEE') }) }) }) context('when the sender does not have permissions', () => { const from = anyone - const value = 1000 const employeeId = 0 + const reimbursement = bigExp(1000, 18) it('reverts', async () => { - await assertRevert(payroll.addReimbursement(employeeId, value, { from }), 'APP_AUTH_FAILED') + await assertRevert(payroll.addReimbursement(employeeId, reimbursement, { from }), 'APP_AUTH_FAILED') }) }) }) context('when it has not been initialized yet', function () { - const value = 10000 const employeeId = 0 + const reimbursement = bigExp(1000, 18) it('reverts', async () => { - await assertRevert(payroll.addReimbursement(employeeId, value, { from: owner }), 'APP_AUTH_FAILED') + await assertRevert(payroll.addReimbursement(employeeId, reimbursement, { from: owner }), 'APP_AUTH_FAILED') }) }) }) @@ -138,7 +138,7 @@ contract('Payroll reimbursements', ([owner, employee, anyone]) => { context('when the sender is an employee', () => { const from = employee - let employeeId, salary = 1000 + let employeeId, salary = annualSalaryPerSecond(1000) beforeEach('add employee and accumulate some salary', async () => { const receipt = await payroll.addEmployee(employee, salary, 'Boss', await payroll.getTimestampPublic()) @@ -158,16 +158,16 @@ contract('Payroll reimbursements', ([owner, employee, anyone]) => { }) context('when the employee has some pending reimbursements', () => { - const reimbursement = 100 + const reimbursement = bigExp(100, 18) beforeEach('add reimbursement', async () => { - await payroll.addReimbursement(employeeId, reimbursement / 2, { from: owner }) - await payroll.addReimbursement(employeeId, reimbursement / 2, { from: owner }) + await payroll.addReimbursement(employeeId, reimbursement.div(2), { from: owner }) + await payroll.addReimbursement(employeeId, reimbursement.div(2), { from: owner }) }) const assertTransferredAmounts = (requestedAmount, expectedRequestedAmount = requestedAmount) => { - const requestedDAI = DAI_RATE.mul(expectedRequestedAmount * allocationDAI / 100).trunc() - const requestedANT = ANT_RATE.mul(expectedRequestedAmount * allocationANT / 100).trunc() + const requestedDAI = expectedRequestedAmount.div(DAI_RATE).mul(allocationDAI).mul(ONE.div(100)).trunc() + const requestedANT = expectedRequestedAmount.div(ANT_RATE).mul(allocationANT).mul(ONE.div(100)).trunc() it('transfers all the pending reimbursements', async () => { const previousDAI = await DAI.balanceOf(employee) @@ -212,7 +212,7 @@ contract('Payroll reimbursements', ([owner, employee, anyone]) => { const [address, employeeSalary, _, reimbursements] = await payroll.getEmployee(employeeId) assert.equal(address, employee, 'employee address does not match') - assert.equal(employeeSalary, salary, 'employee salary does not match') + assert.equal(employeeSalary.toString(), salary.toString(), 'employee salary does not match') assert.equal(previousReimbursements.minus(expectedRequestedAmount).toString(), reimbursements.toString(), 'employee reimbursements does not match') }) } @@ -236,7 +236,7 @@ contract('Payroll reimbursements', ([owner, employee, anyone]) => { } context('when the requested amount is zero', () => { - const requestedAmount = 0 + const requestedAmount = bn(0) context('when the employee has some pending salary', () => { context('when the employee is not terminated', () => { @@ -291,7 +291,7 @@ contract('Payroll reimbursements', ([owner, employee, anyone]) => { }) context('when the requested amount is less than the total reimbursements amount', () => { - const requestedAmount = reimbursement - 1 + const requestedAmount = reimbursement.div(2) context('when the employee has some pending salary', () => { context('when the employee is not terminated', () => { @@ -382,7 +382,7 @@ contract('Payroll reimbursements', ([owner, employee, anyone]) => { }) context('when the requested amount is greater than the total reimbursements amount', () => { - const requestedAmount = reimbursement + 1 + const requestedAmount = reimbursement.plus(1) it('reverts', async () => { await assertRevert(payroll.payday(PAYMENT_TYPES.REIMBURSEMENT, requestedAmount, { from }), 'PAYROLL_INVALID_REQUESTED_AMT') @@ -392,7 +392,7 @@ contract('Payroll reimbursements', ([owner, employee, anyone]) => { context('when the employee does not have pending reimbursements', () => { context('when the requested amount is greater than zero', () => { - const requestedAmount = 100 + const requestedAmount = bigExp(100, 18) it('reverts', async () => { await assertRevert(payroll.payday(PAYMENT_TYPES.REIMBURSEMENT, requestedAmount, { from }), 'PAYROLL_NOTHING_PAID') @@ -400,7 +400,7 @@ contract('Payroll reimbursements', ([owner, employee, anyone]) => { }) context('when the requested amount is zero', () => { - const requestedAmount = 0 + const requestedAmount = bn(0) it('reverts', async () => { await assertRevert(payroll.payday(PAYMENT_TYPES.REIMBURSEMENT, requestedAmount, { from }), 'PAYROLL_NOTHING_PAID') @@ -411,7 +411,7 @@ contract('Payroll reimbursements', ([owner, employee, anyone]) => { context('when the employee did not set any token allocations yet', () => { context('when the employee has some pending reimbursements', () => { - const reimbursement = 100 + const reimbursement = bigExp(1000, 18) beforeEach('add reimbursement', async () => { await payroll.addReimbursement(employeeId, reimbursement / 2, { from: owner }) @@ -419,7 +419,7 @@ contract('Payroll reimbursements', ([owner, employee, anyone]) => { }) context('when the requested amount is zero', () => { - const requestedAmount = 0 + const requestedAmount = bn(0) it('reverts', async () => { await assertRevert(payroll.payday(PAYMENT_TYPES.REIMBURSEMENT, requestedAmount, { from }), 'PAYROLL_NOTHING_PAID') @@ -427,7 +427,7 @@ contract('Payroll reimbursements', ([owner, employee, anyone]) => { }) context('when the requested amount is less than the total reimbursements amount', () => { - const requestedAmount = reimbursement - 1 + const requestedAmount = reimbursement.div(2) it('reverts', async () => { await assertRevert(payroll.payday(PAYMENT_TYPES.REIMBURSEMENT, requestedAmount, { from }), 'PAYROLL_NOTHING_PAID') @@ -443,7 +443,7 @@ contract('Payroll reimbursements', ([owner, employee, anyone]) => { }) context('when the requested amount is greater than the total reimbursements amount', () => { - const requestedAmount = reimbursement + 1 + const requestedAmount = reimbursement.plus(1) it('reverts', async () => { await assertRevert(payroll.payday(PAYMENT_TYPES.REIMBURSEMENT, requestedAmount, { from }), 'PAYROLL_INVALID_REQUESTED_AMT') @@ -453,7 +453,7 @@ contract('Payroll reimbursements', ([owner, employee, anyone]) => { context('when the employee does not have pending reimbursements', () => { context('when the requested amount is greater than zero', () => { - const requestedAmount = 100 + const requestedAmount = bigExp(100, 18) it('reverts', async () => { await assertRevert(payroll.payday(PAYMENT_TYPES.REIMBURSEMENT, requestedAmount, { from }), 'PAYROLL_NOTHING_PAID') @@ -461,7 +461,7 @@ contract('Payroll reimbursements', ([owner, employee, anyone]) => { }) context('when the requested amount is zero', () => { - const requestedAmount = 0 + const requestedAmount = bn(0) it('reverts', async () => { await assertRevert(payroll.payday(PAYMENT_TYPES.REIMBURSEMENT, requestedAmount, { from }), 'PAYROLL_NOTHING_PAID') @@ -475,7 +475,7 @@ contract('Payroll reimbursements', ([owner, employee, anyone]) => { const from = anyone context('when the requested amount is greater than zero', () => { - const requestedAmount = 100 + const requestedAmount = bigExp(100, 18) it('reverts', async () => { await assertRevert(payroll.payday(PAYMENT_TYPES.REIMBURSEMENT, requestedAmount, { from }), 'PAYROLL_EMPLOYEE_DOES_NOT_MATCH') @@ -483,7 +483,7 @@ contract('Payroll reimbursements', ([owner, employee, anyone]) => { }) context('when the requested amount is zero', () => { - const requestedAmount = 0 + const requestedAmount = bn(0) it('reverts', async () => { await assertRevert(payroll.payday(PAYMENT_TYPES.REIMBURSEMENT, requestedAmount, { from }), 'PAYROLL_EMPLOYEE_DOES_NOT_MATCH') @@ -493,7 +493,7 @@ contract('Payroll reimbursements', ([owner, employee, anyone]) => { }) context('when it has not been initialized yet', function () { - const requestedAmount = 0 + const requestedAmount = bn(0) it('reverts', async () => { await assertRevert(payroll.payday(PAYMENT_TYPES.REIMBURSEMENT, requestedAmount, { from: employee }), 'PAYROLL_EMPLOYEE_DOES_NOT_MATCH') 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 e468001e33..381c24cfc0 100644 --- a/future-apps/payroll/test/contracts/Payroll_terminate_employee.test.js +++ b/future-apps/payroll/test/contracts/Payroll_terminate_employee.test.js @@ -2,8 +2,8 @@ const PAYMENT_TYPES = require('../helpers/payment_types') const { assertRevert } = require('@aragon/test-helpers/assertThrow') const { getEvents, getEventArgument } = require('../helpers/events') const { NOW, ONE_MONTH, RATE_EXPIRATION_TIME } = require('../helpers/time') -const { bn, MAX_UINT64, annualSalaryPerSecond } = require('../helpers/numbers')(web3) -const { USD, DAI_RATE, deployDAI, setTokenRate } = require('../helpers/tokens.js')(artifacts, web3) +const { USD, DAI_RATE, deployDAI, setTokenRate } = require('../helpers/tokens')(artifacts, web3) +const { ONE, bn, bigExp, MAX_UINT64, annualSalaryPerSecond } = require('../helpers/numbers')(web3) const { deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy')(artifacts, web3) contract('Payroll employees termination', ([owner, employee, anyone]) => { @@ -81,8 +81,9 @@ contract('Payroll employees termination', ([owner, employee, anyone]) => { // Accrue some salary and extras await increaseTime(ONE_MONTH) - const owedSalary = salary.times(ONE_MONTH) - const reimbursement = 1000 + // Mimic EVM truncation when calculating token amount to transfer + const owedSalary = salary.times(ONE_MONTH).div(ONE.div(100)).trunc().mul(ONE.div(100)) + const reimbursement = bigExp(100000, 18) await payroll.addReimbursement(employeeId, reimbursement, { from: owner }) // Terminate employee and travel some time in the future 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 2412a74c8f..003e58eaae 100644 --- a/future-apps/payroll/test/contracts/Payroll_token_allocations.test.js +++ b/future-apps/payroll/test/contracts/Payroll_token_allocations.test.js @@ -1,7 +1,8 @@ const { assertRevert } = require('@aragon/test-helpers/assertThrow') +const { annualSalaryPerSecond } = require('../helpers/numbers')(web3) const { getEvents, getEventArgument } = require('../helpers/events') const { NOW, ONE_MONTH, RATE_EXPIRATION_TIME } = require('../helpers/time') -const { USD, deployDAI, deployTokenAndDeposit } = require('../helpers/tokens.js')(artifacts, web3) +const { USD, deployDAI, deployTokenAndDeposit } = require('../helpers/tokens')(artifacts, web3) const { deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy')(artifacts, web3) const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' @@ -42,7 +43,7 @@ contract('Payroll token allocations', ([owner, employee, anyone]) => { let employeeId beforeEach('add employee', async () => { - const receipt = await payroll.addEmployee(employee, 100000, 'Boss', await payroll.getTimestampPublic(), { from: owner }) + const receipt = await payroll.addEmployee(employee, annualSalaryPerSecond(100000), 'Boss', await payroll.getTimestampPublic(), { from: owner }) employeeId = getEventArgument(receipt, 'AddEmployee', 'employeeId') }) @@ -204,7 +205,7 @@ contract('Payroll token allocations', ([owner, employee, anyone]) => { let employeeId beforeEach('add employee', async () => { - const receipt = await payroll.addEmployee(employee, 100000, 'Boss', await payroll.getTimestampPublic(), { from: owner }) + const receipt = await payroll.addEmployee(employee, annualSalaryPerSecond(100000), 'Boss', await payroll.getTimestampPublic(), { from: owner }) employeeId = getEventArgument(receipt, 'AddEmployee', 'employeeId') }) diff --git a/future-apps/payroll/test/helpers/tokens.js b/future-apps/payroll/test/helpers/tokens.js index 9066555cf4..2eee4416ce 100644 --- a/future-apps/payroll/test/helpers/tokens.js +++ b/future-apps/payroll/test/helpers/tokens.js @@ -8,9 +8,9 @@ module.exports = (artifacts, web3) => { const formatRate = n => bn(n.toFixed(18)).times(ONE) - const ETH_RATE = bn(20) // 1 ETH = 20 USD - const DAI_RATE = bn(1) // 1 DAI = 1 USD - const ANT_RATE = bn(0.5) // 1 ANT = 0.5 USD + const ETH_RATE = formatRate(20) // 1 ETH = 20 USD + const DAI_RATE = formatRate(1) // 1 DAI = 1 USD + const ANT_RATE = formatRate(0.5) // 1 ANT = 0.5 USD const deployANT = async (sender, finance) => deployTokenAndDeposit(sender, finance, 'ANT') const deployDAI = async (sender, finance) => deployTokenAndDeposit(sender, finance, 'DAI') @@ -30,9 +30,8 @@ module.exports = (artifacts, web3) => { const base = typeof(token) === 'object' ? token.address : token const quote = typeof(denominationToken) === 'object' ? denominationToken.address : denominationToken - const xrt = formatRate(rate) - return feed.update(base, quote, xrt, when, SIG) + return feed.update(base, quote, rate, when, SIG) } async function setTokenRates(feed, denominationToken, tokens, rates, when = undefined) { @@ -40,11 +39,10 @@ module.exports = (artifacts, web3) => { 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) + return feed.updateMany(bases, quotes, rates, whens, sigs) } return { @@ -53,10 +51,11 @@ module.exports = (artifacts, web3) => { ETH_RATE, DAI_RATE, ANT_RATE, + formatRate, deployANT, deployDAI, deployTokenAndDeposit, setTokenRate, - setTokenRates + setTokenRates, } } From 174bea766ce3594857fe63805cf0fa5f63553126 Mon Sep 17 00:00:00 2001 From: Facundo Spagnuolo Date: Fri, 26 Apr 2019 09:28:40 -0300 Subject: [PATCH 5/6] Payroll: Improve rates handling unit test for ETH --- future-apps/payroll/test/contracts/Payroll_rates.test.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/future-apps/payroll/test/contracts/Payroll_rates.test.js b/future-apps/payroll/test/contracts/Payroll_rates.test.js index d8423c43e3..c5290556f1 100644 --- a/future-apps/payroll/test/contracts/Payroll_rates.test.js +++ b/future-apps/payroll/test/contracts/Payroll_rates.test.js @@ -137,11 +137,11 @@ contract('Payroll rates handling,', ([owner, employee, anyone]) => { }) beforeEach('set rates and allow tokens', async () => { - const DAI_TO_ETH_RATE = formatRate(DAI_RATE.div(ETH_RATE)) // 0.050 ETH - const ANT_TO_ETH_RATE = formatRate(ANT_RATE.div(ETH_RATE)) // 0.025 ETH + const ETH_TO_DAI_RATE = formatRate(ETH_RATE.div(DAI_RATE)) // 20 DAI + const ETH_TO_ANT_RATE = formatRate(ETH_RATE.div(ANT_RATE)) // 40 ANT - await setTokenRate(priceFeed, ETH, DAI, DAI_TO_ETH_RATE) - await setTokenRate(priceFeed, ETH, ANT, ANT_TO_ETH_RATE) + await setTokenRate(priceFeed, DAI, ETH, ETH_TO_DAI_RATE) + await setTokenRate(priceFeed, ANT, ETH, ETH_TO_ANT_RATE) await payroll.addAllowedToken(ETH, { from: owner }) await payroll.addAllowedToken(DAI.address, { from: owner }) From 5a6570a5f7b5cccecd7cbc3be64dbf346592dc26 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Fri, 26 Apr 2019 23:20:18 +0200 Subject: [PATCH 6/6] Payroll: revert to old way of obtaining exchange rate and add more clarity (#821) --- future-apps/payroll/contracts/Payroll.sol | 26 ++++++++++++++----- .../test/contracts/Payroll_bonuses.test.js | 6 ++--- .../test/contracts/Payroll_payday.test.js | 14 +++++----- .../contracts/Payroll_reimbursements.test.js | 6 ++--- .../Payroll_terminate_employee.test.js | 9 ++++--- future-apps/payroll/test/helpers/tokens.js | 9 +++++++ 6 files changed, 46 insertions(+), 24 deletions(-) diff --git a/future-apps/payroll/contracts/Payroll.sol b/future-apps/payroll/contracts/Payroll.sol index b53bbf05a4..20f202e7ee 100644 --- a/future-apps/payroll/contracts/Payroll.sol +++ b/future-apps/payroll/contracts/Payroll.sol @@ -655,17 +655,23 @@ contract Payroll is EtherTokenConstant, IForwarder, IsContract, AragonApp { } /** - * @dev Get token exchange rate for a token based on the denomination token - * @param _token Token + * @dev Get token exchange rate for a token based on the denomination token. + * If the denomination token was USD and ETH's price was 100USD, + * this would return 0.01 for ETH. + * @param _token Token to get price of in denomination tokens * @return ONE if _token is denominationToken or 0 if the exchange rate isn't recent enough */ - function _getExchangeRate(address _token) internal view returns (uint128) { + function _getExchangeRateInDenominationToken(address _token) internal view returns (uint128) { // Denomination token has always exchange rate of 1 if (_token == denominationToken) { return ONE; } - (uint128 xrt, uint64 when) = feed.get(_token, denominationToken); + // xrt is the number of `_token` that can be exchanged for one `denominationToken` + (uint128 xrt, uint64 when) = feed.get( + denominationToken, // Base (e.g. USD) + _token // Quote (e.g. ETH) + ); // Check the price feed is recent enough if (getTimestamp64().sub(when) >= rateExpiryTime) { @@ -692,10 +698,16 @@ contract Payroll is EtherTokenConstant, IForwarder, IsContract, AragonApp { address token = allowedTokensArray[i]; uint256 tokenAllocation = employee.allocation[token]; if (tokenAllocation != uint256(0)) { - uint256 exchangeRate = uint256(_getExchangeRate(token)); + // Get the exchange rate for the token in denomination token, + // as we do accounting in denomination tokens + uint128 exchangeRate = _getExchangeRateInDenominationToken(token); require(exchangeRate > 0, ERROR_EXCHANGE_RATE_ZERO); - uint256 tokenAmount = _totalAmount.mul(tokenAllocation).div(exchangeRate).mul(ONE / 100); - // Salary converted to token and applied allocation percentage + + // Salary (in denomination tokens) converted to payout token + // and applied allocation percentage + uint256 tokenAmount = _totalAmount.mul(exchangeRate).mul(tokenAllocation); + // Divide by 100 for the allocation and by ONE for the exchange rate precision + tokenAmount = tokenAmount / (100 * ONE); finance.newImmediatePayment(token, employeeAddress, tokenAmount, paymentReference); emit SendPayment(employeeAddress, token, tokenAmount, paymentReference); diff --git a/future-apps/payroll/test/contracts/Payroll_bonuses.test.js b/future-apps/payroll/test/contracts/Payroll_bonuses.test.js index 74f23bcc04..fc40dc1ee5 100644 --- a/future-apps/payroll/test/contracts/Payroll_bonuses.test.js +++ b/future-apps/payroll/test/contracts/Payroll_bonuses.test.js @@ -4,7 +4,7 @@ const { getEvents, getEventArgument } = require('../helpers/events') const { NOW, ONE_MONTH, RATE_EXPIRATION_TIME } = require('../helpers/time') const { bn, bigExp, annualSalaryPerSecond, ONE, MAX_UINT256 } = require('../helpers/numbers')(web3) const { deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy')(artifacts, web3) -const { USD, deployDAI, deployANT, DAI_RATE, ANT_RATE, setTokenRates } = require('../helpers/tokens')(artifacts, web3) +const { USD, DAI_RATE, ANT_RATE, exchangedAmount, deployDAI, deployANT, setTokenRates } = require('../helpers/tokens')(artifacts, web3) contract('Payroll bonuses', ([owner, employee, anyone]) => { let dao, payroll, payrollBase, finance, vault, priceFeed, DAI, ANT @@ -177,8 +177,8 @@ contract('Payroll bonuses', ([owner, employee, anyone]) => { }) const assertTransferredAmounts = (requestedAmount, expectedRequestedAmount = requestedAmount) => { - const requestedDAI = expectedRequestedAmount.div(DAI_RATE).mul(allocationDAI).mul(ONE.div(100)).trunc() - const requestedANT = expectedRequestedAmount.div(ANT_RATE).mul(allocationANT).mul(ONE.div(100)).trunc() + const requestedDAI = exchangedAmount(expectedRequestedAmount, DAI_RATE, allocationDAI) + const requestedANT = exchangedAmount(expectedRequestedAmount, ANT_RATE, allocationANT) it('transfers all the pending bonus', async () => { const previousDAI = await DAI.balanceOf(employee) diff --git a/future-apps/payroll/test/contracts/Payroll_payday.test.js b/future-apps/payroll/test/contracts/Payroll_payday.test.js index 485d9e7af9..ab289d0f48 100644 --- a/future-apps/payroll/test/contracts/Payroll_payday.test.js +++ b/future-apps/payroll/test/contracts/Payroll_payday.test.js @@ -4,7 +4,7 @@ const { getEventArgument } = require('../helpers/events') const { bn, ONE, MAX_UINT256, bigExp } = require('../helpers/numbers')(web3) const { NOW, ONE_MONTH, TWO_MONTHS, RATE_EXPIRATION_TIME } = require('../helpers/time') const { deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy')(artifacts, web3) -const { USD, deployDAI, deployANT, DAI_RATE, ANT_RATE, setTokenRates } = require('../helpers/tokens')(artifacts, web3) +const { USD, DAI_RATE, ANT_RATE, exchangedAmount, deployDAI, deployANT, setTokenRates } = require('../helpers/tokens')(artifacts, web3) contract('Payroll payday', ([owner, employee, anyone]) => { let dao, payroll, payrollBase, finance, vault, priceFeed, DAI, ANT @@ -57,8 +57,8 @@ contract('Payroll payday', ([owner, employee, anyone]) => { }) const assertTransferredAmounts = (requestedAmount, expectedRequestedAmount = requestedAmount) => { - const requestedDAI = expectedRequestedAmount.div(DAI_RATE).mul(allocationDAI).trunc().mul(ONE.div(100)) - const requestedANT = expectedRequestedAmount.div(ANT_RATE).mul(allocationANT).trunc().mul(ONE.div(100)) + const requestedDAI = exchangedAmount(expectedRequestedAmount, DAI_RATE, allocationDAI) + const requestedANT = exchangedAmount(expectedRequestedAmount, ANT_RATE, allocationANT) it('transfers the requested salary amount', async () => { const previousDAI = await DAI.balanceOf(employee) @@ -104,8 +104,8 @@ contract('Payroll payday', ([owner, employee, anyone]) => { await payroll.payday(PAYMENT_TYPES.PAYROLL, requestedAmount, { from }) const newOwedAmount = salary.mul(ONE_MONTH) - const newDAIAmount = newOwedAmount.div(DAI_RATE).mul(allocationDAI).trunc().mul(ONE.div(100)) - const newANTAmount = newOwedAmount.div(ANT_RATE).mul(allocationANT).trunc().mul(ONE.div(100)) + const newDAIAmount = exchangedAmount(newOwedAmount, DAI_RATE, allocationDAI) + const newANTAmount = exchangedAmount(newOwedAmount, ANT_RATE, allocationANT) await increaseTime(ONE_MONTH) await setTokenRates(priceFeed, USD, [DAI, ANT], [DAI_RATE, ANT_RATE]) @@ -615,8 +615,8 @@ contract('Payroll payday', ([owner, employee, anyone]) => { const requestedAmount = bigExp(1000, 18) const assertTransferredAmounts = requestedAmount => { - const requestedDAI = requestedAmount.div(DAI_RATE).mul(allocationDAI).mul(ONE.div(100)).trunc() - const requestedANT = requestedAmount.div(ANT_RATE).mul(allocationANT).mul(ONE.div(100)).trunc() + const requestedDAI = exchangedAmount(requestedAmount, DAI_RATE, allocationDAI) + const requestedANT = exchangedAmount(requestedAmount, ANT_RATE, allocationANT) it('transfers the requested salary amount', async () => { const previousDAI = await DAI.balanceOf(employee) diff --git a/future-apps/payroll/test/contracts/Payroll_reimbursements.test.js b/future-apps/payroll/test/contracts/Payroll_reimbursements.test.js index eb361be65a..4f6321b0b0 100644 --- a/future-apps/payroll/test/contracts/Payroll_reimbursements.test.js +++ b/future-apps/payroll/test/contracts/Payroll_reimbursements.test.js @@ -4,7 +4,7 @@ const { getEvents, getEventArgument } = require('../helpers/events') const { NOW, ONE_MONTH, RATE_EXPIRATION_TIME } = require('../helpers/time') const { deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy')(artifacts, web3) const { ONE, bn, bigExp, annualSalaryPerSecond, MAX_UINT256 } = require('../helpers/numbers')(web3) -const { USD, deployDAI, deployANT, DAI_RATE, ANT_RATE, setTokenRates } = require('../helpers/tokens')(artifacts, web3) +const { USD, DAI_RATE, ANT_RATE, exchangedAmount, deployDAI, deployANT, setTokenRates } = require('../helpers/tokens')(artifacts, web3) contract('Payroll reimbursements', ([owner, employee, anyone]) => { let dao, payroll, payrollBase, finance, vault, priceFeed, DAI, ANT @@ -166,8 +166,8 @@ contract('Payroll reimbursements', ([owner, employee, anyone]) => { }) const assertTransferredAmounts = (requestedAmount, expectedRequestedAmount = requestedAmount) => { - const requestedDAI = expectedRequestedAmount.div(DAI_RATE).mul(allocationDAI).mul(ONE.div(100)).trunc() - const requestedANT = expectedRequestedAmount.div(ANT_RATE).mul(allocationANT).mul(ONE.div(100)).trunc() + const requestedDAI = exchangedAmount(expectedRequestedAmount, DAI_RATE, allocationDAI) + const requestedANT = exchangedAmount(expectedRequestedAmount, ANT_RATE, allocationANT) it('transfers all the pending reimbursements', async () => { const previousDAI = await DAI.balanceOf(employee) 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 381c24cfc0..9eb687f4de 100644 --- a/future-apps/payroll/test/contracts/Payroll_terminate_employee.test.js +++ b/future-apps/payroll/test/contracts/Payroll_terminate_employee.test.js @@ -2,7 +2,7 @@ const PAYMENT_TYPES = require('../helpers/payment_types') const { assertRevert } = require('@aragon/test-helpers/assertThrow') const { getEvents, getEventArgument } = require('../helpers/events') const { NOW, ONE_MONTH, RATE_EXPIRATION_TIME } = require('../helpers/time') -const { USD, DAI_RATE, deployDAI, setTokenRate } = require('../helpers/tokens')(artifacts, web3) +const { USD, DAI_RATE, exchangedAmount, deployDAI, setTokenRate } = require('../helpers/tokens')(artifacts, web3) const { ONE, bn, bigExp, MAX_UINT64, annualSalaryPerSecond } = require('../helpers/numbers')(web3) const { deployContracts, createPayrollAndPriceFeed } = require('../helpers/deploy')(artifacts, web3) @@ -81,8 +81,6 @@ contract('Payroll employees termination', ([owner, employee, anyone]) => { // Accrue some salary and extras await increaseTime(ONE_MONTH) - // Mimic EVM truncation when calculating token amount to transfer - const owedSalary = salary.times(ONE_MONTH).div(ONE.div(100)).trunc().mul(ONE.div(100)) const reimbursement = bigExp(100000, 18) await payroll.addReimbursement(employeeId, reimbursement, { from: owner }) @@ -95,8 +93,11 @@ contract('Payroll employees termination', ([owner, employee, anyone]) => { await payroll.payday(PAYMENT_TYPES.REIMBURSEMENT, 0, { from: employee }) await assertRevert(payroll.getEmployee(employeeId), 'PAYROLL_EMPLOYEE_DOESNT_EXIST') + const owedSalaryInDai = exchangedAmount(salary.times(ONE_MONTH), DAI_RATE, 100) + const reimbursementInDai = exchangedAmount(reimbursement, DAI_RATE, 100) + const currentDAI = await DAI.balanceOf(employee) - const expectedDAI = previousDAI.plus(owedSalary).plus(reimbursement) + const expectedDAI = previousDAI.plus(owedSalaryInDai).plus(reimbursementInDai) assert.equal(currentDAI.toString(), expectedDAI.toString(), 'current balance does not match') }) diff --git a/future-apps/payroll/test/helpers/tokens.js b/future-apps/payroll/test/helpers/tokens.js index 2eee4416ce..e8684cc426 100644 --- a/future-apps/payroll/test/helpers/tokens.js +++ b/future-apps/payroll/test/helpers/tokens.js @@ -12,6 +12,14 @@ module.exports = (artifacts, web3) => { const DAI_RATE = formatRate(1) // 1 DAI = 1 USD const ANT_RATE = formatRate(0.5) // 1 ANT = 0.5 USD + function exchangedAmount(amount, rate, tokenAllocation) { + // Mimic PPF inversion truncation, as we set the denomination token always + // as the price feed's quote token + const inverseRate = ONE.pow(2).div(rate).trunc() + // Mimic EVM calculation and truncation for token conversion + return amount.mul(inverseRate).mul(tokenAllocation).div(ONE.mul(100)).trunc() + } + const deployANT = async (sender, finance) => deployTokenAndDeposit(sender, finance, 'ANT') const deployDAI = async (sender, finance) => deployTokenAndDeposit(sender, finance, 'DAI') @@ -52,6 +60,7 @@ module.exports = (artifacts, web3) => { DAI_RATE, ANT_RATE, formatRate, + exchangedAmount, deployANT, deployDAI, deployTokenAndDeposit,