const { assertRevert } = require('@aragon/contract-helpers-test/assertThrow') const { assertBn, bn, MAX_UINT64 } = require('@aragon/contract-helpers-test/numbers') const { deploy } = require('./helpers/deploy')(artifacts) const { approveAndStake, approveStakeAndLock } = require('./helpers/helpers')(artifacts) const { DEFAULT_STAKE_AMOUNT, DEFAULT_LOCK_AMOUNT, EMPTY_DATA, ZERO_ADDRESS } = require('./helpers/constants') const { STAKING_ERRORS } = require('./helpers/errors') contract('Staking app, Transferring', ([owner, user1, user2]) => { let staking, token, lockManager beforeEach(async () => { const deployment = await deploy(owner) token = deployment.token staking = deployment.staking lockManager = deployment.lockManager }) context('Transfers', async () => { context('From stake', async () => { const transfersFromStake = (transferType) => { it('transfers', async () => { // const initialTotalStake = await staking.totalStaked() const transferAmount = DEFAULT_STAKE_AMOUNT.div(bn(2)) await approveAndStake({ staking, from: owner }) await staking[transferType](user1, transferAmount) assertBn(await staking.unlockedBalanceOf(owner), DEFAULT_STAKE_AMOUNT.sub(transferAmount), 'Owner balance should match') const userStakedBalance = transferType == 'transfer' ? transferAmount : bn(0) assertBn(await staking.unlockedBalanceOf(user1), userStakedBalance, 'User 1 unlocked balance should match') const userExternalBalance = transferType == 'transfer' ? bn(0) : transferAmount assertBn(await token.balanceOf(user1), userExternalBalance, 'User 1 external balance should match') // total stake const totalStaked = transferType == 'transfer' ? DEFAULT_STAKE_AMOUNT : DEFAULT_STAKE_AMOUNT.sub(transferAmount) assertBn(await staking.totalStaked(), totalStaked, 'Total stake should match') }) it('fails transferring zero tokens', async () => { await approveAndStake({ staking, from: owner }) await assertRevert(staking[transferType](user1, 0)/*, STAKING_ERRORS.ERROR_AMOUNT_ZERO */) }) it('fails transferring more than staked balance', async () => { await approveAndStake({ staking, amount: DEFAULT_STAKE_AMOUNT, from: owner }) await assertRevert(staking[transferType](user1, DEFAULT_STAKE_AMOUNT.add(bn(1)))/*, STAKING_ERRORS.ERROR_NOT_ENOUGH_BALANCE */) }) it('fails transferring more than unlocked balance', async () => { await approveStakeAndLock({ staking, manager: lockManager.address, from: owner }) await assertRevert(staking[transferType](user1, DEFAULT_STAKE_AMOUNT)/*, STAKING_ERRORS.ERROR_NOT_ENOUGH_BALANCE */) }) } context('within Staking app', () => { transfersFromStake('transfer') }) context('to external balance (unstaked)', () => { transfersFromStake('transferAndUnstake') }) }) const transfersFromLock = (transferType) => { it('transfers', async () => { await approveStakeAndLock({ staking, manager: lockManager.address, from: owner }) const transferAmount = DEFAULT_LOCK_AMOUNT.div(bn(2)) await lockManager[transferType](staking.address, owner, user1, transferAmount) assertBn(await staking.unlockedBalanceOf(owner), DEFAULT_STAKE_AMOUNT.sub(DEFAULT_LOCK_AMOUNT), 'Owner balance should match') const userUnlockedBalance = transferType == 'slash' ? transferAmount : bn(0) assertBn(await staking.unlockedBalanceOf(user1), userUnlockedBalance, 'User 1 unlocked balance should match') const userExternalBalance = transferType == 'slash' ? bn(0) : transferAmount assertBn(await token.balanceOf(user1), userExternalBalance, 'User 1 external balance should match') // total stake const totalStaked = transferType == 'slash' ? DEFAULT_STAKE_AMOUNT : DEFAULT_STAKE_AMOUNT.sub(transferAmount) assertBn(await staking.totalStaked(), totalStaked, 'Total stake should match') // check lock values const { _amount: amount, _data: data } = await staking.getLock(owner, lockManager.address) assertBn(amount, DEFAULT_LOCK_AMOUNT.sub(transferAmount), 'locked amount should match') }) it('transfers the whole lock amount', async () => { await approveStakeAndLock({ staking, manager: lockManager.address, from: owner }) await lockManager[transferType](staking.address, owner, user1, DEFAULT_LOCK_AMOUNT) assertBn(await staking.unlockedBalanceOf(owner), DEFAULT_STAKE_AMOUNT.sub(DEFAULT_LOCK_AMOUNT), 'Owner balance should match') const userUnlockedBalance = transferType == 'slash' ? DEFAULT_LOCK_AMOUNT : bn(0) assertBn(await staking.unlockedBalanceOf(user1), userUnlockedBalance, 'User 1 unlocked balance should match') const userExternalBalance = transferType == 'slash' ? bn(0) : DEFAULT_LOCK_AMOUNT assertBn(await token.balanceOf(user1), userExternalBalance, 'User 1 external balance should match') // total stake const totalStaked = transferType == 'slash' ? DEFAULT_STAKE_AMOUNT : DEFAULT_STAKE_AMOUNT.sub(DEFAULT_LOCK_AMOUNT) assertBn(await staking.totalStaked(), totalStaked, 'Total stake should match') // check lock values const { _amount: amount, _data: data } = await staking.getLock(owner, lockManager.address) assertBn(amount, bn(0), 'locked amount should match') }) it('fails transferring zero tokens', async () => { await approveStakeAndLock({ staking, manager: lockManager.address, from: owner }) await assertRevert(lockManager[transferType](staking.address, owner, user1, 0)/*, STAKING_ERRORS.ERROR_AMOUNT_ZERO */) }) it('fails transferring more than locked balance', async () => { await approveStakeAndLock({ staking, manager: lockManager.address, from: owner }) await assertRevert(lockManager[transferType](staking.address, owner, user1, DEFAULT_LOCK_AMOUNT.add(bn(1)))/*, STAKING_ERRORS.ERROR_NOT_ENOUGH_LOCK */) }) it('fails if sender is not manager', async () => { await approveStakeAndLock({ staking, manager: user1, from: owner }) await assertRevert(lockManager[transferType](staking.address, owner, user1, DEFAULT_LOCK_AMOUNT)/*, STAKING_ERRORS.ERROR_NOT_ENOUGH_LOCK */) }) it('fails transferring from unlocked lock', async () => { await approveStakeAndLock({ staking, manager: lockManager.address, from: owner }) // unlock await lockManager.unlockAndRemoveManager(staking.address, owner) await assertRevert(lockManager[transferType](staking.address, owner, user2, DEFAULT_LOCK_AMOUNT, { from: user1 })/*, STAKING_ERRORS.ERROR_NOT_ENOUGH_LOCK */) }) } context('From Lock', async () => { context('within Staking app', () => { transfersFromLock('slash') }) context('to external balance (unstaked)', () => { transfersFromLock('slashAndUnstake') }) }) }) })