protocol/contracts/staking/test/unit_tests/staking_proxy_test.ts
2019-11-27 13:02:37 +11:00

301 lines
15 KiB
TypeScript

import { blockchainTests, constants, expect, verifyEventsFromLogs } from '@0x/contracts-test-utils';
import { AuthorizableRevertErrors } from '@0x/contracts-utils';
import { BigNumber, StakingRevertErrors } from '@0x/utils';
import * as _ from 'lodash';
import { artifacts } from '../artifacts';
import {
StakingProxyEvents,
TestProxyDestinationContract,
TestProxyDestinationEvents,
TestStakingProxyUnitContract,
} from '../wrappers';
import { constants as stakingConstants } from '../../src/constants';
blockchainTests.resets('StakingProxy unit tests', env => {
const testString = 'Hello, World!';
const testRevertString = 'Goodbye, World!';
let accounts: string[];
let owner: string;
let authorizedAddress: string;
let notAuthorizedAddresses: string[];
let testProxyContract: TestStakingProxyUnitContract;
let testContractViaProxy: TestProxyDestinationContract;
let testContract: TestProxyDestinationContract;
let testContract2: TestProxyDestinationContract;
before(async () => {
// Create accounts
accounts = await env.getAccountAddressesAsync();
[owner, authorizedAddress, ...notAuthorizedAddresses] = accounts;
// Deploy contracts
testContract = await TestProxyDestinationContract.deployFrom0xArtifactAsync(
artifacts.TestProxyDestination,
env.provider,
env.txDefaults,
artifacts,
);
testContract2 = await TestProxyDestinationContract.deployFrom0xArtifactAsync(
artifacts.TestProxyDestination,
env.provider,
env.txDefaults,
artifacts,
);
testProxyContract = await TestStakingProxyUnitContract.deployFrom0xArtifactAsync(
artifacts.TestStakingProxyUnit,
env.provider,
env.txDefaults,
artifacts,
testContract.address,
);
const logDecoderDependencies = _.mapValues(artifacts, v => v.compilerOutput.abi);
testContractViaProxy = new TestProxyDestinationContract(
testProxyContract.address,
env.provider,
env.txDefaults,
logDecoderDependencies,
);
// Add authorized address to Staking Proxy
await testProxyContract.addAuthorizedAddress(authorizedAddress).sendTransactionAsync({ from: owner });
});
describe('Fallback function', () => {
it('should pass back the return value of the destination contract', async () => {
const returnValue = await testContractViaProxy.echo(testString).callAsync();
expect(returnValue).to.equal(testString);
});
it('should revert with correct value when destination reverts', async () => {
return expect(testContractViaProxy.die().callAsync()).to.revertWith(testRevertString);
});
it('should revert if no staking contract is attached', async () => {
await testProxyContract.detachStakingContract().awaitTransactionSuccessAsync({ from: authorizedAddress });
const expectedError = new StakingRevertErrors.ProxyDestinationCannotBeNilError();
const tx = testContractViaProxy.echo(testString).callAsync();
return expect(tx).to.revertWith(expectedError);
});
});
describe('attachStakingContract', () => {
it('should successfully attaching a new staking contract', async () => {
// Cache existing staking contract and attach a new one
const initStakingContractAddress = await testProxyContract.stakingContract().callAsync();
const txReceipt = await testProxyContract
.attachStakingContract(testContract2.address)
.awaitTransactionSuccessAsync({ from: authorizedAddress });
// Validate `ContractAttachedToProxy` event
verifyEventsFromLogs(
txReceipt.logs,
[
{
newStakingContractAddress: testContract2.address,
},
],
StakingProxyEvents.StakingContractAttachedToProxy,
);
// Check that `init` was called on destination contract
verifyEventsFromLogs(
txReceipt.logs,
[
{
initCalled: true,
},
],
TestProxyDestinationEvents.InitCalled,
);
// Validate new staking contract address
const finalStakingContractAddress = await testProxyContract.stakingContract().callAsync();
expect(finalStakingContractAddress).to.be.equal(testContract2.address);
expect(finalStakingContractAddress).to.not.equal(initStakingContractAddress);
});
it('should revert if call to `init` on new staking contract fails', async () => {
await testProxyContract.setInitFailFlag().awaitTransactionSuccessAsync();
const tx = testProxyContract.attachStakingContract(testContract2.address).awaitTransactionSuccessAsync({
from: authorizedAddress,
});
const expectedError = 'INIT_FAIL_FLAG_SET';
return expect(tx).to.revertWith(expectedError);
});
it('should revert if called by unauthorized address', async () => {
const tx = testProxyContract.attachStakingContract(testContract2.address).awaitTransactionSuccessAsync({
from: notAuthorizedAddresses[0],
});
const expectedError = new AuthorizableRevertErrors.SenderNotAuthorizedError(notAuthorizedAddresses[0]);
return expect(tx).to.revertWith(expectedError);
});
});
describe('detachStakingContract', () => {
it('should detach staking contract', async () => {
// Cache existing staking contract and attach a new one
const initStakingContractAddress = await testProxyContract.stakingContract().callAsync();
const txReceipt = await testProxyContract.detachStakingContract().awaitTransactionSuccessAsync({
from: authorizedAddress,
});
// Validate that event was emitted
verifyEventsFromLogs(txReceipt.logs, [{}], StakingProxyEvents.StakingContractDetachedFromProxy);
// Validate staking contract address was unset
const finalStakingContractAddress = await testProxyContract.stakingContract().callAsync();
expect(finalStakingContractAddress).to.be.equal(stakingConstants.NIL_ADDRESS);
expect(finalStakingContractAddress).to.not.equal(initStakingContractAddress);
});
it('should revert if called by unauthorized address', async () => {
const tx = testProxyContract.detachStakingContract().awaitTransactionSuccessAsync({
from: notAuthorizedAddresses[0],
});
const expectedError = new AuthorizableRevertErrors.SenderNotAuthorizedError(notAuthorizedAddresses[0]);
return expect(tx).to.revertWith(expectedError);
});
});
describe('batchExecute', () => {
it('should execute no-op if no calls to make', async () => {
await testProxyContract.batchExecute([]).awaitTransactionSuccessAsync();
});
it('should call one function and return the output', async () => {
const calls = [testContract.echo(testString).getABIEncodedTransactionData()];
const rawResults = await testProxyContract.batchExecute(calls).callAsync();
expect(rawResults.length).to.equal(1);
const returnValues = [testContract.getABIDecodedReturnData<{}>('echo', rawResults[0])];
expect(returnValues[0]).to.equal(testString);
});
it('should call multiple functions and return their outputs', async () => {
const calls = [
testContract.echo(testString).getABIEncodedTransactionData(),
testContract.doMath(new BigNumber(2), new BigNumber(1)).getABIEncodedTransactionData(),
];
const rawResults = await testProxyContract.batchExecute(calls).callAsync();
expect(rawResults.length).to.equal(2);
const returnValues = [
testContract.getABIDecodedReturnData<string>('echo', rawResults[0]),
testContract.getABIDecodedReturnData<BigNumber[]>('doMath', rawResults[1]),
];
expect(returnValues[0]).to.equal(testString);
expect(returnValues[1][0]).to.bignumber.equal(new BigNumber(3));
expect(returnValues[1][1]).to.bignumber.equal(new BigNumber(1));
});
it('should revert if a call reverts', async () => {
const calls = [
testContract.echo(testString).getABIEncodedTransactionData(),
testContract.die().getABIEncodedTransactionData(),
testContract.doMath(new BigNumber(2), new BigNumber(1)).getABIEncodedTransactionData(),
];
const tx = testProxyContract.batchExecute(calls).callAsync();
const expectedError = testRevertString;
return expect(tx).to.revertWith(expectedError);
});
it('should revert if no staking contract is attached', async () => {
await testProxyContract.detachStakingContract().awaitTransactionSuccessAsync({ from: authorizedAddress });
const calls = [testContract.echo(testString).getABIEncodedTransactionData()];
const tx = testProxyContract.batchExecute(calls).callAsync();
const expectedError = new StakingRevertErrors.ProxyDestinationCannotBeNilError();
return expect(tx).to.revertWith(expectedError);
});
});
describe('assertValidStorageParams', () => {
const validStorageParams = {
epochDurationInSeconds: new BigNumber(stakingConstants.ONE_DAY_IN_SECONDS * 5),
cobbDouglasAlphaNumerator: new BigNumber(1),
cobbDouglasAlphaDenominator: new BigNumber(1),
rewardDelegatedStakeWeight: constants.PPM_DENOMINATOR,
minimumPoolStake: new BigNumber(100),
};
it('should not revert if all storage params are valid', async () => {
await testProxyContract.setTestStorageParams(validStorageParams).awaitTransactionSuccessAsync();
await testProxyContract.assertValidStorageParams().callAsync();
});
it('should revert if `epochDurationInSeconds` is less than 5 days', async () => {
const invalidStorageParams = {
...validStorageParams,
epochDurationInSeconds: new BigNumber(0),
};
await testProxyContract.setTestStorageParams(invalidStorageParams).awaitTransactionSuccessAsync();
const tx = testProxyContract.assertValidStorageParams().callAsync();
const expectedError = new StakingRevertErrors.InvalidParamValueError(
StakingRevertErrors.InvalidParamValueErrorCodes.InvalidEpochDuration,
);
return expect(tx).to.revertWith(expectedError);
});
it('should revert if `epochDurationInSeconds` is greater than 30 days', async () => {
const invalidStorageParams = {
...validStorageParams,
epochDurationInSeconds: new BigNumber(stakingConstants.ONE_DAY_IN_SECONDS * 31),
};
await testProxyContract.setTestStorageParams(invalidStorageParams).awaitTransactionSuccessAsync();
const tx = testProxyContract.assertValidStorageParams().callAsync();
const expectedError = new StakingRevertErrors.InvalidParamValueError(
StakingRevertErrors.InvalidParamValueErrorCodes.InvalidEpochDuration,
);
return expect(tx).to.revertWith(expectedError);
});
it('should revert if `cobbDouglasAlphaNumerator` is greater than `cobbDouglasAlphaDenominator`', async () => {
const invalidStorageParams = {
...validStorageParams,
cobbDouglasAlphaNumerator: new BigNumber(2),
cobbDouglasAlphaDenominator: new BigNumber(1),
};
await testProxyContract.setTestStorageParams(invalidStorageParams).awaitTransactionSuccessAsync();
const tx = testProxyContract.assertValidStorageParams().callAsync();
const expectedError = new StakingRevertErrors.InvalidParamValueError(
StakingRevertErrors.InvalidParamValueErrorCodes.InvalidCobbDouglasAlpha,
);
return expect(tx).to.revertWith(expectedError);
});
it('should revert if `cobbDouglasAlphaDenominator` equals zero', async () => {
const invalidStorageParams = {
...validStorageParams,
cobbDouglasAlphaDenominator: new BigNumber(0),
};
await testProxyContract.setTestStorageParams(invalidStorageParams).awaitTransactionSuccessAsync();
const tx = testProxyContract.assertValidStorageParams().callAsync();
const expectedError = new StakingRevertErrors.InvalidParamValueError(
StakingRevertErrors.InvalidParamValueErrorCodes.InvalidCobbDouglasAlpha,
);
return expect(tx).to.revertWith(expectedError);
});
it('should revert if `rewardDelegatedStakeWeight` is greater than PPM_DENOMINATOR', async () => {
const invalidStorageParams = {
...validStorageParams,
rewardDelegatedStakeWeight: new BigNumber(constants.PPM_DENOMINATOR + 1),
};
await testProxyContract.setTestStorageParams(invalidStorageParams).awaitTransactionSuccessAsync();
const tx = testProxyContract.assertValidStorageParams().callAsync();
const expectedError = new StakingRevertErrors.InvalidParamValueError(
StakingRevertErrors.InvalidParamValueErrorCodes.InvalidRewardDelegatedStakeWeight,
);
return expect(tx).to.revertWith(expectedError);
});
it('should revert if `minimumPoolStake` is less than two', async () => {
const invalidStorageParams = {
...validStorageParams,
minimumPoolStake: new BigNumber(1),
};
await testProxyContract.setTestStorageParams(invalidStorageParams).awaitTransactionSuccessAsync();
const tx = testProxyContract.assertValidStorageParams().callAsync();
const expectedError = new StakingRevertErrors.InvalidParamValueError(
StakingRevertErrors.InvalidParamValueErrorCodes.InvalidMinimumPoolStake,
);
return expect(tx).to.revertWith(expectedError);
});
});
});
// tslint:disable: max-file-line-count