301 lines
15 KiB
TypeScript
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
|