Skip to content
Permalink
Browse files

Payroll: Ceil last payroll date calculation (#776)

* Payroll: Rename payment `reference` to `paymentReference`

* Payroll: Remove return values from `_payday` and `_reimburse` functions

* Payroll: Implement accrued salary

* Payroll: Support employees bonus

* Payroll: Ceil last payroll date calculation

* Payroll: Address feedback from @sohkai in #774

* Payroll: Address feedback from @sohkai in #775

* Payroll: Address feedback from @sohkai in #776

* Payroll: Fix compiler warnings
  • Loading branch information...
facuspagnuolo committed Apr 16, 2019
1 parent 3041a1b commit 7b8bedf276f9dc351512e3bf9a787a9ba0b7f495
@@ -52,8 +52,8 @@
"params": ["Employee Id", "Yearly salary"]
},
{
"name": "Add accrued value for employee",
"id": "ADD_ACCRUED_VALUE_ROLE",
"name": "Add reimbursements for employee",
"id": "ADD_REIMBURSEMENT_ROLE",
"params": ["Employee Id", "Amount"]
},
{

Large diffs are not rendered by default.

Oops, something went wrong.
@@ -99,7 +99,7 @@ contract PayrollKit is KitBase {
acl.createPermission(employer, payroll, payroll.TERMINATE_EMPLOYEE_ROLE(), root);
acl.createPermission(employer, payroll, payroll.ALLOWED_TOKENS_MANAGER_ROLE(), root);
acl.createPermission(employer, payroll, payroll.SET_EMPLOYEE_SALARY_ROLE(), root);
acl.createPermission(employer, payroll, payroll.ADD_ACCRUED_VALUE_ROLE(), root);
acl.createPermission(employer, payroll, payroll.ADD_REIMBURSEMENT_ROLE(), root);
acl.createPermission(root, payroll, payroll.CHANGE_PRICE_FEED_ROLE(), root);
acl.createPermission(root, payroll, payroll.MODIFY_RATE_EXPIRY_ROLE(), root);

@@ -5,7 +5,6 @@ import "@aragon/test-helpers/contracts/TimeHelpersMock.sol";


contract PayrollMock is Payroll, TimeHelpersMock {
function getMaxAccruedValue() public pure returns (uint256) { return MAX_UINT256; }
function getMaxAllowedTokens() public pure returns (uint256) { return MAX_ALLOWED_TOKENS; }
function getAllowedTokensArrayLength() public view returns (uint256) { return allowedTokensArray.length; }
}
@@ -88,10 +88,10 @@ contract('Payroll employees addition', ([owner, employee, anotherEmployee, anyon
assert.equal(event.startDate.toString(), (await currentTimestamp()).toString(), 'employee start date does not match')
assert.equal(event.initialDenominationSalary.toString(), anotherSalary.toString(), 'employee salary does not match')

const [address, employeeSalary, bonus, accruedValue, accruedSalary, lastPayroll, endDate] = await payroll.getEmployee(anotherEmployeeId)
const [address, employeeSalary, bonus, reimbursements, accruedSalary, lastPayroll, endDate] = await payroll.getEmployee(anotherEmployeeId)
assert.equal(address, anotherEmployee, 'employee account does not match')
assert.equal(bonus.toString(), 0, 'employee bonus does not match')
assert.equal(accruedValue, 0, 'employee accrued value does not match')
assert.equal(reimbursements, 0, 'employee reimbursements does not match')
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(), (await currentTimestamp()).toString(), 'employee last payroll does not match')
@@ -196,10 +196,10 @@ contract('Payroll employees addition', ([owner, employee, anotherEmployee, anyon
assert.equal(event.startDate.toString(), startDate, 'employee start date does not match')
assert.equal(event.initialDenominationSalary.toString(), anotherSalary.toString(), 'employee salary does not match')

const [address, employeeSalary, bonus, accruedValue, accruedSalary, lastPayroll, endDate] = await payroll.getEmployee(anotherEmployeeId)
const [address, employeeSalary, bonus, reimbursements, accruedSalary, lastPayroll, endDate] = await payroll.getEmployee(anotherEmployeeId)
assert.equal(address, anotherEmployee, 'employee address does not match')
assert.equal(bonus.toString(), 0, 'employee bonus does not match')
assert.equal(accruedValue, 0, 'employee accrued value does not match')
assert.equal(reimbursements, 0, 'employee reimbursements does not match')
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')
@@ -1,3 +1,4 @@
const PAYMENT_TYPES = require('../helpers/payment_types')
const { getEvent } = require('../helpers/events')
const { assertRevert } = require('@aragon/test-helpers/assertThrow')
const { deployErc20TokenAndDeposit, deployContracts, createPayrollInstance, mockTimestamps } = require('../helpers/setup.js')(artifacts, web3)
@@ -14,7 +15,6 @@ contract('Payroll allowed tokens,', ([owner, employee, anotherEmployee, anyone])
const RATE_EXPIRATION_TIME = TWO_MONTHS

const TOKEN_DECIMALS = 18
const PAYROLL_PAYMENT_TYPE = 0

before('setup base apps and tokens', async () => {
({ dao, finance, vault, priceFeed, payrollBase } = await deployContracts(owner))
@@ -102,7 +102,7 @@ contract('Payroll allowed tokens,', ([owner, employee, anotherEmployee, anyone])
const allocationTx = await payroll.determineAllocation(tokenAddresses, allocations, { from: employee })
assert.isBelow(allocationTx.receipt.cumulativeGasUsed, MAX_GAS_USED, 'Too much gas consumed for allocation')

const paydayTx = await payroll.payday(PAYROLL_PAYMENT_TYPE, 0, { from: employee })
const paydayTx = await payroll.payday(PAYMENT_TYPES.PAYROLL, 0, { from: employee })
assert.isBelow(paydayTx.receipt.cumulativeGasUsed, MAX_GAS_USED, 'Too much gas consumed for payday')
})
})
@@ -1,9 +1,10 @@
const PAYMENT_TYPES = require('../helpers/payment_types')
const { assertRevert } = require('@aragon/test-helpers/assertThrow')
const { getEvents, getEventArgument } = require('../helpers/events')
const { bigExp, maxUint256 } = require('../helpers/numbers')(web3)
const { deployErc20TokenAndDeposit, deployContracts, createPayrollInstance, mockTimestamps } = require('../helpers/setup.js')(artifacts, web3)

contract('Payroll bonuses', ([owner, employee, anotherEmployee, anyone]) => {
contract('Payroll bonuses', ([owner, employee, anyone]) => {
let dao, payroll, payrollBase, finance, vault, priceFeed, denominationToken, anotherToken

const NOW = 1553703809 // random fixed timestamp in seconds
@@ -13,8 +14,6 @@ contract('Payroll bonuses', ([owner, employee, anotherEmployee, anyone]) => {

const PCT_ONE = bigExp(1, 18)
const TOKEN_DECIMALS = 18
const BONUS_PAYMENT_TYPE = 2
const PAYROLL_PAYMENT_TYPE = 0

before('setup base apps and tokens', async () => {
({ dao, finance, vault, priceFeed, payrollBase } = await deployContracts(owner))
@@ -183,7 +182,7 @@ contract('Payroll bonuses', ([owner, employee, anotherEmployee, anyone]) => {
const previousDenominationTokenBalance = await denominationToken.balanceOf(employee)
const previousAnotherTokenBalance = await anotherToken.balanceOf(employee)

await payroll.payday(BONUS_PAYMENT_TYPE, requestedAmount, { from })
await payroll.payday(PAYMENT_TYPES.BONUS, requestedAmount, { from })

const currentDenominationTokenBalance = await denominationToken.balanceOf(employee)
const expectedDenominationTokenBalance = previousDenominationTokenBalance.plus(requestedDenominationTokenAmount);
@@ -196,7 +195,7 @@ contract('Payroll bonuses', ([owner, employee, anotherEmployee, anyone]) => {
})

it('emits one event per allocated token', async () => {
const receipt = await payroll.payday(BONUS_PAYMENT_TYPE, requestedAmount, { from })
const receipt = await payroll.payday(PAYMENT_TYPES.BONUS, requestedAmount, { from })

const events = receipt.logs.filter(l => l.event === 'SendPayment')
assert.equal(events.length, 2, 'should have emitted two events')
@@ -219,7 +218,7 @@ contract('Payroll bonuses', ([owner, employee, anotherEmployee, anyone]) => {
const assertEmployeeIsNotRemoved = (requestedAmount, expectedRequestedAmount = requestedAmount) => {
it('does not remove the employee and resets the bonus amount', async () => {
const previousBonus = (await payroll.getEmployee(employeeId))[2]
await payroll.payday(BONUS_PAYMENT_TYPE, requestedAmount, { from })
await payroll.payday(PAYMENT_TYPES.BONUS, requestedAmount, { from })

const [address, employeeSalary, bonus] = await payroll.getEmployee(employeeId)

@@ -241,7 +240,7 @@ contract('Payroll bonuses', ([owner, employee, anotherEmployee, anyone]) => {
})

it('reverts', async () => {
await assertRevert(payroll.payday(BONUS_PAYMENT_TYPE, requestedAmount, { from }), 'PAYROLL_EXCHANGE_RATE_ZERO')
await assertRevert(payroll.payday(PAYMENT_TYPES.BONUS, requestedAmount, { from }), 'PAYROLL_EXCHANGE_RATE_ZERO')
})
})
}
@@ -265,7 +264,7 @@ contract('Payroll bonuses', ([owner, employee, anotherEmployee, anyone]) => {

context('when the employee does not have pending salary', () => {
beforeEach('cash out pending salary', async () => {
await payroll.payday(PAYROLL_PAYMENT_TYPE, 0, { from })
await payroll.payday(PAYMENT_TYPES.PAYROLL, 0, { from })
})

context('when the employee is not terminated', () => {
@@ -281,7 +280,7 @@ contract('Payroll bonuses', ([owner, employee, anotherEmployee, anyone]) => {
assertTransferredAmounts(requestedAmount, bonusAmount)

it('removes the employee', async () => {
await payroll.payday(BONUS_PAYMENT_TYPE, requestedAmount, { from })
await payroll.payday(PAYMENT_TYPES.BONUS, requestedAmount, { from })

await assertRevert(payroll.getEmployee(employeeId), 'PAYROLL_EMPLOYEE_DOESNT_EXIST')
})
@@ -293,7 +292,7 @@ contract('Payroll bonuses', ([owner, employee, anotherEmployee, anyone]) => {
})

it('reverts', async () => {
await assertRevert(payroll.payday(BONUS_PAYMENT_TYPE, requestedAmount, { from }), 'PAYROLL_EXCHANGE_RATE_ZERO')
await assertRevert(payroll.payday(PAYMENT_TYPES.BONUS, requestedAmount, { from }), 'PAYROLL_EXCHANGE_RATE_ZERO')
})
})
})
@@ -319,7 +318,7 @@ contract('Payroll bonuses', ([owner, employee, anotherEmployee, anyone]) => {

context('when the employee does not have pending salary', () => {
beforeEach('cash out pending salary', async () => {
await payroll.payday(PAYROLL_PAYMENT_TYPE, 0, { from })
await payroll.payday(PAYMENT_TYPES.PAYROLL, 0, { from })
})

context('when the employee is not terminated', () => {
@@ -355,7 +354,7 @@ contract('Payroll bonuses', ([owner, employee, anotherEmployee, anyone]) => {

context('when the employee does not have pending salary', () => {
beforeEach('cash out pending salary', async () => {
await payroll.payday(PAYROLL_PAYMENT_TYPE, 0, { from })
await payroll.payday(PAYMENT_TYPES.PAYROLL, 0, { from })
})

context('when the employee is not terminated', () => {
@@ -371,7 +370,7 @@ contract('Payroll bonuses', ([owner, employee, anotherEmployee, anyone]) => {
assertTransferredAmounts(requestedAmount)

it('removes the employee', async () => {
await payroll.payday(BONUS_PAYMENT_TYPE, requestedAmount, { from })
await payroll.payday(PAYMENT_TYPES.BONUS, requestedAmount, { from })

await assertRevert(payroll.getEmployee(employeeId), 'PAYROLL_EMPLOYEE_DOESNT_EXIST')
})
@@ -383,7 +382,7 @@ contract('Payroll bonuses', ([owner, employee, anotherEmployee, anyone]) => {
})

it('reverts', async () => {
await assertRevert(payroll.payday(BONUS_PAYMENT_TYPE, requestedAmount, { from }), 'PAYROLL_EXCHANGE_RATE_ZERO')
await assertRevert(payroll.payday(PAYMENT_TYPES.BONUS, requestedAmount, { from }), 'PAYROLL_EXCHANGE_RATE_ZERO')
})
})
})
@@ -394,7 +393,7 @@ contract('Payroll bonuses', ([owner, employee, anotherEmployee, anyone]) => {
const requestedAmount = bonusAmount + 1

it('reverts', async () => {
await assertRevert(payroll.payday(BONUS_PAYMENT_TYPE, requestedAmount, { from }), 'PAYROLL_INVALID_REQUESTED_AMT')
await assertRevert(payroll.payday(PAYMENT_TYPES.BONUS, requestedAmount, { from }), 'PAYROLL_INVALID_REQUESTED_AMT')
})
})
})
@@ -404,15 +403,15 @@ contract('Payroll bonuses', ([owner, employee, anotherEmployee, anyone]) => {
const requestedAmount = 100

it('reverts', async () => {
await assertRevert(payroll.payday(BONUS_PAYMENT_TYPE, requestedAmount, { from }), 'PAYROLL_NOTHING_PAID')
await assertRevert(payroll.payday(PAYMENT_TYPES.BONUS, requestedAmount, { from }), 'PAYROLL_NOTHING_PAID')
})
})

context('when the requested amount is zero', () => {
const requestedAmount = 0

it('reverts', async () => {
await assertRevert(payroll.payday(BONUS_PAYMENT_TYPE, requestedAmount, { from }), 'PAYROLL_NOTHING_PAID')
await assertRevert(payroll.payday(PAYMENT_TYPES.BONUS, requestedAmount, { from }), 'PAYROLL_NOTHING_PAID')
})
})
})
@@ -431,31 +430,31 @@ contract('Payroll bonuses', ([owner, employee, anotherEmployee, anyone]) => {
const requestedAmount = 0

it('reverts', async () => {
await assertRevert(payroll.payday(BONUS_PAYMENT_TYPE, requestedAmount, { from }), 'PAYROLL_NOTHING_PAID')
await assertRevert(payroll.payday(PAYMENT_TYPES.BONUS, requestedAmount, { from }), 'PAYROLL_NOTHING_PAID')
})
})

context('when the requested amount is less than the total bonus amount', () => {
const requestedAmount = bonusAmount - 1

it('reverts', async () => {
await assertRevert(payroll.payday(BONUS_PAYMENT_TYPE, requestedAmount, { from }), 'PAYROLL_NOTHING_PAID')
await assertRevert(payroll.payday(PAYMENT_TYPES.BONUS, requestedAmount, { from }), 'PAYROLL_NOTHING_PAID')
})
})

context('when the requested amount is equal to the total bonus amount', () => {
const requestedAmount = bonusAmount

it('reverts', async () => {
await assertRevert(payroll.payday(BONUS_PAYMENT_TYPE, requestedAmount, { from }), 'PAYROLL_NOTHING_PAID')
await assertRevert(payroll.payday(PAYMENT_TYPES.BONUS, requestedAmount, { from }), 'PAYROLL_NOTHING_PAID')
})
})

context('when the requested amount is greater than the total bonus amount', () => {
const requestedAmount = bonusAmount + 1

it('reverts', async () => {
await assertRevert(payroll.payday(BONUS_PAYMENT_TYPE, requestedAmount, { from }), 'PAYROLL_INVALID_REQUESTED_AMT')
await assertRevert(payroll.payday(PAYMENT_TYPES.BONUS, requestedAmount, { from }), 'PAYROLL_INVALID_REQUESTED_AMT')
})
})
})
@@ -465,15 +464,15 @@ contract('Payroll bonuses', ([owner, employee, anotherEmployee, anyone]) => {
const requestedAmount = 100

it('reverts', async () => {
await assertRevert(payroll.payday(BONUS_PAYMENT_TYPE, requestedAmount, { from }), 'PAYROLL_NOTHING_PAID')
await assertRevert(payroll.payday(PAYMENT_TYPES.BONUS, requestedAmount, { from }), 'PAYROLL_NOTHING_PAID')
})
})

context('when the requested amount is zero', () => {
const requestedAmount = 0

it('reverts', async () => {
await assertRevert(payroll.payday(BONUS_PAYMENT_TYPE, requestedAmount, { from }), 'PAYROLL_NOTHING_PAID')
await assertRevert(payroll.payday(PAYMENT_TYPES.BONUS, requestedAmount, { from }), 'PAYROLL_NOTHING_PAID')
})
})
})
@@ -487,15 +486,15 @@ contract('Payroll bonuses', ([owner, employee, anotherEmployee, anyone]) => {
const requestedAmount = 100

it('reverts', async () => {
await assertRevert(payroll.payday(BONUS_PAYMENT_TYPE, requestedAmount, { from }), 'PAYROLL_EMPLOYEE_DOES_NOT_MATCH')
await assertRevert(payroll.payday(PAYMENT_TYPES.BONUS, requestedAmount, { from }), 'PAYROLL_EMPLOYEE_DOES_NOT_MATCH')
})
})

context('when the requested amount is zero', () => {
const requestedAmount = 0

it('reverts', async () => {
await assertRevert(payroll.payday(BONUS_PAYMENT_TYPE, requestedAmount, { from }), 'PAYROLL_EMPLOYEE_DOES_NOT_MATCH')
await assertRevert(payroll.payday(PAYMENT_TYPES.BONUS, requestedAmount, { from }), 'PAYROLL_EMPLOYEE_DOES_NOT_MATCH')
})
})
})
@@ -505,7 +504,7 @@ contract('Payroll bonuses', ([owner, employee, anotherEmployee, anyone]) => {
const requestedAmount = 0

it('reverts', async () => {
await assertRevert(payroll.payday(BONUS_PAYMENT_TYPE, requestedAmount, { from: employee }), 'PAYROLL_EMPLOYEE_DOES_NOT_MATCH')
await assertRevert(payroll.payday(PAYMENT_TYPES.BONUS, requestedAmount, { from: employee }), 'PAYROLL_EMPLOYEE_DOES_NOT_MATCH')
})
})
})
Oops, something went wrong.

0 comments on commit 7b8bedf

Please sign in to comment.
You can’t perform that action at this time.