Files
protocol/contracts/staking/test/pools_test.ts

254 lines
13 KiB
TypeScript

import {
chaiSetup,
constants,
expectTransactionFailedAsync,
provider,
txDefaults,
web3Wrapper,
} from '@0x/contracts-test-utils';
import { DummyERC20TokenContract } from '@0x/contracts-erc20';
import { BlockchainLifecycle } from '@0x/dev-utils';
import { RevertReason } from '@0x/types';
import { BigNumber } from '@0x/utils';
import * as chai from 'chai';
import { LogWithDecodedArgs } from 'ethereum-types';
import * as ethUtil from 'ethereumjs-util';
import * as _ from 'lodash';
import { constants as stakingConstants } from './utils/constants';
import { StakingWrapper } from './utils/staking_wrapper';
import { ERC20Wrapper, ERC20ProxyContract } from '@0x/contracts-asset-proxy';
import { StakingContract } from '../src';
import { StakerActor } from './actors/StakerActor';
import { DelegatorActor } from './actors/DelegatorActor';
import { PoolOperatorActor } from './actors/PoolOperatorActor';
import { MakerActor } from './actors/MakerActor';
chaiSetup.configure();
const expect = chai.expect;
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
// tslint:disable:no-unnecessary-type-assertion
describe('Staking Pool Management', () => {
// constants
const ZRX_TOKEN_DECIMALS = new BigNumber(18);
// tokens & addresses
let accounts: string[];
let owner: string;
let exchange: string;
let users: string[];
let zrxTokenContract: DummyERC20TokenContract;
let erc20ProxyContract: ERC20ProxyContract;
// wrappers
let stakingWrapper: StakingWrapper;
let erc20Wrapper: ERC20Wrapper;
// tests
before(async () => {
await blockchainLifecycle.startAsync();
});
after(async () => {
await blockchainLifecycle.revertAsync();
});
before(async () => {
// create accounts
accounts = await web3Wrapper.getAvailableAddressesAsync();
owner = accounts[0];
exchange = accounts[1];
users = accounts.slice(2);
// deploy erc20 proxy
erc20Wrapper = new ERC20Wrapper(provider, accounts, owner);
erc20ProxyContract = await erc20Wrapper.deployProxyAsync();
// deploy zrx token
[zrxTokenContract] = await erc20Wrapper.deployDummyTokensAsync(1, ZRX_TOKEN_DECIMALS);
await erc20Wrapper.setBalancesAndAllowancesAsync();
// deploy staking contracts
stakingWrapper = new StakingWrapper(provider, owner, erc20ProxyContract, zrxTokenContract, accounts);
await stakingWrapper.deployAndConfigureContracts();
});
beforeEach(async () => {
await blockchainLifecycle.startAsync();
});
afterEach(async () => {
await blockchainLifecycle.revertAsync();
});
describe('Staking Pool Management', () => {
it('Should successfully create a pool', async() => {
// test parameters
const operatorAddress = users[0];
const operatorShare = 39;
const poolOperator = new PoolOperatorActor(operatorAddress, stakingWrapper);
// create pool
const poolId = await poolOperator.createPoolAsync(operatorShare);
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
// check that the next pool id was incremented
const expectedNextPoolId = "0x0000000000000000000000000000000200000000000000000000000000000000";
const nextPoolId = await stakingWrapper.getNextPoolIdAsync();
expect(nextPoolId).to.be.equal(expectedNextPoolId);
});
it('Should successfully add/remove a maker to a pool', async() => {
// test parameters
const operatorAddress = users[0];
const operatorShare = 39;
const poolOperator = new PoolOperatorActor(operatorAddress, stakingWrapper);
const makerAddress = users[1];
const maker = new MakerActor(makerAddress, stakingWrapper);
// create pool
const poolId = await poolOperator.createPoolAsync(operatorShare);
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
// add maker to pool
const makerApproval = maker.signApprovalForStakingPool(poolId);
await poolOperator.addMakerToPoolAsync(poolId, makerAddress, makerApproval.signature);
// remove maker from pool
await poolOperator.removeMakerFromPoolAsync(poolId, makerAddress);
});
it('Should successfully add/remove multipler makers to the same pool', async() => {
// test parameters
const operatorAddress = users[0];
const operatorShare = 39;
const poolOperator = new PoolOperatorActor(operatorAddress, stakingWrapper);
const makerAddresses = users.slice(1, 4);
const makers = [
new MakerActor(makerAddresses[0], stakingWrapper),
new MakerActor(makerAddresses[1], stakingWrapper),
new MakerActor(makerAddresses[2], stakingWrapper),
];
// create pool
const poolId = await poolOperator.createPoolAsync(operatorShare);
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
// add makers to pool
const makerApprovals = [
makers[0].signApprovalForStakingPool(poolId),
makers[1].signApprovalForStakingPool(poolId),
makers[2].signApprovalForStakingPool(poolId),
];
await poolOperator.addMakerToPoolAsync(poolId, makerAddresses[0], makerApprovals[0].signature);
await poolOperator.addMakerToPoolAsync(poolId, makerAddresses[1], makerApprovals[1].signature);
await poolOperator.addMakerToPoolAsync(poolId, makerAddresses[2], makerApprovals[2].signature);
// remove maker from pool
await poolOperator.removeMakerFromPoolAsync(poolId, makerAddresses[0]);
await poolOperator.removeMakerFromPoolAsync(poolId, makerAddresses[1]);
// @TODO - this fails with `RuntimeError: VM Exception while processing transaction: revert` on Ganache
// await poolOperator.removeMakerFromPoolAsync(poolId, makerAddresses[2]);
});
it('Should fail to add the same maker twice', async() => {
// test parameters
const operatorAddress = users[0];
const operatorShare = 39;
const poolOperator = new PoolOperatorActor(operatorAddress, stakingWrapper);
const makerAddress = users[1];
const maker = new MakerActor(makerAddress, stakingWrapper);
// create pool
const poolId = await poolOperator.createPoolAsync(operatorShare);
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
// add maker to pool
const makerApproval = maker.signApprovalForStakingPool(poolId);
await poolOperator.addMakerToPoolAsync(poolId, makerAddress, makerApproval.signature);
// add same maker to pool again
await poolOperator.addMakerToPoolAsync(poolId, makerAddress, makerApproval.signature, RevertReason.MakerAddressAlreadyRegistered);
});
it('Should fail to remove a maker that does not exist', async() => {
// test parameters
const operatorAddress = users[0];
const operatorShare = 39;
const poolOperator = new PoolOperatorActor(operatorAddress, stakingWrapper);
const makerAddress = users[1];
const maker = new MakerActor(makerAddress, stakingWrapper);
// create pool
const poolId = await poolOperator.createPoolAsync(operatorShare);
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
// remove non-existent maker from pool
await poolOperator.removeMakerFromPoolAsync(poolId, makerAddress, RevertReason.MakerAddressNotRegistered);
});
it('Should fail to add a maker who signed with the wrong private key', async() => {
// test parameters
const operatorAddress = users[0];
const operatorShare = 39;
const poolOperator = new PoolOperatorActor(operatorAddress, stakingWrapper);
const makerAddress = users[1];
const badMakerPrivateKey = ethUtil.toBuffer('0x0000000000000000000000000000000000000000000000000000000000000001');
const maker = new MakerActor(makerAddress, stakingWrapper, badMakerPrivateKey);
// create pool
const poolId = await poolOperator.createPoolAsync(operatorShare);
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
// add maker to pool
const makerApproval = maker.signApprovalForStakingPool(poolId);
await poolOperator.addMakerToPoolAsync(poolId, makerAddress, makerApproval.signature, RevertReason.InvalidMakerSignature);
});
it('Should fail to add a maker who signed with the wrong staking contract address', async() => {
// test parameters
const operatorAddress = users[0];
const operatorShare = 39;
const poolOperator = new PoolOperatorActor(operatorAddress, stakingWrapper);
const makerAddress = users[1];
const forceMakerKeyLookup = undefined;
const notStakingContractAddress = users[2];
const maker = new MakerActor(makerAddress, stakingWrapper, forceMakerKeyLookup, notStakingContractAddress);
// create pool
const poolId = await poolOperator.createPoolAsync(operatorShare);
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
// add maker to pool
const makerApproval = maker.signApprovalForStakingPool(poolId);
await poolOperator.addMakerToPoolAsync(poolId, makerAddress, makerApproval.signature, RevertReason.InvalidMakerSignature);
});
it('Should fail to add a maker who signed with the wrong chain id', async() => {
// test parameters
const operatorAddress = users[0];
const operatorShare = 39;
const poolOperator = new PoolOperatorActor(operatorAddress, stakingWrapper);
const makerAddress = users[1];
const forceMakerKeyLookup = undefined;
const forceStakingContractLookup = undefined;
const badChainId = 209348;
const maker = new MakerActor(makerAddress, stakingWrapper, forceMakerKeyLookup, forceStakingContractLookup, badChainId);
// create pool
const poolId = await poolOperator.createPoolAsync(operatorShare);
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
// add maker to pool
const makerApproval = maker.signApprovalForStakingPool(poolId);
await poolOperator.addMakerToPoolAsync(poolId, makerAddress, makerApproval.signature, RevertReason.InvalidMakerSignature);
});
it('Should fail to add a maker when called by someone other than the pool operator', async() => {
// test parameters
const operatorAddress = users[0];
const operatorShare = 39;
const poolOperator = new PoolOperatorActor(operatorAddress, stakingWrapper);
const makerAddress = users[1];
const maker = new MakerActor(makerAddress, stakingWrapper);
const notOperatorAddress = users[2];
// create pool
const poolId = await poolOperator.createPoolAsync(operatorShare);
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
// add maker to pool
const makerApproval = maker.signApprovalForStakingPool(poolId);
await expectTransactionFailedAsync(
stakingWrapper.addMakerToPoolAsync(poolId, makerAddress, makerApproval.signature, notOperatorAddress),
RevertReason.OnlyCallableByPoolOperator
);
});
it('Should fail to remove a maker when called by someone other than the pool operator', async() => {
// test parameters
const operatorAddress = users[0];
const operatorShare = 39;
const poolOperator = new PoolOperatorActor(operatorAddress, stakingWrapper);
const makerAddress = users[1];
const maker = new MakerActor(makerAddress, stakingWrapper);
const notOperatorAddress = users[2];
// create pool
const poolId = await poolOperator.createPoolAsync(operatorShare);
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
// add maker to pool
const makerApproval = maker.signApprovalForStakingPool(poolId);
await poolOperator.addMakerToPoolAsync(poolId, makerAddress, makerApproval.signature);
// try to remove the maker address from an address other than the operator
await expectTransactionFailedAsync(
stakingWrapper.removeMakerFromPoolAsync(poolId, makerAddress, notOperatorAddress),
RevertReason.OnlyCallableByPoolOperator
);
});
});
});
// tslint:enable:no-unnecessary-type-assertion