@0x:contracts-staking Added tests for protocol fees
				
					
				
			This commit is contained in:
		@@ -99,37 +99,42 @@ contract MixinExchangeFees is
 | 
			
		||||
        payable
 | 
			
		||||
        onlyExchange
 | 
			
		||||
    {
 | 
			
		||||
        // Get the pool id of the maker address, and use this pool id to get the amount
 | 
			
		||||
        // of fees collected during this epoch.
 | 
			
		||||
        bytes32 poolId = getStakingPoolIdOfMaker(makerAddress);
 | 
			
		||||
        uint256 _feesCollectedThisEpoch = protocolFeesThisEpochByPool[poolId];
 | 
			
		||||
 | 
			
		||||
        if (msg.value == 0) {
 | 
			
		||||
            // Transfer the protocol fee to this address.
 | 
			
		||||
            erc20Proxy.transferFrom(
 | 
			
		||||
                wethAssetData,
 | 
			
		||||
                payerAddress,
 | 
			
		||||
                address(this),
 | 
			
		||||
                protocolFeePaid
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            // Update the amount of protocol fees paid to this pool this epoch.
 | 
			
		||||
            protocolFeesThisEpochByPool[poolId] = _feesCollectedThisEpoch.safeAdd(protocolFeePaid);
 | 
			
		||||
 | 
			
		||||
        } else if (msg.value == protocolFeePaid) {
 | 
			
		||||
            // Update the amount of protocol fees paid to this pool this epoch.
 | 
			
		||||
            protocolFeesThisEpochByPool[poolId] = _feesCollectedThisEpoch.safeAdd(protocolFeePaid);
 | 
			
		||||
        } else {
 | 
			
		||||
            // If the wrong message value was sent, revert with a rich error.
 | 
			
		||||
        // If the protocol fee payment is invalid, revert with a rich error.
 | 
			
		||||
        if (
 | 
			
		||||
            protocolFeePaid == 0 ||
 | 
			
		||||
            (msg.value != protocolFeePaid && msg.value != 0)
 | 
			
		||||
        ) {
 | 
			
		||||
            LibRichErrors.rrevert(LibStakingRichErrors.InvalidProtocolFeePaymentError(
 | 
			
		||||
                protocolFeePaid,
 | 
			
		||||
                msg.value
 | 
			
		||||
            ));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // If there were no fees collected prior to this payment, activate the pool that is being paid.
 | 
			
		||||
        if (_feesCollectedThisEpoch == 0) {
 | 
			
		||||
            activePoolsThisEpoch.push(poolId);
 | 
			
		||||
        // Transfer the protocol fee to this address if it should be paid in WETH.
 | 
			
		||||
        if (msg.value == 0) {
 | 
			
		||||
            erc20Proxy.transferFrom(
 | 
			
		||||
                WETH_ASSET_DATA,
 | 
			
		||||
                payerAddress,
 | 
			
		||||
                address(this),
 | 
			
		||||
                protocolFeePaid
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Get the pool id of the maker address.
 | 
			
		||||
        bytes32 poolId = getStakingPoolIdOfMaker(makerAddress);
 | 
			
		||||
 | 
			
		||||
        // Only attribute the protocol fee payment to a pool if the maker is registered to a pool.
 | 
			
		||||
        if (poolId != NIL_POOL_ID) {
 | 
			
		||||
            // Use the maker pool id to get the amount of fees collected during this epoch in the pool.
 | 
			
		||||
            uint256 _feesCollectedThisEpoch = protocolFeesThisEpochByPool[poolId];
 | 
			
		||||
 | 
			
		||||
            // Update the amount of protocol fees paid to this pool this epoch.
 | 
			
		||||
            protocolFeesThisEpochByPool[poolId] = _feesCollectedThisEpoch.safeAdd(protocolFeePaid);
 | 
			
		||||
 | 
			
		||||
            // If there were no fees collected prior to this payment, activate the pool that is being paid.
 | 
			
		||||
            if (_feesCollectedThisEpoch == 0) {
 | 
			
		||||
                activePoolsThisEpoch.push(poolId);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -179,6 +184,18 @@ contract MixinExchangeFees is
 | 
			
		||||
        return protocolFeesThisEpochByPool[poolId];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev Withdraws the entire WETH balance of the contract.
 | 
			
		||||
    function _unwrapWETH()
 | 
			
		||||
        internal
 | 
			
		||||
    {
 | 
			
		||||
        uint256 wethBalance = IEtherToken(WETH_ADDRESS).balanceOf(address(this));
 | 
			
		||||
 | 
			
		||||
        // Don't withdraw WETH if the WETH balance is zero as a gas optimization.
 | 
			
		||||
        if (wethBalance != 0) {
 | 
			
		||||
            IEtherToken(WETH_ADDRESS).withdraw(wethBalance);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev Pays rewards to market making pools that were active this epoch.
 | 
			
		||||
    /// Each pool receives a portion of the fees generated this epoch (see _cobbDouglas) that is
 | 
			
		||||
    /// proportional to (i) the fee volume attributed to their pool over the epoch, and
 | 
			
		||||
@@ -204,11 +221,12 @@ contract MixinExchangeFees is
 | 
			
		||||
            uint256 finalContractBalance
 | 
			
		||||
        )
 | 
			
		||||
    {
 | 
			
		||||
        // step 1/4 - withdraw the entire wrapper ether balance into this contract.
 | 
			
		||||
        //            WETH is unwrapped here to keep `payProtocolFee()` calls relatively cheap.
 | 
			
		||||
        uint256 wethBalance = IEtherToken(WETH_ADDRESS).balanceOf(address(this));
 | 
			
		||||
        IEtherToken(WETH_ADDRESS).withdraw(wethBalance);
 | 
			
		||||
        // step 1/4 - withdraw the entire wrapped ether balance into this contract. WETH
 | 
			
		||||
        //            is unwrapped here to keep `payProtocolFee()` calls relatively cheap,
 | 
			
		||||
        //            and WETH is only withdrawn if this contract's WETH balance is nonzero.
 | 
			
		||||
        _unwrapWETH();
 | 
			
		||||
 | 
			
		||||
        // Initialize initial values
 | 
			
		||||
        totalActivePools = activePoolsThisEpoch.length;
 | 
			
		||||
        totalFeesCollected = 0;
 | 
			
		||||
        totalWeightedStake = 0;
 | 
			
		||||
 
 | 
			
		||||
@@ -34,6 +34,8 @@ contract MixinConstants is
 | 
			
		||||
 | 
			
		||||
    bytes32 constant internal NIL_POOL_ID = 0x0000000000000000000000000000000000000000000000000000000000000000;
 | 
			
		||||
 | 
			
		||||
    bytes32 constant internal NIL_POOL_ID = 0x0000000000000000000000000000000000000000000000000000000000000000;
 | 
			
		||||
 | 
			
		||||
    address constant internal NIL_ADDRESS = 0x0000000000000000000000000000000000000000;
 | 
			
		||||
 | 
			
		||||
    bytes32 constant internal UNKNOWN_STAKING_POOL_ID = 0x0000000000000000000000000000000000000000000000000000000000000000;
 | 
			
		||||
@@ -46,4 +48,7 @@ contract MixinConstants is
 | 
			
		||||
 | 
			
		||||
    // The address of the canonical WETH contract -- this will be used as an alternative to ether for paying protocol fees.
 | 
			
		||||
    address constant internal WETH_ADDRESS = address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
 | 
			
		||||
 | 
			
		||||
    // abi.encodeWithSelector(IAssetData(address(0)).ERC20Token.selector, WETH_ADDRESS)
 | 
			
		||||
    bytes constant internal WETH_ASSET_DATA = hex"f47261b0000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2";
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -37,20 +37,11 @@ contract MixinStorage is
 | 
			
		||||
    constructor()
 | 
			
		||||
        public
 | 
			
		||||
        Ownable()
 | 
			
		||||
    {
 | 
			
		||||
        // Set the erc20 asset proxy data.
 | 
			
		||||
        wethAssetData = abi.encodeWithSelector(
 | 
			
		||||
            IAssetData(address(0)).ERC20Token.selector,
 | 
			
		||||
            WETH_ADDRESS
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    {} // solhint-disable-line no-empty-blocks
 | 
			
		||||
 | 
			
		||||
    // 0x ERC20 Proxy
 | 
			
		||||
    IAssetProxy internal erc20Proxy;
 | 
			
		||||
 | 
			
		||||
    // The asset data that should be sent to transfer weth
 | 
			
		||||
    bytes internal wethAssetData;
 | 
			
		||||
 | 
			
		||||
    // address of staking contract
 | 
			
		||||
    address internal stakingContract;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										41
									
								
								contracts/staking/contracts/test/TestProtocolFees.sol
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								contracts/staking/contracts/test/TestProtocolFees.sol
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,41 @@
 | 
			
		||||
/*
 | 
			
		||||
 | 
			
		||||
  Copyright 2019 ZeroEx Intl.
 | 
			
		||||
 | 
			
		||||
  Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
  you may not use this file except in compliance with the License.
 | 
			
		||||
  You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
  Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
  distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
  See the License for the specific language governing permissions and
 | 
			
		||||
  limitations under the License.
 | 
			
		||||
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
pragma solidity ^0.5.9;
 | 
			
		||||
pragma experimental ABIEncoderV2;
 | 
			
		||||
 | 
			
		||||
import "../src/Staking.sol";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
contract TestProtocolFees is
 | 
			
		||||
    Staking
 | 
			
		||||
{
 | 
			
		||||
    function setPoolIdOfMaker(bytes32 poolId, address makerAddress)
 | 
			
		||||
        external
 | 
			
		||||
    {
 | 
			
		||||
        poolIdByMakerAddress[makerAddress] = poolId;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function getActivePoolsByEpoch()
 | 
			
		||||
        external
 | 
			
		||||
        view
 | 
			
		||||
        returns (bytes32[] memory)
 | 
			
		||||
    {
 | 
			
		||||
        return activePoolsThisEpoch;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,44 @@
 | 
			
		||||
/*
 | 
			
		||||
 | 
			
		||||
  Copyright 2019 ZeroEx Intl.
 | 
			
		||||
 | 
			
		||||
  Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
  you may not use this file except in compliance with the License.
 | 
			
		||||
  You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
  Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
  distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
  See the License for the specific language governing permissions and
 | 
			
		||||
  limitations under the License.
 | 
			
		||||
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
pragma solidity ^0.5.9;
 | 
			
		||||
 | 
			
		||||
import "@0x/contracts-asset-proxy/contracts/src/ERC20Proxy.sol";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
contract TestProtocolFeesERC20Proxy is
 | 
			
		||||
    ERC20Proxy
 | 
			
		||||
{
 | 
			
		||||
    event TransferFromCalled(
 | 
			
		||||
        bytes assetData,
 | 
			
		||||
        address from,
 | 
			
		||||
        address to,
 | 
			
		||||
        uint256 amount
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    function transferFrom(
 | 
			
		||||
        bytes calldata assetData,
 | 
			
		||||
        address from,
 | 
			
		||||
        address to,
 | 
			
		||||
        uint256 amount
 | 
			
		||||
    )
 | 
			
		||||
        external
 | 
			
		||||
    {
 | 
			
		||||
        emit TransferFromCalled(assetData, from, to, amount);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										53
									
								
								contracts/staking/contracts/test/TestStaking.sol
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								contracts/staking/contracts/test/TestStaking.sol
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,53 @@
 | 
			
		||||
/*
 | 
			
		||||
 | 
			
		||||
  Copyright 2019 ZeroEx Intl.
 | 
			
		||||
 | 
			
		||||
  Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
  you may not use this file except in compliance with the License.
 | 
			
		||||
  You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
  Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
  distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
  See the License for the specific language governing permissions and
 | 
			
		||||
  limitations under the License.
 | 
			
		||||
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
pragma solidity ^0.5.9;
 | 
			
		||||
pragma experimental ABIEncoderV2;
 | 
			
		||||
 | 
			
		||||
import "../src/Staking.sol";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
contract TestStaking is
 | 
			
		||||
    Staking
 | 
			
		||||
{
 | 
			
		||||
    // Stub out `payProtocolFee` to be the naive payProtocolFee function so that tests will
 | 
			
		||||
    // not fail for WETH protocol fees.
 | 
			
		||||
    function payProtocolFee(
 | 
			
		||||
        address makerAddress,
 | 
			
		||||
        address,
 | 
			
		||||
        uint256
 | 
			
		||||
    )
 | 
			
		||||
        external
 | 
			
		||||
        payable
 | 
			
		||||
        onlyExchange
 | 
			
		||||
    {
 | 
			
		||||
        uint256 amount = msg.value;
 | 
			
		||||
        bytes32 poolId = getStakingPoolIdOfMaker(makerAddress);
 | 
			
		||||
        uint256 _feesCollectedThisEpoch = protocolFeesThisEpochByPool[poolId];
 | 
			
		||||
        protocolFeesThisEpochByPool[poolId] = _feesCollectedThisEpoch.safeAdd(amount);
 | 
			
		||||
        if (_feesCollectedThisEpoch == 0) {
 | 
			
		||||
            activePoolsThisEpoch.push(poolId);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Stub out `_unwrapWETH` to prevent the calls to `finalizeFees` from failing in tests
 | 
			
		||||
    // that do not relate to protocol fee payments in WETH.
 | 
			
		||||
    function _unwrapWETH()
 | 
			
		||||
        internal
 | 
			
		||||
    {} // solhint-disable-line no-empty-blocks
 | 
			
		||||
}
 | 
			
		||||
@@ -40,6 +40,9 @@ import * as StakingPoolRewardVault from '../generated-artifacts/StakingPoolRewar
 | 
			
		||||
import * as StakingProxy from '../generated-artifacts/StakingProxy.json';
 | 
			
		||||
import * as TestCobbDouglas from '../generated-artifacts/TestCobbDouglas.json';
 | 
			
		||||
import * as TestLibFixedMath from '../generated-artifacts/TestLibFixedMath.json';
 | 
			
		||||
import * as TestProtocolFees from '../generated-artifacts/TestProtocolFees.json';
 | 
			
		||||
import * as TestProtocolFeesERC20Proxy from '../generated-artifacts/TestProtocolFeesERC20Proxy.json';
 | 
			
		||||
import * as TestStaking from '../generated-artifacts/TestStaking.json';
 | 
			
		||||
import * as TestStorageLayout from '../generated-artifacts/TestStorageLayout.json';
 | 
			
		||||
import * as ZrxVault from '../generated-artifacts/ZrxVault.json';
 | 
			
		||||
export const artifacts = {
 | 
			
		||||
@@ -79,5 +82,8 @@ export const artifacts = {
 | 
			
		||||
    ZrxVault: ZrxVault as ContractArtifact,
 | 
			
		||||
    TestCobbDouglas: TestCobbDouglas as ContractArtifact,
 | 
			
		||||
    TestLibFixedMath: TestLibFixedMath as ContractArtifact,
 | 
			
		||||
    TestProtocolFees: TestProtocolFees as ContractArtifact,
 | 
			
		||||
    TestProtocolFeesERC20Proxy: TestProtocolFeesERC20Proxy as ContractArtifact,
 | 
			
		||||
    TestStaking: TestStaking as ContractArtifact,
 | 
			
		||||
    TestStorageLayout: TestStorageLayout as ContractArtifact,
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -38,5 +38,8 @@ export * from '../generated-wrappers/staking_pool_reward_vault';
 | 
			
		||||
export * from '../generated-wrappers/staking_proxy';
 | 
			
		||||
export * from '../generated-wrappers/test_cobb_douglas';
 | 
			
		||||
export * from '../generated-wrappers/test_lib_fixed_math';
 | 
			
		||||
export * from '../generated-wrappers/test_protocol_fees';
 | 
			
		||||
export * from '../generated-wrappers/test_protocol_fees_erc20_proxy';
 | 
			
		||||
export * from '../generated-wrappers/test_staking';
 | 
			
		||||
export * from '../generated-wrappers/test_storage_layout';
 | 
			
		||||
export * from '../generated-wrappers/zrx_vault';
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,8 @@ import { BigNumber } from '@0x/utils';
 | 
			
		||||
import * as chai from 'chai';
 | 
			
		||||
import * as _ from 'lodash';
 | 
			
		||||
 | 
			
		||||
import { artifacts } from '../src';
 | 
			
		||||
 | 
			
		||||
import { constants as stakingConstants } from './utils/constants';
 | 
			
		||||
import { StakingWrapper } from './utils/staking_wrapper';
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										361
									
								
								contracts/staking/test/protocol_fees.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										361
									
								
								contracts/staking/test/protocol_fees.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,361 @@
 | 
			
		||||
import { blockchainTests, constants, expect, LogDecoder } from '@0x/contracts-test-utils';
 | 
			
		||||
import { StakingRevertErrors } from '@0x/order-utils';
 | 
			
		||||
import { BigNumber } from '@0x/utils';
 | 
			
		||||
import { LogWithDecodedArgs } from 'ethereum-types';
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
    artifacts,
 | 
			
		||||
    TestProtocolFeesContract,
 | 
			
		||||
    TestProtocolFeesERC20ProxyContract,
 | 
			
		||||
    TestProtocolFeesERC20ProxyTransferFromCalledEventArgs,
 | 
			
		||||
} from '../src';
 | 
			
		||||
 | 
			
		||||
// tslint:disable:no-unnecessary-type-assertion
 | 
			
		||||
blockchainTests('Protocol Fee Unit Tests', env => {
 | 
			
		||||
    // The accounts that will be used during testing.
 | 
			
		||||
    let owner: string;
 | 
			
		||||
    let exchange: string;
 | 
			
		||||
    let nonExchange: string;
 | 
			
		||||
    let makerAddress: string;
 | 
			
		||||
    let payerAddress: string;
 | 
			
		||||
 | 
			
		||||
    // The contract that will be used for testng `payProtocolFee` and `_unwrapETH`.
 | 
			
		||||
    let protocolFees: TestProtocolFeesContract;
 | 
			
		||||
    let proxy: TestProtocolFeesERC20ProxyContract;
 | 
			
		||||
 | 
			
		||||
    // The log decoder that will be used to decode logs from TestProtocolFeesERC20Proxy.
 | 
			
		||||
    let logDecoder: LogDecoder;
 | 
			
		||||
 | 
			
		||||
    // The default protocol fee that will be paid -- a somewhat realistic value.
 | 
			
		||||
    const DEFAULT_PROTOCOL_FEE_PAID = new BigNumber(150000).times(10000000);
 | 
			
		||||
 | 
			
		||||
    // The default pool Id that will be used.
 | 
			
		||||
    const DEFAULT_POOL_ID = '0x0000000000000000000000000000000000000000000000000000000000000001';
 | 
			
		||||
 | 
			
		||||
    // The WETH asset data that should be set in the contract.
 | 
			
		||||
    const WETH_ASSET_DATA = '0xf47261b0000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2';
 | 
			
		||||
 | 
			
		||||
    before(async () => {
 | 
			
		||||
        // Get accounts to represent the exchange and an address that is not a registered exchange.
 | 
			
		||||
        [
 | 
			
		||||
            owner,
 | 
			
		||||
            exchange,
 | 
			
		||||
            nonExchange,
 | 
			
		||||
            makerAddress,
 | 
			
		||||
            payerAddress,
 | 
			
		||||
        ] = (await env.web3Wrapper.getAvailableAddressesAsync()).slice(0, 6);
 | 
			
		||||
 | 
			
		||||
        // Deploy the protocol fees contract.
 | 
			
		||||
        protocolFees = await TestProtocolFeesContract.deployFrom0xArtifactAsync(
 | 
			
		||||
            artifacts.TestProtocolFees,
 | 
			
		||||
            env.provider,
 | 
			
		||||
            {
 | 
			
		||||
                ...env.txDefaults,
 | 
			
		||||
                from: owner,
 | 
			
		||||
            },
 | 
			
		||||
            {},
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // Deploy the erc20Proxy for testing.
 | 
			
		||||
        proxy = await TestProtocolFeesERC20ProxyContract.deployFrom0xArtifactAsync(
 | 
			
		||||
            artifacts.TestProtocolFeesERC20Proxy,
 | 
			
		||||
            env.provider,
 | 
			
		||||
            env.txDefaults,
 | 
			
		||||
            {},
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // Register the test ERC20Proxy in the exchange.
 | 
			
		||||
        await protocolFees.addERC20AssetProxy.awaitTransactionSuccessAsync(proxy.address);
 | 
			
		||||
 | 
			
		||||
        // Register an exchange in the protocol fee contract.
 | 
			
		||||
        await protocolFees.addExchangeAddress.awaitTransactionSuccessAsync(exchange, { from: owner });
 | 
			
		||||
 | 
			
		||||
        // "Register" the makerAddress in the default pool.
 | 
			
		||||
        await protocolFees.setPoolIdOfMaker.awaitTransactionSuccessAsync(DEFAULT_POOL_ID, makerAddress);
 | 
			
		||||
 | 
			
		||||
        // Create the log decoder that will be used to decode TransferFromCalledEvent logs.
 | 
			
		||||
        logDecoder = new LogDecoder(env.web3Wrapper, artifacts);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    blockchainTests.resets('payProtocolFee', () => {
 | 
			
		||||
        // Verify that the DEFAULT_POOL_ID was pushed to the active pool list and that the correct amount
 | 
			
		||||
        // is registered in the pool, or that the NIL_POOL's state was unaffected depending on which pool id
 | 
			
		||||
        // was provided.
 | 
			
		||||
        async function verifyEndStateAsync(poolId: string, amount: BigNumber): Promise<void> {
 | 
			
		||||
            if (poolId === DEFAULT_POOL_ID) {
 | 
			
		||||
                // Ensure that the `DEFAULT_POOL_ID` was pushed into this epoch's active pool.
 | 
			
		||||
                const activePools = await protocolFees.getActivePoolsByEpoch.callAsync();
 | 
			
		||||
                expect(activePools.length).to.be.eq(1);
 | 
			
		||||
                expect(activePools[0]).to.be.eq(DEFAULT_POOL_ID);
 | 
			
		||||
 | 
			
		||||
                // Ensure that the `DEFAULT_PROTOCOL_FEE_PAID` was attributed to the maker's pool.
 | 
			
		||||
                const feesInMakerPool = await protocolFees.getProtocolFeesThisEpochByPool.callAsync(DEFAULT_POOL_ID);
 | 
			
		||||
                expect(feesInMakerPool).bignumber.to.be.eq(amount);
 | 
			
		||||
            } else {
 | 
			
		||||
                // Ensure that the only active pool this epoch is the "zero" pool.
 | 
			
		||||
                const activePools = await protocolFees.getActivePoolsByEpoch.callAsync();
 | 
			
		||||
                expect(activePools.length).to.be.eq(0);
 | 
			
		||||
 | 
			
		||||
                // Ensure that the `NIL_POOL` was not attributed a payment.
 | 
			
		||||
                const feesInMakerPool = await protocolFees.getProtocolFeesThisEpochByPool.callAsync(
 | 
			
		||||
                    constants.NULL_BYTES32,
 | 
			
		||||
                );
 | 
			
		||||
                expect(feesInMakerPool).bignumber.to.be.eq(constants.ZERO_AMOUNT);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        it('should revert if called by a non-exchange', async () => {
 | 
			
		||||
            const expectedError = new StakingRevertErrors.OnlyCallableByExchangeError(nonExchange);
 | 
			
		||||
            const tx = protocolFees.payProtocolFee.sendTransactionAsync(
 | 
			
		||||
                makerAddress,
 | 
			
		||||
                payerAddress,
 | 
			
		||||
                DEFAULT_PROTOCOL_FEE_PAID,
 | 
			
		||||
                { from: nonExchange },
 | 
			
		||||
            );
 | 
			
		||||
            return expect(tx).to.revertWith(expectedError);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should revert if `protocolFeePaid` is zero with zero value sent', async () => {
 | 
			
		||||
            const expectedError = new StakingRevertErrors.InvalidProtocolFeePaymentError(
 | 
			
		||||
                constants.ZERO_AMOUNT,
 | 
			
		||||
                constants.ZERO_AMOUNT,
 | 
			
		||||
            );
 | 
			
		||||
            const tx = protocolFees.payProtocolFee.sendTransactionAsync(
 | 
			
		||||
                makerAddress,
 | 
			
		||||
                payerAddress,
 | 
			
		||||
                constants.ZERO_AMOUNT,
 | 
			
		||||
                { from: exchange, value: constants.ZERO_AMOUNT },
 | 
			
		||||
            );
 | 
			
		||||
            return expect(tx).to.revertWith(expectedError);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should revert if `protocolFeePaid` is zero with non-zero value sent', async () => {
 | 
			
		||||
            const expectedError = new StakingRevertErrors.InvalidProtocolFeePaymentError(
 | 
			
		||||
                constants.ZERO_AMOUNT,
 | 
			
		||||
                DEFAULT_PROTOCOL_FEE_PAID,
 | 
			
		||||
            );
 | 
			
		||||
            const tx = protocolFees.payProtocolFee.sendTransactionAsync(
 | 
			
		||||
                makerAddress,
 | 
			
		||||
                payerAddress,
 | 
			
		||||
                constants.ZERO_AMOUNT,
 | 
			
		||||
                { from: exchange, value: DEFAULT_PROTOCOL_FEE_PAID },
 | 
			
		||||
            );
 | 
			
		||||
            return expect(tx).to.revertWith(expectedError);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should revert if `protocolFeePaid` is different than the provided message value', async () => {
 | 
			
		||||
            const differentProtocolFeePaid = DEFAULT_PROTOCOL_FEE_PAID.minus(50);
 | 
			
		||||
            const expectedError = new StakingRevertErrors.InvalidProtocolFeePaymentError(
 | 
			
		||||
                differentProtocolFeePaid,
 | 
			
		||||
                DEFAULT_PROTOCOL_FEE_PAID,
 | 
			
		||||
            );
 | 
			
		||||
            const tx = protocolFees.payProtocolFee.sendTransactionAsync(
 | 
			
		||||
                makerAddress,
 | 
			
		||||
                payerAddress,
 | 
			
		||||
                differentProtocolFeePaid,
 | 
			
		||||
                { from: exchange, value: DEFAULT_PROTOCOL_FEE_PAID },
 | 
			
		||||
            );
 | 
			
		||||
            return expect(tx).to.revertWith(expectedError);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should call `transferFrom` in the proxy if no value is sent and the maker is not in a pool', async () => {
 | 
			
		||||
            const receipt = await protocolFees.payProtocolFee.awaitTransactionSuccessAsync(
 | 
			
		||||
                payerAddress, // This is an unregistered maker address
 | 
			
		||||
                payerAddress,
 | 
			
		||||
                DEFAULT_PROTOCOL_FEE_PAID,
 | 
			
		||||
                { from: exchange, value: 0 },
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            // Ensure that the correct number of logs were recorded.
 | 
			
		||||
            expect(receipt.logs.length).to.be.eq(1);
 | 
			
		||||
 | 
			
		||||
            // Ensure that the correct log was recorded.
 | 
			
		||||
            const log = logDecoder.decodeLogOrThrow(receipt.logs[0]) as LogWithDecodedArgs<
 | 
			
		||||
                TestProtocolFeesERC20ProxyTransferFromCalledEventArgs
 | 
			
		||||
            >;
 | 
			
		||||
            expect(log.event).to.be.eq('TransferFromCalled');
 | 
			
		||||
            expect(log.args.assetData).to.be.eq(WETH_ASSET_DATA);
 | 
			
		||||
            expect(log.args.amount).bignumber.to.be.eq(DEFAULT_PROTOCOL_FEE_PAID);
 | 
			
		||||
            expect(log.args.from).to.be.eq(payerAddress);
 | 
			
		||||
            expect(log.args.to).to.be.eq(protocolFees.address);
 | 
			
		||||
 | 
			
		||||
            // Verify that the end state is correct.
 | 
			
		||||
            await verifyEndStateAsync(constants.NULL_BYTES32, constants.ZERO_AMOUNT);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should call `transferFrom` in the proxy and update `protocolFeesThisEpochByPool` if no value is sent and the maker is in a pool', async () => {
 | 
			
		||||
            const receipt = await protocolFees.payProtocolFee.awaitTransactionSuccessAsync(
 | 
			
		||||
                makerAddress,
 | 
			
		||||
                payerAddress,
 | 
			
		||||
                DEFAULT_PROTOCOL_FEE_PAID,
 | 
			
		||||
                { from: exchange, value: 0 },
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            // Ensure that the correct number of logs were recorded.
 | 
			
		||||
            expect(receipt.logs.length).to.be.eq(1);
 | 
			
		||||
 | 
			
		||||
            // Ensure that the correct log was recorded.
 | 
			
		||||
            const log = logDecoder.decodeLogOrThrow(receipt.logs[0]) as LogWithDecodedArgs<
 | 
			
		||||
                TestProtocolFeesERC20ProxyTransferFromCalledEventArgs
 | 
			
		||||
            >;
 | 
			
		||||
            expect(log.event).to.be.eq('TransferFromCalled');
 | 
			
		||||
            expect(log.args.assetData).to.be.eq(WETH_ASSET_DATA);
 | 
			
		||||
            expect(log.args.amount).bignumber.to.be.eq(DEFAULT_PROTOCOL_FEE_PAID);
 | 
			
		||||
            expect(log.args.from).to.be.eq(payerAddress);
 | 
			
		||||
            expect(log.args.to).to.be.eq(protocolFees.address);
 | 
			
		||||
 | 
			
		||||
            // Verify that the end state is correct.
 | 
			
		||||
            await verifyEndStateAsync(DEFAULT_POOL_ID, DEFAULT_PROTOCOL_FEE_PAID);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should not call `transferFrom` in the proxy and should not update `protocolFeesThisEpochByPool` if value is sent and the maker is not in a pool', async () => {
 | 
			
		||||
            const receipt = await protocolFees.payProtocolFee.awaitTransactionSuccessAsync(
 | 
			
		||||
                payerAddress, // This is an unregistered maker address
 | 
			
		||||
                payerAddress,
 | 
			
		||||
                DEFAULT_PROTOCOL_FEE_PAID,
 | 
			
		||||
                { from: exchange, value: DEFAULT_PROTOCOL_FEE_PAID },
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            // Ensure that the correct number of logs were recorded.
 | 
			
		||||
            expect(receipt.logs.length).to.be.eq(0);
 | 
			
		||||
 | 
			
		||||
            // Verify that the end state is correct.
 | 
			
		||||
            await verifyEndStateAsync(constants.NULL_BYTES32, constants.ZERO_AMOUNT);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should not call `transferFrom` in the proxy and should update `protocolFeesThisEpochByPool` if value is sent and the maker is in a pool', async () => {
 | 
			
		||||
            const receipt = await protocolFees.payProtocolFee.awaitTransactionSuccessAsync(
 | 
			
		||||
                makerAddress,
 | 
			
		||||
                payerAddress,
 | 
			
		||||
                DEFAULT_PROTOCOL_FEE_PAID,
 | 
			
		||||
                { from: exchange, value: DEFAULT_PROTOCOL_FEE_PAID },
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            // Ensure that the correct number of logs were recorded.
 | 
			
		||||
            expect(receipt.logs.length).to.be.eq(0);
 | 
			
		||||
 | 
			
		||||
            // Verify that the end state is correct.
 | 
			
		||||
            await verifyEndStateAsync(DEFAULT_POOL_ID, DEFAULT_PROTOCOL_FEE_PAID);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should only have one active pool if a fee is paid on behalf of one maker ETH twice', async () => {
 | 
			
		||||
            // Pay the first fee
 | 
			
		||||
            await protocolFees.payProtocolFee.awaitTransactionSuccessAsync(
 | 
			
		||||
                makerAddress,
 | 
			
		||||
                payerAddress,
 | 
			
		||||
                DEFAULT_PROTOCOL_FEE_PAID,
 | 
			
		||||
                { from: exchange, value: DEFAULT_PROTOCOL_FEE_PAID },
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            // Pay the second fee
 | 
			
		||||
            const receipt = await protocolFees.payProtocolFee.awaitTransactionSuccessAsync(
 | 
			
		||||
                makerAddress,
 | 
			
		||||
                payerAddress,
 | 
			
		||||
                DEFAULT_PROTOCOL_FEE_PAID,
 | 
			
		||||
                { from: exchange, value: DEFAULT_PROTOCOL_FEE_PAID },
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            // Ensure that the correct number of logs were recorded.
 | 
			
		||||
            expect(receipt.logs.length).to.be.eq(0);
 | 
			
		||||
 | 
			
		||||
            // Verify that the end state is correct -- namely that the active pools list was only updated once,
 | 
			
		||||
            // and that the correct amount is recorded on behalf of the maker.
 | 
			
		||||
            await verifyEndStateAsync(DEFAULT_POOL_ID, DEFAULT_PROTOCOL_FEE_PAID.times(2));
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should only have one active pool if a fee is paid on behalf of one maker in WETH and then ETH', async () => {
 | 
			
		||||
            // Pay the first fee
 | 
			
		||||
            await protocolFees.payProtocolFee.awaitTransactionSuccessAsync(
 | 
			
		||||
                makerAddress,
 | 
			
		||||
                payerAddress,
 | 
			
		||||
                DEFAULT_PROTOCOL_FEE_PAID,
 | 
			
		||||
                { from: exchange, value: 0 },
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            // Pay the second fee
 | 
			
		||||
            const receipt = await protocolFees.payProtocolFee.awaitTransactionSuccessAsync(
 | 
			
		||||
                makerAddress,
 | 
			
		||||
                payerAddress,
 | 
			
		||||
                DEFAULT_PROTOCOL_FEE_PAID,
 | 
			
		||||
                { from: exchange, value: DEFAULT_PROTOCOL_FEE_PAID },
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            // Ensure that the correct number of logs were recorded.
 | 
			
		||||
            expect(receipt.logs.length).to.be.eq(0);
 | 
			
		||||
 | 
			
		||||
            // Verify that the end state is correct -- namely that the active pools list was only updated once,
 | 
			
		||||
            // and that the correct amount is recorded on behalf of the maker.
 | 
			
		||||
            await verifyEndStateAsync(DEFAULT_POOL_ID, DEFAULT_PROTOCOL_FEE_PAID.times(2));
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should only have one active pool if a fee is paid on behalf of one maker in ETH and then WETH', async () => {
 | 
			
		||||
            // Pay the first fee
 | 
			
		||||
            await protocolFees.payProtocolFee.awaitTransactionSuccessAsync(
 | 
			
		||||
                makerAddress,
 | 
			
		||||
                payerAddress,
 | 
			
		||||
                DEFAULT_PROTOCOL_FEE_PAID,
 | 
			
		||||
                { from: exchange, value: DEFAULT_PROTOCOL_FEE_PAID },
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            // Pay the second fee
 | 
			
		||||
            const receipt = await protocolFees.payProtocolFee.awaitTransactionSuccessAsync(
 | 
			
		||||
                makerAddress,
 | 
			
		||||
                payerAddress,
 | 
			
		||||
                DEFAULT_PROTOCOL_FEE_PAID,
 | 
			
		||||
                { from: exchange, value: 0 },
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            // Ensure that the correct number of logs were recorded.
 | 
			
		||||
            expect(receipt.logs.length).to.be.eq(1);
 | 
			
		||||
 | 
			
		||||
            // Ensure that the correct log was recorded
 | 
			
		||||
            const log = logDecoder.decodeLogOrThrow(receipt.logs[0]) as LogWithDecodedArgs<
 | 
			
		||||
                TestProtocolFeesERC20ProxyTransferFromCalledEventArgs
 | 
			
		||||
            >;
 | 
			
		||||
            expect(log.event).to.be.eq('TransferFromCalled');
 | 
			
		||||
            expect(log.args.assetData).to.be.eq(WETH_ASSET_DATA);
 | 
			
		||||
            expect(log.args.amount).bignumber.to.be.eq(DEFAULT_PROTOCOL_FEE_PAID);
 | 
			
		||||
            expect(log.args.from).to.be.eq(payerAddress);
 | 
			
		||||
            expect(log.args.to).to.be.eq(protocolFees.address);
 | 
			
		||||
 | 
			
		||||
            // Verify that the end state is correct -- namely that the active pools list was only updated once,
 | 
			
		||||
            // and that the correct amount is recorded on behalf of the maker.
 | 
			
		||||
            await verifyEndStateAsync(DEFAULT_POOL_ID, DEFAULT_PROTOCOL_FEE_PAID.times(2));
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should only have one active pool if a fee is paid on behalf of one maker in WETH twice', async () => {
 | 
			
		||||
            // Pay the first fee
 | 
			
		||||
            await protocolFees.payProtocolFee.awaitTransactionSuccessAsync(
 | 
			
		||||
                makerAddress,
 | 
			
		||||
                payerAddress,
 | 
			
		||||
                DEFAULT_PROTOCOL_FEE_PAID,
 | 
			
		||||
                { from: exchange, value: 0 },
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            // Pay the second fee
 | 
			
		||||
            const receipt = await protocolFees.payProtocolFee.awaitTransactionSuccessAsync(
 | 
			
		||||
                makerAddress,
 | 
			
		||||
                payerAddress,
 | 
			
		||||
                DEFAULT_PROTOCOL_FEE_PAID,
 | 
			
		||||
                { from: exchange, value: 0 },
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            // Ensure that the correct number of logs were recorded.
 | 
			
		||||
            expect(receipt.logs.length).to.be.eq(1);
 | 
			
		||||
 | 
			
		||||
            // Ensure that the correct log was recorded.
 | 
			
		||||
            const log = logDecoder.decodeLogOrThrow(receipt.logs[0]) as LogWithDecodedArgs<
 | 
			
		||||
                TestProtocolFeesERC20ProxyTransferFromCalledEventArgs
 | 
			
		||||
            >;
 | 
			
		||||
            expect(log.event).to.be.eq('TransferFromCalled');
 | 
			
		||||
            expect(log.args.assetData).to.be.eq(WETH_ASSET_DATA);
 | 
			
		||||
            expect(log.args.amount).bignumber.to.be.eq(DEFAULT_PROTOCOL_FEE_PAID);
 | 
			
		||||
            expect(log.args.from).to.be.eq(payerAddress);
 | 
			
		||||
            expect(log.args.to).to.be.eq(protocolFees.address);
 | 
			
		||||
 | 
			
		||||
            // Verify that the end state is correct -- namely that the active pools list was only updated once,
 | 
			
		||||
            // and that the correct amount is recorded on behalf of the maker.
 | 
			
		||||
            await verifyEndStateAsync(DEFAULT_POOL_ID, DEFAULT_PROTOCOL_FEE_PAID.times(2));
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
@@ -4,6 +4,8 @@ import { blockchainTests, describe, expect, provider, web3Wrapper } from '@0x/co
 | 
			
		||||
import { BigNumber } from '@0x/utils';
 | 
			
		||||
import * as _ from 'lodash';
 | 
			
		||||
 | 
			
		||||
import { artifacts } from '../src';
 | 
			
		||||
 | 
			
		||||
import { FinalizerActor } from './actors/finalizer_actor';
 | 
			
		||||
import { StakerActor } from './actors/staker_actor';
 | 
			
		||||
import { StakingWrapper } from './utils/staking_wrapper';
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,8 @@ import { StakingRevertErrors } from '@0x/order-utils';
 | 
			
		||||
import { BigNumber } from '@0x/utils';
 | 
			
		||||
import * as _ from 'lodash';
 | 
			
		||||
 | 
			
		||||
import { artifacts } from '../src';
 | 
			
		||||
 | 
			
		||||
import { StakerActor } from './actors/staker_actor';
 | 
			
		||||
import { StakingWrapper } from './utils/staking_wrapper';
 | 
			
		||||
import { StakeInfo, StakeStatus } from './utils/types';
 | 
			
		||||
 
 | 
			
		||||
@@ -62,7 +62,7 @@ export class StakingWrapper {
 | 
			
		||||
    constructor(
 | 
			
		||||
        provider: Provider,
 | 
			
		||||
        ownerAddres: string,
 | 
			
		||||
        erc20ProxyContract: ERC20ProxyContract,
 | 
			
		||||
        erc20ProxyContract: any, // This needs to be the `any` type so that other types of proxies can be used
 | 
			
		||||
        zrxTokenContract: DummyERC20TokenContract,
 | 
			
		||||
    ) {
 | 
			
		||||
        this._web3Wrapper = new Web3Wrapper(provider);
 | 
			
		||||
 
 | 
			
		||||
@@ -38,6 +38,9 @@
 | 
			
		||||
        "generated-artifacts/StakingProxy.json",
 | 
			
		||||
        "generated-artifacts/TestCobbDouglas.json",
 | 
			
		||||
        "generated-artifacts/TestLibFixedMath.json",
 | 
			
		||||
        "generated-artifacts/TestProtocolFees.json",
 | 
			
		||||
        "generated-artifacts/TestProtocolFeesERC20Proxy.json",
 | 
			
		||||
        "generated-artifacts/TestStaking.json",
 | 
			
		||||
        "generated-artifacts/TestStorageLayout.json",
 | 
			
		||||
        "generated-artifacts/ZrxVault.json"
 | 
			
		||||
    ],
 | 
			
		||||
 
 | 
			
		||||
@@ -179,6 +179,19 @@ export class InvalidStakeStatusError extends RevertError {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class InvalidProtocolFeePaymentError extends RevertError {
 | 
			
		||||
    constructor(
 | 
			
		||||
        expectedProtocolFeePaid?: BigNumber | number | string,
 | 
			
		||||
        actualProtocolFeePaid?: BigNumber | number | string,
 | 
			
		||||
    ) {
 | 
			
		||||
        super(
 | 
			
		||||
            'InvalidProtocolFeePaymentError',
 | 
			
		||||
            'InvalidProtocolFeePaymentError(uint256 expectedProtocolFeePaid, uint256 actualProtocolFeePaid)',
 | 
			
		||||
            { expectedProtocolFeePaid, actualProtocolFeePaid },
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const types = [
 | 
			
		||||
    MiscalculatedRewardsError,
 | 
			
		||||
    OnlyCallableByExchangeError,
 | 
			
		||||
@@ -200,6 +213,7 @@ const types = [
 | 
			
		||||
    EthVaultNotSetError,
 | 
			
		||||
    RewardVaultNotSetError,
 | 
			
		||||
    InvalidStakeStatusError,
 | 
			
		||||
    InvalidProtocolFeePaymentError,
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
// Register the types we've defined.
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user