diff --git a/future-apps/payroll/contracts/Payroll.sol b/future-apps/payroll/contracts/Payroll.sol index 20f202e7ee..8077daa8e0 100644 --- a/future-apps/payroll/contracts/Payroll.sol +++ b/future-apps/payroll/contracts/Payroll.sol @@ -89,7 +89,7 @@ contract Payroll is EtherTokenConstant, IForwarder, IsContract, AragonApp { event TerminateEmployee(uint256 indexed employeeId, address indexed accountAddress, uint64 endDate); event ChangeAddressByEmployee(uint256 indexed employeeId, address indexed oldAddress, address indexed newAddress); event DetermineAllocation(uint256 indexed employeeId, address indexed employee); - event SendPayment(address indexed employee, address indexed token, uint256 amount, string paymentReference); + event SendPayment(address indexed employee, address indexed token, uint256 amount, uint128 exchangeRate, string paymentReference); event SetPriceFeed(address indexed feed); event SetRateExpiryTime(uint64 time); event AddEmployee( @@ -710,7 +710,7 @@ contract Payroll is EtherTokenConstant, IForwarder, IsContract, AragonApp { tokenAmount = tokenAmount / (100 * ONE); finance.newImmediatePayment(token, employeeAddress, tokenAmount, paymentReference); - emit SendPayment(employeeAddress, token, tokenAmount, paymentReference); + emit SendPayment(employeeAddress, token, tokenAmount, exchangeRate, paymentReference); somethingPaid = true; } } diff --git a/future-apps/payroll/test/contracts/Payroll_bonuses.test.js b/future-apps/payroll/test/contracts/Payroll_bonuses.test.js index fc40dc1ee5..71d9f491a7 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, DAI_RATE, ANT_RATE, exchangedAmount, deployDAI, deployANT, setTokenRates } = require('../helpers/tokens')(artifacts, web3) +const { USD, DAI_RATE, ANT_RATE, inverseRate, exchangedAmount, deployDAI, deployANT, setTokenRates } = require('../helpers/tokens')(artifacts, web3) contract('Payroll bonuses', ([owner, employee, anyone]) => { let dao, payroll, payrollBase, finance, vault, priceFeed, DAI, ANT @@ -198,19 +198,21 @@ contract('Payroll bonuses', ([owner, employee, anyone]) => { it('emits one event per allocated token', async () => { const receipt = await payroll.payday(PAYMENT_TYPES.BONUS, requestedAmount, { from }) - const events = receipt.logs.filter(l => l.event === 'SendPayment') + const events = getEvents(receipt, 'SendPayment') assert.equal(events.length, 2, 'should have emitted two events') 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.exchangeRate.toString(), inverseRate(DAI_RATE).toString(), 'payment exchange rate 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.exchangeRate.toString(), inverseRate(ANT_RATE).toString(), 'payment exchange rate does not match') assert.equal(eventANT.paymentReference, 'Bonus', 'payment reference does not match') }) } diff --git a/future-apps/payroll/test/contracts/Payroll_payday.test.js b/future-apps/payroll/test/contracts/Payroll_payday.test.js index ab289d0f48..65ff9f6908 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, DAI_RATE, ANT_RATE, exchangedAmount, deployDAI, deployANT, setTokenRates } = require('../helpers/tokens')(artifacts, web3) +const { USD, DAI_RATE, ANT_RATE, inverseRate, exchangedAmount, deployDAI, deployANT, setTokenRates } = require('../helpers/tokens')(artifacts, web3) contract('Payroll payday', ([owner, employee, anyone]) => { let dao, payroll, payrollBase, finance, vault, priceFeed, DAI, ANT @@ -85,12 +85,14 @@ contract('Payroll payday', ([owner, employee, anyone]) => { 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.toString(), 'payment amount does not match') + assert.equal(eventDAI.exchangeRate.toString(), inverseRate(DAI_RATE).toString(), 'payment exchange rate 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.toString(), 'payment amount does not match') + assert.equal(eventANT.exchangeRate.toString(), inverseRate(ANT_RATE).toString(), 'payment exchange rate does not match') assert.equal(eventANT.paymentReference, 'Payroll', 'payment reference 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 c5290556f1..9013c9b5f8 100644 --- a/future-apps/payroll/test/contracts/Payroll_rates.test.js +++ b/future-apps/payroll/test/contracts/Payroll_rates.test.js @@ -1,8 +1,9 @@ +const { getEvents } = require('../helpers/events') const PAYMENT_TYPES = require('../helpers/payment_types') -const { bigExp } = require('../helpers/numbers')(web3) +const { bigExp, ONE } = 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, formatRate, setTokenRate } = require('../helpers/tokens')(artifacts, web3) +const { USD, ETH, ETH_RATE, deployDAI, DAI_RATE, deployANT, ANT_RATE, formatRate, inverseRate, setTokenRate } = require('../helpers/tokens')(artifacts, web3) contract('Payroll rates handling,', ([owner, employee, anyone]) => { let dao, payroll, payrollBase, finance, vault, priceFeed, DAI, ANT @@ -55,13 +56,18 @@ contract('Payroll rates handling,', ([owner, employee, anyone]) => { 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 { tx, receipt, logs } = await payroll.payday(PAYMENT_TYPES.PAYROLL, 0, { from: employee }) const { gasPrice } = await web3.eth.getTransaction(tx) const txCost = gasPrice.mul(receipt.gasUsed) + const events = getEvents({ logs }, 'SendPayment') + assert.equal(events.length, 1, 'should have emitted one event') + // 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') + const eventETH = events.find(e => e.args.token === ETH).args + assert.equal(eventETH.exchangeRate.toString(), inverseRate(ETH_RATE).toString(), 'ETH exchange rate does not match') // no DAI income expected const currentDAI = await DAI.balanceOf(employee) @@ -83,10 +89,13 @@ contract('Payroll rates handling,', ([owner, employee, anyone]) => { 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 { tx, receipt, logs } = await payroll.payday(PAYMENT_TYPES.PAYROLL, 0, { from: employee }) const { gasPrice } = await web3.eth.getTransaction(tx) const txCost = gasPrice.mul(receipt.gasUsed) + const events = getEvents({ logs }, 'SendPayment') + assert.equal(events.length, 1, 'should have emitted one event') + // 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') @@ -98,6 +107,8 @@ contract('Payroll rates handling,', ([owner, employee, anyone]) => { // 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') + const eventANT = events.find(e => e.args.token === ANT.address).args + assert.equal(eventANT.exchangeRate.toString(), inverseRate(ANT_RATE).toString(), 'ANT exchange rate does not match') }) }) @@ -111,35 +122,46 @@ contract('Payroll rates handling,', ([owner, employee, anyone]) => { 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 { tx, receipt, logs } = await payroll.payday(PAYMENT_TYPES.PAYROLL, 0, { from: employee }) const { gasPrice } = await web3.eth.getTransaction(tx) const txCost = gasPrice.mul(receipt.gasUsed) + const events = getEvents({ logs }, 'SendPayment') + assert.equal(events.length, 3, 'should have emitted three events') + // 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') + const eventETH = events.find(e => e.args.token === ETH).args + assert.equal(eventETH.exchangeRate.toString(), inverseRate(ETH_RATE).toString(), 'ETH exchange rate 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') + const eventDAI = events.find(e => e.args.token === DAI.address).args + assert.equal(eventDAI.exchangeRate.toString(), inverseRate(DAI_RATE).toString(), 'DAI exchange rate 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') + const eventANT = events.find(e => e.args.token === ANT.address).args + assert.equal(eventANT.exchangeRate.toString(), inverseRate(ANT_RATE).toString(), 'ANT exchange rate does not match') }) }) }) context('when the denomination token is ETH', async () => { + 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 + 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 () => { - 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 - + // Note that we set ETH as the quote token for these tests so we don't need to apply the + // inversion when checking rates await setTokenRate(priceFeed, DAI, ETH, ETH_TO_DAI_RATE) await setTokenRate(priceFeed, ANT, ETH, ETH_TO_ANT_RATE) @@ -167,13 +189,18 @@ contract('Payroll rates handling,', ([owner, employee, anyone]) => { 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 { tx, receipt, logs } = await payroll.payday(PAYMENT_TYPES.PAYROLL, 0, { from: employee }) const { gasPrice } = await web3.eth.getTransaction(tx) const txCost = gasPrice.mul(receipt.gasUsed) + const events = getEvents({ logs }, 'SendPayment') + assert.equal(events.length, 1, 'should have emitted one event') + // 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') + const eventETH = events.find(e => e.args.token === ETH).args + assert.equal(eventETH.exchangeRate.toString(), ONE.toString(), 'ETH exchange rate does not match') // no DAI income expected const currentDAI = await DAI.balanceOf(employee) @@ -195,10 +222,13 @@ contract('Payroll rates handling,', ([owner, employee, anyone]) => { 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 { tx, receipt, logs } = await payroll.payday(PAYMENT_TYPES.PAYROLL, 0, { from: employee }) const { gasPrice } = await web3.eth.getTransaction(tx) const txCost = gasPrice.mul(receipt.gasUsed) + const events = getEvents({ logs }, 'SendPayment') + assert.equal(events.length, 1, 'should have emitted one event') + // 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') @@ -210,6 +240,8 @@ contract('Payroll rates handling,', ([owner, employee, anyone]) => { // 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') + const eventANT = events.find(e => e.args.token === ANT.address).args + assert.equal(eventANT.exchangeRate.toString(), ETH_TO_ANT_RATE.toString(), 'ANT exchange rate does not match') }) }) @@ -223,35 +255,44 @@ contract('Payroll rates handling,', ([owner, employee, anyone]) => { 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 { tx, receipt, logs } = await payroll.payday(PAYMENT_TYPES.PAYROLL, 0, { from: employee }) const { gasPrice } = await web3.eth.getTransaction(tx) const txCost = gasPrice.mul(receipt.gasUsed) + const events = getEvents({ logs }, 'SendPayment') + assert.equal(events.length, 3, 'should have emitted three events') + // 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') + const eventETH = events.find(e => e.args.token === ETH).args + assert.equal(eventETH.exchangeRate.toString(), ONE.toString(), 'ETH exchange rate 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') + const eventDAI = events.find(e => e.args.token === DAI.address).args + assert.equal(eventDAI.exchangeRate.toString(), ETH_TO_DAI_RATE.toString(), 'DAI exchange rate 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') + const eventANT = events.find(e => e.args.token === ANT.address).args + assert.equal(eventANT.exchangeRate.toString(), ETH_TO_ANT_RATE.toString(), 'ANT exchange rate does not match') }) }) }) context('when the denomination token is DAI', async () => { + 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 + 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 () => { - 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) @@ -279,13 +320,18 @@ contract('Payroll rates handling,', ([owner, employee, anyone]) => { 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 { tx, receipt, logs } = await payroll.payday(PAYMENT_TYPES.PAYROLL, 0, { from: employee }) const { gasPrice } = await web3.eth.getTransaction(tx) const txCost = gasPrice.mul(receipt.gasUsed) + const events = getEvents({ logs }, 'SendPayment') + assert.equal(events.length, 1, 'should have emitted three events') + // 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') + const eventETH = events.find(e => e.args.token === ETH).args + assert.equal(eventETH.exchangeRate.toString(), inverseRate(ETH_TO_DAI_RATE).toString(), 'ETH exchange rate does not match') // no DAI income expected const currentDAI = await DAI.balanceOf(employee) @@ -307,10 +353,13 @@ contract('Payroll rates handling,', ([owner, employee, anyone]) => { 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 { tx, receipt, logs } = await payroll.payday(PAYMENT_TYPES.PAYROLL, 0, { from: employee }) const { gasPrice } = await web3.eth.getTransaction(tx) const txCost = gasPrice.mul(receipt.gasUsed) + const events = getEvents({ logs }, 'SendPayment') + assert.equal(events.length, 1, 'should have emitted three events') + // 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') @@ -322,6 +371,8 @@ contract('Payroll rates handling,', ([owner, employee, anyone]) => { // 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') + const eventANT = events.find(e => e.args.token === ANT.address).args + assert.equal(eventANT.exchangeRate.toString(), inverseRate(ANT_TO_DAI_RATE).toString(), 'ANT exchange rate does not match') }) }) @@ -335,35 +386,44 @@ contract('Payroll rates handling,', ([owner, employee, anyone]) => { 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 { tx, receipt, logs } = await payroll.payday(PAYMENT_TYPES.PAYROLL, 0, { from: employee }) const { gasPrice } = await web3.eth.getTransaction(tx) const txCost = gasPrice.mul(receipt.gasUsed) + const events = getEvents({ logs }, 'SendPayment') + assert.equal(events.length, 3, 'should have emitted three events') + // 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') + const eventETH = events.find(e => e.args.token === ETH).args + assert.equal(eventETH.exchangeRate.toString(), inverseRate(ETH_TO_DAI_RATE).toString(), 'ETH exchange rate 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') + const eventDAI = events.find(e => e.args.token === DAI.address).args + assert.equal(eventDAI.exchangeRate.toString(), ONE.toString(), 'DAI exchange rate 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') + const eventANT = events.find(e => e.args.token === ANT.address).args + assert.equal(eventANT.exchangeRate.toString(), inverseRate(ANT_TO_DAI_RATE).toString(), 'ANT exchange rate does not match') }) }) }) context('when the denomination token is ANT', async () => { + 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 + 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 () => { - 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) @@ -391,13 +451,18 @@ contract('Payroll rates handling,', ([owner, employee, anyone]) => { 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 { tx, receipt, logs } = await payroll.payday(PAYMENT_TYPES.PAYROLL, 0, { from: employee }) const { gasPrice } = await web3.eth.getTransaction(tx) const txCost = gasPrice.mul(receipt.gasUsed) + const events = getEvents({ logs }, 'SendPayment') + assert.equal(events.length, 1, 'should have emitted three events') + // 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(), bigExp(3, 18).toString(), 'expected current ETH amount does not match') + const eventETH = events.find(e => e.args.token === ETH).args + assert.equal(eventETH.exchangeRate.toString(), inverseRate(ETH_TO_ANT_RATE).toString(), 'ETH exchange rate does not match') // no DAI income expected const currentDAI = await DAI.balanceOf(employee) @@ -419,10 +484,13 @@ contract('Payroll rates handling,', ([owner, employee, anyone]) => { 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 { tx, receipt, logs } = await payroll.payday(PAYMENT_TYPES.PAYROLL, 0, { from: employee }) const { gasPrice } = await web3.eth.getTransaction(tx) const txCost = gasPrice.mul(receipt.gasUsed) + const events = getEvents({ logs }, 'SendPayment') + assert.equal(events.length, 1, 'should have emitted three events') + // 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') @@ -434,6 +502,8 @@ contract('Payroll rates handling,', ([owner, employee, anyone]) => { // 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(), bigExp(120, 18).toString(), 'expected current ANT amount does not match') + const eventANT = events.find(e => e.args.token === ANT.address).args + assert.equal(eventANT.exchangeRate.toString(), ONE.toString(), 'ANT exchange rate does not match') }) }) @@ -447,21 +517,30 @@ contract('Payroll rates handling,', ([owner, employee, anyone]) => { 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 { tx, receipt, logs } = await payroll.payday(PAYMENT_TYPES.PAYROLL, 0, { from: employee }) const { gasPrice } = await web3.eth.getTransaction(tx) const txCost = gasPrice.mul(receipt.gasUsed) + const events = getEvents({ logs }, 'SendPayment') + assert.equal(events.length, 3, 'should have emitted three events') + // 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(), bigExp(1.5, 18).toString(), 'expected current ETH amount does not match') + const eventETH = events.find(e => e.args.token === ETH).args + assert.equal(eventETH.exchangeRate.toString(), inverseRate(ETH_TO_ANT_RATE).toString(), 'ETH exchange rate 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(), bigExp(15, 18).toString(), 'expected current DAI amount does not match') + const eventDAI = events.find(e => e.args.token === DAI.address).args + assert.equal(eventDAI.exchangeRate.toString(), inverseRate(DAI_TO_ANT_RATE).toString(), 'DAI exchange rate 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(), bigExp(30, 18).toString(), 'expected current ANT amount does not match') + const eventANT = events.find(e => e.args.token === ANT.address).args + assert.equal(eventANT.exchangeRate.toString(), ONE.toString(), 'ANT exchange rate does not match') }) }) }) diff --git a/future-apps/payroll/test/contracts/Payroll_reimbursements.test.js b/future-apps/payroll/test/contracts/Payroll_reimbursements.test.js index 4f6321b0b0..2dac35bb64 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, DAI_RATE, ANT_RATE, exchangedAmount, deployDAI, deployANT, setTokenRates } = require('../helpers/tokens')(artifacts, web3) +const { USD, DAI_RATE, ANT_RATE, inverseRate, exchangedAmount, deployDAI, deployANT, setTokenRates } = require('../helpers/tokens')(artifacts, web3) contract('Payroll reimbursements', ([owner, employee, anyone]) => { let dao, payroll, payrollBase, finance, vault, priceFeed, DAI, ANT @@ -187,19 +187,21 @@ contract('Payroll reimbursements', ([owner, employee, anyone]) => { it('emits one event per allocated token', async () => { const receipt = await payroll.payday(PAYMENT_TYPES.REIMBURSEMENT, requestedAmount, { from }) - const events = receipt.logs.filter(l => l.event === 'SendPayment') + const events = getEvents(receipt, 'SendPayment') assert.equal(events.length, 2, 'should have emitted two events') 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.exchangeRate.toString(), inverseRate(DAI_RATE).toString(), 'payment exchange rate 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.exchangeRate.toString(), inverseRate(ANT_RATE).toString(), 'payment exchange rate does not match') assert.equal(eventANT.paymentReference, 'Reimbursement', 'payment reference does not match') }) } diff --git a/future-apps/payroll/test/helpers/tokens.js b/future-apps/payroll/test/helpers/tokens.js index e8684cc426..65f15c1bce 100644 --- a/future-apps/payroll/test/helpers/tokens.js +++ b/future-apps/payroll/test/helpers/tokens.js @@ -12,12 +12,16 @@ 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 inverseRate(rate) { + // Mimic EVM truncation + return ONE.pow(2).div(rate).trunc() + } + 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() + // Invert the rate, as we always set the denomination token as the price feed's quote token + const inversedRate = inverseRate(rate) // Mimic EVM calculation and truncation for token conversion - return amount.mul(inverseRate).mul(tokenAllocation).div(ONE.mul(100)).trunc() + return amount.mul(inversedRate).mul(tokenAllocation).div(ONE.mul(100)).trunc() } const deployANT = async (sender, finance) => deployTokenAndDeposit(sender, finance, 'ANT') @@ -60,6 +64,7 @@ module.exports = (artifacts, web3) => { DAI_RATE, ANT_RATE, formatRate, + inverseRate, exchangedAmount, deployANT, deployDAI,