protocol/contracts/staking/test/unit_tests/staking_proxy_test.ts
F. Eugene Aumson f11d8a5bd8
@0x/order-utils refactors for v3: orderParsingUtils, signatureUtils, orderHashUtils, RevertErrors, transactionHashUtils (#2321)
* move orderParsingUtils from order-utils to connect

* Remove many functions from signatureUtils

Removed from the exported object, that is.  All of them are used in
other existing code, so they were all moved to be as local to their
usage as possible.

* remove orderHashUtils.isValidOrderHash()

* Move all *RevertErrors from order-utils...

...into their respective @0x/contracts- packages.

* Refactor @0x/order-utils' orderHashUtils away

- Move existing routines into @0x/contracts-test-utils

- Migrate non-contract-test callers to a newly-exposed getOrderHash()
method in DevUtils.

* Move all *RevertErrors from @0x/utils...

...into their respective @0x/contracts- packages.

* rm transactionHashUtils.isValidTransactionHash()

* DevUtils.sol: Fail yarn test if too big to deploy

* Refactor @0x/order-utils transactionHashUtils away

- Move existing routines into @0x/contracts-test-utils

- Migrate non-contract-test callers to a newly-exposed
getTransactionHash() method in DevUtils.

* Consolidate `Removed export...` CHANGELOG entries

* Rm EthBalanceChecker from devutils wrapper exports

* Stop importing from '.' or '.../src'

* fix builds

* fix prettier; dangling promise

* increase max bundle size
2019-11-14 17:14:24 -05:00

302 lines
15 KiB
TypeScript

import { blockchainTests, constants, expect, verifyEventsFromLogs } from '@0x/contracts-test-utils';
import { AuthorizableRevertErrors } from '@0x/contracts-utils';
import { BigNumber } 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';
import StakingRevertErrors = require('../../src/staking_revert_errors');
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