laconicd/tests/solidity/suites/staking/test/helpers/helpers.js

284 lines
11 KiB
JavaScript
Raw Normal View History

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,
}
}