@aragon/apps-payroll
Version:
_**Code in Github:**_ [aragon-apps/apps/payroll](https://github.com/aragon/aragon-apps/tree/master/future-apps/payroll)
251 lines (196 loc) • 11.1 kB
JavaScript
const { USD } = require('../helpers/tokens')(artifacts, web3)
const { assertRevert } = require('@aragon/test-helpers/assertThrow')
const { getEvents, getEventArgument } = require('@aragon/test-helpers/events')
const { NOW, ONE_MONTH, RATE_EXPIRATION_TIME } = require('../helpers/time')
const { bn, 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
const increaseTime = async seconds => {
await payroll.mockIncreaseTime(seconds)
await priceFeed.mockIncreaseTime(seconds)
}
before('deploy base apps and tokens', async () => {
({ dao, finance, vault, payrollBase } = await deployContracts(owner))
})
beforeEach('create payroll and price feed instance', async () => {
({ payroll, priceFeed } = await createPayrollAndPriceFeed(dao, payrollBase, owner, NOW))
})
describe('setEmployeeSalary', () => {
context('when it has already been initialized', function () {
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', () => {
const from = owner
context('when the given employee exists', () => {
let employeeId
const previousSalary = annualSalaryPerSecond(100000)
beforeEach('add employee', async () => {
const receipt = await payroll.addEmployee(employee, previousSalary, await payroll.getTimestampPublic(), 'Boss')
employeeId = getEventArgument(receipt, 'AddEmployee', 'employeeId')
})
context('when the given employee is active', () => {
const itSetsSalarySuccessfully = newSalary => {
it('changes the salary of the employee', async () => {
await payroll.setEmployeeSalary(employeeId, newSalary, { from })
const salary = (await payroll.getEmployee(employeeId))[1]
assert.equal(salary.toString(), newSalary.toString(), 'accrued salary does not match')
})
it('adds previous owed salary to the accrued salary', async () => {
await increaseTime(ONE_MONTH)
const receipt = await payroll.setEmployeeSalary(employeeId, newSalary, { from })
await increaseTime(ONE_MONTH)
const accruedSalary = (await payroll.getEmployee(employeeId))[2]
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.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.mul(2), { from })
const accruedSalary = (await payroll.getEmployee(employeeId))[2]
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 () => {
const receipt = await payroll.setEmployeeSalary(employeeId, newSalary, { from })
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.toString(), 'salary does not match')
})
}
context('when the given value greater than zero', () => {
const newSalary = previousSalary.mul(2)
context('when the employee is not owed a huge salary amount', () => {
itSetsSalarySuccessfully(newSalary)
})
context('when the employee is owed a huge salary amount', () => {
beforeEach('accrued a huge salary amount', async () => {
await payroll.setEmployeeSalary(employeeId, MAX_UINT256, { from })
await increaseTime(ONE_MONTH)
})
it('reverts', async () => {
await assertRevert(payroll.setEmployeeSalary(employeeId, newSalary, { from }), 'MATH_MUL_OVERFLOW')
})
})
})
context('when the given value is zero', () => {
const newSalary = bn(0)
itSetsSalarySuccessfully(newSalary)
})
})
context('when the given employee is not active', () => {
beforeEach('terminate employee', async () => {
await payroll.terminateEmployee(employeeId, await payroll.getTimestampPublic(), { from: owner })
await increaseTime(ONE_MONTH)
})
it('reverts', async () => {
await assertRevert(payroll.setEmployeeSalary(employeeId, 1000, { from }), 'PAYROLL_NON_ACTIVE_EMPLOYEE')
})
})
})
context('when the given employee does not exist', async () => {
const employeeId = 0
it('reverts', async () => {
await assertRevert(payroll.setEmployeeSalary(employeeId, 1000, { from }), 'PAYROLL_NON_ACTIVE_EMPLOYEE')
})
})
})
context('when the sender does not have permissions', () => {
const from = anyone
it('reverts', async () => {
await assertRevert(payroll.setEmployeeSalary(0, 1000, { from }), 'APP_AUTH_FAILED')
})
})
})
context('when it has not been initialized yet', function () {
const employeeId = 0
const salary = 10000
it('reverts', async () => {
await assertRevert(payroll.setEmployeeSalary(employeeId, salary, { from: owner }), 'APP_AUTH_FAILED')
})
})
})
describe('changeAddressByEmployee', () => {
context('when it has already been initialized', function () {
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', () => {
const from = employee
let employeeId
beforeEach('add employee', async () => {
const receipt = await payroll.addEmployee(employee, annualSalaryPerSecond(100000), await payroll.getTimestampPublic(), 'Boss', { from: owner })
employeeId = getEventArgument(receipt, 'AddEmployee', 'employeeId')
})
const itHandlesChangingEmployeeAddressSuccessfully = () => {
context('when the given address is a plain new address', () => {
const newAddress = anyone
it('changes the address of the employee', async () => {
await payroll.changeAddressByEmployee(newAddress, { from })
const [address] = await payroll.getEmployee(employeeId)
assert.equal(address, newAddress, 'employee address does not match')
})
it('emits an event', async () => {
const receipt = await payroll.changeAddressByEmployee(newAddress, { from })
const events = getEvents(receipt, 'ChangeAddressByEmployee')
assert.equal(events.length, 1, 'number of ChangeAddressByEmployee emitted events does not match')
assert.equal(events[0].args.employeeId.toString(), employeeId, 'employee id does not match')
assert.equal(events[0].args.newAccountAddress, newAddress, 'new address does not match')
assert.equal(events[0].args.oldAccountAddress, employee, 'previous address does not match')
})
})
context('when the given address is the same address', () => {
const newAddress = employee
it('reverts', async () => {
await assertRevert(payroll.changeAddressByEmployee(newAddress, { from }), 'PAYROLL_EMPLOYEE_ALREADY_EXIST')
})
})
context('when the given address belongs to another employee', () => {
beforeEach('add another employee', async () => {
await payroll.addEmployee(anotherEmployee, annualSalaryPerSecond(100000), await payroll.getTimestampPublic(), 'Boss', { from: owner })
})
it('reverts', async () => {
await assertRevert(payroll.changeAddressByEmployee(anotherEmployee, { from }), 'PAYROLL_EMPLOYEE_ALREADY_EXIST')
})
})
context('when the given address is the zero address', () => {
const newAddress = ZERO_ADDRESS
it('reverts', async () => {
await assertRevert(payroll.changeAddressByEmployee(newAddress, { from }), 'PAYROLL_EMPLOYEE_NULL_ADDRESS')
})
})
}
context('when the given employee is active', () => {
itHandlesChangingEmployeeAddressSuccessfully()
})
context('when the given employee is not active', () => {
beforeEach('terminate employee', async () => {
await payroll.terminateEmployee(employeeId, await payroll.getTimestampPublic(), { from: owner })
await increaseTime(ONE_MONTH)
})
itHandlesChangingEmployeeAddressSuccessfully()
})
})
context('when the sender is not an employee', async () => {
const from = anyone
it('reverts', async () => {
await assertRevert(payroll.changeAddressByEmployee(anotherEmployee, { from }), 'PAYROLL_SENDER_DOES_NOT_MATCH')
})
})
})
context('when it has not been initialized yet', function () {
it('reverts', async () => {
await assertRevert(payroll.changeAddressByEmployee(anotherEmployee, { from: anyone }), 'PAYROLL_SENDER_DOES_NOT_MATCH')
})
})
})
})