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
 |