laconicd/tests-solidity/suites/staking/test/helpers/helpers.js
Brett Sun c9639c3860
tests: add solidity test suites (#487)
* tests: add solidity test suite

* tests: remove require strings

* Update tests-solidity/init-test-node.sh

* Update tests-solidity/init-test-node.sh

Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com>
2020-09-01 17:16:28 -04:00

284 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const { assertRevert } = require('@aragon/contract-helpers-test/assertThrow')
const { bn, assertBn } = require('@aragon/contract-helpers-test/numbers')
const { DEFAULT_STAKE_AMOUNT, DEFAULT_LOCK_AMOUNT, EMPTY_DATA, ZERO_ADDRESS } = require('./constants')
const { STAKING_ERRORS } = require('../helpers/errors')
module.exports = (artifacts) => {
const StandardTokenMock = artifacts.require('StandardTokenMock')
const LockManagerMock = artifacts.require('LockManagerMock')
const approveAndStake = async ({ staking, amount = DEFAULT_STAKE_AMOUNT, from }) => {
const token = await StandardTokenMock.at(await staking.token())
await token.approve(staking.address, amount, { from })
await staking.stake(amount, EMPTY_DATA, { from })
}
const approveStakeAndLock = async ({
staking,
manager,
allowanceAmount = DEFAULT_LOCK_AMOUNT,
lockAmount = DEFAULT_LOCK_AMOUNT,
stakeAmount = DEFAULT_STAKE_AMOUNT,
data = EMPTY_DATA,
from
}) => {
await approveAndStake({ staking, stake: stakeAmount, from })
const receipt = await staking.allowManagerAndLock(lockAmount, manager, allowanceAmount, data, { from })
return receipt
}
// funds flows helpers
function UserState(address, walletBalance) {
this.address = address
this.walletBalance = walletBalance
this.stakedBalance = bn(0)
this.lockedBalance = bn(0)
this.walletAdd = (amount) => { this.walletBalance = this.walletBalance.add(amount) }
this.walletSub = (amount) => { this.walletBalance = this.walletBalance.sub(amount) }
this.stakedAdd = (amount) => { this.stakedBalance = this.stakedBalance.add(amount) }
this.stakedSub = (amount) => { this.stakedBalance = this.stakedBalance.sub(amount) }
this.lockedAdd = (amount) => { this.lockedBalance = this.lockedBalance.add(amount) }
this.lockedSub = (amount) => { this.lockedBalance = this.lockedBalance.sub(amount) }
this.totalBalance = () => this.walletBalance.add(this.stakedBalance)
}
const approveAndStakeWithState = async ({ staking, amount = DEFAULT_STAKE_AMOUNT, user }) => {
await approveAndStake({ staking, amount, from: user.address })
user.walletSub(amount)
user.stakedAdd(amount)
}
const approveStakeAndLockWithState = async ({
staking,
manager,
allowanceAmount = DEFAULT_LOCK_AMOUNT,
lockAmount = DEFAULT_LOCK_AMOUNT,
stakeAmount = DEFAULT_STAKE_AMOUNT,
data = EMPTY_DATA,
user
}) => {
await approveStakeAndLock({ staking, manager, allowanceAmount, lockAmount, stakeAmount, data, from: user.address })
user.walletSub(stakeAmount)
user.stakedAdd(stakeAmount)
user.lockedAdd(lockAmount)
}
const unstakeWithState = async ({ staking, unstakeAmount, user }) => {
await staking.unstake(unstakeAmount, EMPTY_DATA, { from: user.address })
user.walletAdd(unstakeAmount)
user.stakedSub(unstakeAmount)
}
const unlockWithState = async ({ staking, managerAddress, unlockAmount, user }) => {
await staking.unlock(user.address, managerAddress, unlockAmount, { from: user.address })
user.lockedSub(unlockAmount)
}
const unlockFromManagerWithState = async ({ staking, lockManager, unlockAmount, user }) => {
await lockManager.unlock(staking.address, user.address, unlockAmount, { from: user.address })
user.lockedSub(unlockAmount)
}
const transferWithState = async ({ staking, transferAmount, userFrom, userTo }) => {
await staking.transfer(userTo.address, transferAmount, { from: userFrom.address })
userTo.stakedAdd(transferAmount)
userFrom.stakedSub(transferAmount)
}
const transferAndUnstakeWithState = async ({ staking, transferAmount, userFrom, userTo }) => {
await staking.transferAndUnstake(userTo.address, transferAmount, { from: userFrom.address })
userTo.walletAdd(transferAmount)
userFrom.stakedSub(transferAmount)
}
const slashWithState = async ({ staking, slashAmount, userFrom, userTo, managerAddress }) => {
await staking.slash(userFrom.address, userTo.address, slashAmount, { from: managerAddress })
userTo.stakedAdd(slashAmount)
userFrom.stakedSub(slashAmount)
userFrom.lockedSub(slashAmount)
}
const slashAndUnstakeWithState = async ({ staking, slashAmount, userFrom, userTo, managerAddress }) => {
await staking.slashAndUnstake(userFrom.address, userTo.address, slashAmount, { from: managerAddress })
userTo.walletAdd(slashAmount)
userFrom.stakedSub(slashAmount)
userFrom.lockedSub(slashAmount)
}
const slashFromContractWithState = async ({ staking, slashAmount, userFrom, userTo, lockManager }) => {
await lockManager.slash(staking.address, userFrom.address, userTo.address, slashAmount)
userTo.stakedAdd(slashAmount)
userFrom.stakedSub(slashAmount)
userFrom.lockedSub(slashAmount)
}
const slashAndUnstakeFromContractWithState = async ({ staking, slashAmount, userFrom, userTo, lockManager }) => {
await lockManager.slashAndUnstake(staking.address, userFrom.address, userTo.address, slashAmount)
userTo.walletAdd(slashAmount)
userFrom.stakedSub(slashAmount)
userFrom.lockedSub(slashAmount)
}
// check that real user balances (token in external wallet, staked and locked) match with accounted in state
const checkUserBalances = async ({ staking, users }) => {
const token = await StandardTokenMock.at(await staking.token())
await Promise.all(
users.map(async (user) => {
assertBn(user.walletBalance, await token.balanceOf(user.address), 'token balance doesnt match')
const balances = await staking.getBalancesOf(user.address)
assertBn(user.stakedBalance, balances.staked, 'staked balance doesnt match')
assertBn(user.lockedBalance, balances.locked, 'locked balance doesnt match')
})
)
}
// check that Staking contract total staked matches with:
// - total staked by users in state (must go in combination with checkUserBalances, to make sure this is legit)
// - token balance of staking app
const checkTotalStaked = async ({ staking, users }) => {
const totalStaked = await staking.totalStaked()
const totalStakedState = users.reduce((total, user) => total.add(user.stakedBalance), bn(0))
assertBn(totalStaked, totalStakedState, 'total staked doesnt match')
const token = await StandardTokenMock.at(await staking.token())
const stakingTokenBalance = await token.balanceOf(staking.address)
assertBn(totalStaked, stakingTokenBalance, 'Staking token balance doesnt match')
}
// check that staked balance is greater than locked balance for all users
// uses local state for efficiency, so it must go with checkUserBalances
const checkStakeAndLock = ({ staking, users }) => {
users.map(user => assert.isTrue(user.stakedBalance.gte(user.lockedBalance)))
}
// check that allowed balance is always greater than locked balance, for all pairs of owner-manager
const checkAllowanceAndLock = async ({ staking, users, managers }) => {
await Promise.all(
users.map(async (user) => await Promise.all(
managers.map(async (manager) => {
const lock = await staking.getLock(user.address, manager)
assert.isTrue(lock._amount.lte(lock._allowance))
})
))
)
}
// check that users cant unstake more than unlocked balance
const checkOverUnstaking = async ({ staking, users }) => {
await Promise.all(
users.map(async (user) => {
await assertRevert(
staking.unstake(user.stakedBalance.sub(user.lockedBalance).add(bn(1)), EMPTY_DATA, { from: user.address })/*,
STAKING_ERRORS.ERROR_NOT_ENOUGH_BALANCE
*/
)
})
)
}
// check that users cant unlock more than locked balance
const checkOverUnlocking = async ({ staking, users, managers }) => {
await Promise.all(
users.map(async (user) => await Promise.all(
managers.map(async (manager) => {
const lock = await staking.getLock(user.address, manager)
// const errorMessage = lock._allowance.gt(bn(0)) ? STAKING_ERRORS.ERROR_NOT_ENOUGH_LOCK : STAKING_ERRORS.ERROR_LOCK_DOES_NOT_EXIST
await assertRevert(
staking.unlock(user.address, manager, user.lockedBalance.add(bn(1)), { from: user.address })/*,
errorMessage
*/
)
})
))
)
}
// check that users cant transfer more than unlocked balance
const checkOverTransferring = async ({ staking, users }) => {
await Promise.all(
users.map(async (user) => {
const to = user.address === users[0].address ? users[1].address : users[0].address
await assertRevert(
staking.transfer(to, user.stakedBalance.sub(user.lockedBalance).add(bn(1)), { from: user.address })/*,
STAKING_ERRORS.ERROR_NOT_ENOUGH_BALANCE
*/
)
await assertRevert(
staking.transferAndUnstake(to, user.stakedBalance.sub(user.lockedBalance).add(bn(1)), { from: user.address })/*,
STAKING_ERRORS.ERROR_NOT_ENOUGH_BALANCE
*/
)
})
)
}
// check that managers cant slash more than locked balance
const checkOverSlashing = async ({ staking, users, managers }) => {
await Promise.all(
users.map(async (user) => {
const to = user.address === users[0].address ? users[1].address : users[0].address
for (let i = 0; i < managers.length - 1; i++) {
await assertRevert(
staking.slash(user.address, to, user.lockedBalance.add(bn(1)), { from: managers[i] })/*,
STAKING_ERRORS.ERROR_NOT_ENOUGH_LOCK
*/
)
await assertRevert(
staking.slashAndUnstake(user.address, to, user.lockedBalance.add(bn(1)), { from: managers[i] }),/*
STAKING_ERRORS.ERROR_NOT_ENOUGH_LOCK
*/
)
}
// last in the array is a contract
const lockManagerAddress = managers[managers.length - 1]
const lockManager = await LockManagerMock.at(lockManagerAddress)
await assertRevert(
lockManager.slash(staking.address, user.address, to, user.lockedBalance.add(bn(1))),/*
STAKING_ERRORS.ERROR_NOT_ENOUGH_LOCK
*/
)
await assertRevert(
lockManager.slashAndUnstake(staking.address, user.address, to, user.lockedBalance.add(bn(1))),/*
STAKING_ERRORS.ERROR_NOT_ENOUGH_LOCK
*/
)
})
)
}
const checkInvariants = async ({ staking, users, managers }) => {
await checkUserBalances({ staking, users })
await checkTotalStaked({ staking, users })
checkStakeAndLock({ staking, users })
await checkAllowanceAndLock({ staking, users, managers })
await checkOverUnstaking({ staking, users })
await checkOverUnlocking({ staking, users, managers })
await checkOverTransferring({ staking, users })
await checkOverSlashing({ staking, users, managers })
}
return {
approveAndStake,
approveStakeAndLock,
UserState,
approveAndStakeWithState,
approveStakeAndLockWithState,
unstakeWithState,
unlockWithState,
unlockFromManagerWithState,
transferWithState,
transferAndUnstakeWithState,
slashWithState,
slashAndUnstakeWithState,
slashFromContractWithState,
slashAndUnstakeFromContractWithState,
checkInvariants,
}
}