309 lines
11 KiB
JavaScript
309 lines
11 KiB
JavaScript
|
const { assertRevert } = require('@aragon/contract-helpers-test/assertThrow')
|
|||
|
const { bn, assertBn, MAX_UINT64 } = require('@aragon/contract-helpers-test/numbers')
|
|||
|
|
|||
|
const { deploy } = require('../helpers/deploy')(artifacts)
|
|||
|
const {
|
|||
|
UserState,
|
|||
|
approveAndStakeWithState,
|
|||
|
approveStakeAndLockWithState,
|
|||
|
unstakeWithState,
|
|||
|
unlockWithState,
|
|||
|
unlockFromManagerWithState,
|
|||
|
transferWithState,
|
|||
|
transferAndUnstakeWithState,
|
|||
|
slashWithState,
|
|||
|
slashAndUnstakeWithState,
|
|||
|
slashFromContractWithState,
|
|||
|
slashAndUnstakeFromContractWithState,
|
|||
|
checkInvariants,
|
|||
|
} = require('../helpers/helpers')(artifacts)
|
|||
|
const { DEFAULT_STAKE_AMOUNT, DEFAULT_LOCK_AMOUNT, EMPTY_DATA, ZERO_ADDRESS } = require('../helpers/constants')
|
|||
|
|
|||
|
contract('Staking app, Locking funds flows', ([_, owner, user1, user2, user3]) => {
|
|||
|
let staking, lockManager, users, managers, token
|
|||
|
|
|||
|
beforeEach(async () => {
|
|||
|
const deployment = await deploy(owner)
|
|||
|
staking = deployment.staking
|
|||
|
lockManager = deployment.lockManager
|
|||
|
|
|||
|
token = deployment.token
|
|||
|
|
|||
|
// fund users and create user state objects
|
|||
|
users = []
|
|||
|
const userAddresses = [user1, user2, user3]
|
|||
|
await Promise.all(userAddresses.map(async (userAddress, index) => {
|
|||
|
const amount = DEFAULT_STAKE_AMOUNT.mul(bn(userAddresses.length - index))
|
|||
|
users.push(new UserState(userAddress, amount))
|
|||
|
await token.transfer(userAddress, amount, { from: owner })
|
|||
|
}))
|
|||
|
|
|||
|
// managers
|
|||
|
managers = users.reduce((result, user) => {
|
|||
|
result.push(user.address)
|
|||
|
return result
|
|||
|
}, [])
|
|||
|
managers.push(lockManager.address)
|
|||
|
})
|
|||
|
|
|||
|
describe('same origin and destiny', () => {
|
|||
|
context('when user hasn’t staked', () => {
|
|||
|
it('check invariants', async () => {
|
|||
|
await checkInvariants({ staking, users, managers })
|
|||
|
})
|
|||
|
})
|
|||
|
|
|||
|
context('when user has staked', () => {
|
|||
|
const stakeAmount = DEFAULT_STAKE_AMOUNT
|
|||
|
|
|||
|
beforeEach('stakes', async () => {
|
|||
|
await checkInvariants({ staking, users, managers })
|
|||
|
await approveAndStakeWithState({ staking, amount: stakeAmount, user: users[0] })
|
|||
|
await checkInvariants({ staking, users, managers })
|
|||
|
})
|
|||
|
|
|||
|
context('when user hasn’t locked', () => {
|
|||
|
it('unstakes half', async () => {
|
|||
|
await unstakeWithState({ staking, unstakeAmount: stakeAmount.div(bn(2)), user: users[0] })
|
|||
|
await checkInvariants({ staking, users, managers })
|
|||
|
})
|
|||
|
|
|||
|
it('unstakes all', async () => {
|
|||
|
await unstakeWithState({ staking, unstakeAmount: stakeAmount, user: users[0] })
|
|||
|
await checkInvariants({ staking, users, managers })
|
|||
|
})
|
|||
|
})
|
|||
|
|
|||
|
context('when user has locked', () => {
|
|||
|
const lockAmount = DEFAULT_LOCK_AMOUNT
|
|||
|
|
|||
|
const moveFunds = ({ isContract, canUnlock = false }) => {
|
|||
|
let lockManagerAddress
|
|||
|
|
|||
|
beforeEach('stakes and locks', async () => {
|
|||
|
lockManagerAddress = isContract ? lockManager.address : user3
|
|||
|
if (isContract && canUnlock) {
|
|||
|
await lockManager.setResult(true)
|
|||
|
}
|
|||
|
|
|||
|
await checkInvariants({ staking, users, managers })
|
|||
|
await approveStakeAndLockWithState({
|
|||
|
staking,
|
|||
|
manager: lockManagerAddress,
|
|||
|
stakeAmount,
|
|||
|
allowanceAmount: stakeAmount,
|
|||
|
lockAmount,
|
|||
|
user: users[0]
|
|||
|
})
|
|||
|
await checkInvariants({ staking, users, managers })
|
|||
|
})
|
|||
|
|
|||
|
const unstake = async (unstakeAmount) => {
|
|||
|
await unstakeWithState({ staking, unstakeAmount, user: users[0] })
|
|||
|
await checkInvariants({ staking, users, managers })
|
|||
|
}
|
|||
|
|
|||
|
it('unstakes remaining', async () => {
|
|||
|
await unstake(stakeAmount.sub(lockAmount))
|
|||
|
})
|
|||
|
|
|||
|
const unlockAndUnstake = async (unlockAmount) => {
|
|||
|
await unlockWithState({ staking, managerAddress: lockManagerAddress, unlockAmount, user: users[0]})
|
|||
|
await unstake(stakeAmount.sub(lockAmount.sub(unlockAmount)))
|
|||
|
}
|
|||
|
|
|||
|
const unlockAndUnstakeFromManager = async (unlockAmount) => {
|
|||
|
await unlockFromManagerWithState({ staking, lockManager, unlockAmount, user: users[0]})
|
|||
|
await unstake(stakeAmount.sub(lockAmount.sub(unlockAmount)))
|
|||
|
}
|
|||
|
|
|||
|
if (canUnlock) {
|
|||
|
it('owner unlocks half and then unstakes', async () => {
|
|||
|
await unlockAndUnstake(lockAmount.div(bn(2)))
|
|||
|
})
|
|||
|
|
|||
|
it('owner unlocks all and then unstakes', async () => {
|
|||
|
await unlockAndUnstake(lockAmount)
|
|||
|
})
|
|||
|
} else {
|
|||
|
it('owner cannot unlock', async () => {
|
|||
|
await assertRevert(staking.unlock(users[0].address, lockManagerAddress, bn(1), { from: users[0].address }))
|
|||
|
})
|
|||
|
|
|||
|
if (isContract) {
|
|||
|
it('manager unlocks half and then owner unstakes', async () => {
|
|||
|
await unlockAndUnstakeFromManager(lockAmount.div(bn(2)))
|
|||
|
})
|
|||
|
|
|||
|
it('manager unlocks all and then owner unstakes', async () => {
|
|||
|
await unlockAndUnstakeFromManager(lockAmount)
|
|||
|
})
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
context('when lock manager is EOA', () => {
|
|||
|
moveFunds({ isContract: false, canUnlock: false })
|
|||
|
})
|
|||
|
|
|||
|
context('when lock manager is contract', () => {
|
|||
|
context('when lock manager allows to unlock', () => {
|
|||
|
moveFunds({ isContract: true, canUnlock: true })
|
|||
|
})
|
|||
|
|
|||
|
context('when lock manager doesn’t allow to unlock', () => {
|
|||
|
moveFunds({ isContract: true, canUnlock: false })
|
|||
|
})
|
|||
|
})
|
|||
|
})
|
|||
|
})
|
|||
|
})
|
|||
|
|
|||
|
describe('different origin and destiny', () => {
|
|||
|
context('when user hasn’t staked', () => {
|
|||
|
it('check invariants', async () => {
|
|||
|
await checkInvariants({ staking, users, managers })
|
|||
|
})
|
|||
|
})
|
|||
|
|
|||
|
context('when user has staked', () => {
|
|||
|
const stakeAmount = DEFAULT_STAKE_AMOUNT
|
|||
|
|
|||
|
beforeEach('stakes', async () => {
|
|||
|
await checkInvariants({ staking, users, managers })
|
|||
|
await approveAndStakeWithState({ staking, amount: stakeAmount, user: users[0] })
|
|||
|
await checkInvariants({ staking, users, managers })
|
|||
|
})
|
|||
|
|
|||
|
context('when user hasn’t locked', () => {
|
|||
|
context('to staking balance', () => {
|
|||
|
it('transfers half', async () => {
|
|||
|
await transferWithState({ staking, transferAmount: stakeAmount.div(bn(2)), userFrom: users[0], userTo: users[1] })
|
|||
|
await checkInvariants({ staking, users, managers })
|
|||
|
})
|
|||
|
|
|||
|
it('transfers all', async () => {
|
|||
|
await transferWithState({ staking, transferAmount: stakeAmount, userFrom: users[0], userTo: users[1] })
|
|||
|
await checkInvariants({ staking, users, managers })
|
|||
|
})
|
|||
|
})
|
|||
|
|
|||
|
context('to external wallet', () => {
|
|||
|
it('transfers half', async () => {
|
|||
|
await transferAndUnstakeWithState({ staking, transferAmount: stakeAmount.div(bn(2)), userFrom: users[0], userTo: users[1] })
|
|||
|
await checkInvariants({ staking, users, managers })
|
|||
|
})
|
|||
|
|
|||
|
it('transfers all', async () => {
|
|||
|
await transferAndUnstakeWithState({ staking, transferAmount: stakeAmount, userFrom: users[0], userTo: users[1] })
|
|||
|
await checkInvariants({ staking, users, managers })
|
|||
|
})
|
|||
|
})
|
|||
|
})
|
|||
|
|
|||
|
context('when user has locked', () => {
|
|||
|
const lockAmount = DEFAULT_LOCK_AMOUNT
|
|||
|
|
|||
|
const moveFunds = ({ isContract, canUnlock = false, toStaking }) => {
|
|||
|
let lockManagerAddress
|
|||
|
|
|||
|
beforeEach('stakes and locks', async () => {
|
|||
|
lockManagerAddress = isContract ? lockManager.address : user3
|
|||
|
if (isContract && canUnlock) {
|
|||
|
await lockManager.setResult(true)
|
|||
|
}
|
|||
|
|
|||
|
await checkInvariants({ staking, users, managers })
|
|||
|
await approveStakeAndLockWithState({
|
|||
|
staking,
|
|||
|
manager: lockManagerAddress,
|
|||
|
stakeAmount,
|
|||
|
allowanceAmount: stakeAmount,
|
|||
|
lockAmount,
|
|||
|
user: users[0]
|
|||
|
})
|
|||
|
await checkInvariants({ staking, users, managers })
|
|||
|
})
|
|||
|
|
|||
|
const transfer = async (transferAmount) => {
|
|||
|
await transferWithState({ staking, transferAmount, userFrom: users[0], userTo: users[1] })
|
|||
|
await checkInvariants({ staking, users, managers })
|
|||
|
}
|
|||
|
|
|||
|
it('transfers remaining', async () => {
|
|||
|
await transfer(stakeAmount.sub(lockAmount))
|
|||
|
})
|
|||
|
|
|||
|
const slashAndTransfer = async (slashAmount) => {
|
|||
|
if (toStaking) {
|
|||
|
await slashWithState({ staking, slashAmount, userFrom: users[0], userTo: users[1], managerAddress: lockManagerAddress })
|
|||
|
} else {
|
|||
|
await slashAndUnstakeWithState({ staking, slashAmount, userFrom: users[0], userTo: users[1], managerAddress: lockManagerAddress })
|
|||
|
}
|
|||
|
await transfer(stakeAmount.sub(lockAmount.sub(slashAmount)))
|
|||
|
}
|
|||
|
|
|||
|
const slashAndTransferFromContract = async (slashAmount) => {
|
|||
|
if (toStaking) {
|
|||
|
await slashFromContractWithState({ staking, slashAmount, userFrom: users[0], userTo: users[1], lockManager })
|
|||
|
} else {
|
|||
|
await slashAndUnstakeFromContractWithState({ staking, slashAmount, userFrom: users[0], userTo: users[1], lockManager })
|
|||
|
}
|
|||
|
await transfer(stakeAmount.sub(lockAmount.sub(slashAmount)))
|
|||
|
}
|
|||
|
|
|||
|
if (isContract) {
|
|||
|
it('manager unlockes half and then owner transfers', async () => {
|
|||
|
await slashAndTransferFromContract(lockAmount.div(bn(2)))
|
|||
|
})
|
|||
|
|
|||
|
it('manager slashes all and then owner transfers', async () => {
|
|||
|
await slashAndTransferFromContract(lockAmount)
|
|||
|
})
|
|||
|
} else {
|
|||
|
it('manager slashes half and then transfers', async () => {
|
|||
|
await slashAndTransfer(lockAmount.div(bn(2)))
|
|||
|
})
|
|||
|
|
|||
|
it('manager slashes all and then transfers', async () => {
|
|||
|
await slashAndTransfer(lockAmount)
|
|||
|
})
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
context('when lock manager is EOA', () => {
|
|||
|
context('to staking balance', () => {
|
|||
|
moveFunds({ isContract: false, canUnlock: false, toStaking: true })
|
|||
|
})
|
|||
|
|
|||
|
context('to external wallet', () => {
|
|||
|
moveFunds({ isContract: false, canUnlock: false, toStaking: false })
|
|||
|
})
|
|||
|
})
|
|||
|
|
|||
|
context('when lock manager is contract', () => {
|
|||
|
context('when lock manager allows to unlock', () => {
|
|||
|
context('to staking balance', () => {
|
|||
|
moveFunds({ isContract: true, canUnlock: true, toStaking: true })
|
|||
|
})
|
|||
|
|
|||
|
context('to external wallet', () => {
|
|||
|
moveFunds({ isContract: true, canUnlock: true, toStaking: false })
|
|||
|
})
|
|||
|
})
|
|||
|
|
|||
|
context('when lock manager doesn’t allow to unlock', () => {
|
|||
|
context('to staking balance', () => {
|
|||
|
moveFunds({ isContract: true, canUnlock: false, toStaking: true })
|
|||
|
})
|
|||
|
|
|||
|
context('to external wallet', () => {
|
|||
|
moveFunds({ isContract: true, canUnlock: false, toStaking: false })
|
|||
|
})
|
|||
|
})
|
|||
|
})
|
|||
|
})
|
|||
|
})
|
|||
|
})
|
|||
|
})
|