diff --git a/contracts/.solhint.json b/contracts/.solhint.json index 3090d981a1..3be459e776 100644 --- a/contracts/.solhint.json +++ b/contracts/.solhint.json @@ -16,6 +16,7 @@ "quotes": ["error", "double"], "separate-by-one-line-in-contract": "error", "space-after-comma": "error", - "statement-indent": "error" + "statement-indent": "error", + "no-empty-blocks": false } } diff --git a/contracts/integrations/contracts/test/TestFixinProtocolFeesIntegration.sol b/contracts/integrations/contracts/test/TestFixinProtocolFeesIntegration.sol new file mode 100644 index 0000000000..173472f361 --- /dev/null +++ b/contracts/integrations/contracts/test/TestFixinProtocolFeesIntegration.sol @@ -0,0 +1,34 @@ +/* + + Copyright 2020 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.6.5; + +import "@0x/contracts-zero-ex/contracts/test/TestFixinProtocolFees.sol"; + + +contract TestFixinProtocolFeesIntegration is TestFixinProtocolFees { + constructor( + IEtherTokenV06 weth, + IStaking staking, + uint32 protocolFeeMultiplier + ) + public + TestFixinProtocolFees(weth, staking, protocolFeeMultiplier) + { + } +} diff --git a/contracts/integrations/contracts/test/TestStaking.sol b/contracts/integrations/contracts/test/TestStaking.sol new file mode 100644 index 0000000000..644e065384 --- /dev/null +++ b/contracts/integrations/contracts/test/TestStaking.sol @@ -0,0 +1,110 @@ +/* + + 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 "@0x/contracts-staking/contracts/src/Staking.sol"; + + +contract TestStaking is + Staking +{ + IEtherToken public testWeth; + + struct TestPool { + uint96 operatorStake; + uint96 membersStake; + } + + mapping(bytes32 => TestPool) private _testPools; + + constructor(address exchangeAddress, IEtherToken _testWeth) public { + testWeth = _testWeth; + + _addAuthorizedAddress(msg.sender); + init(); + validExchanges[exchangeAddress] = true; + _removeAuthorizedAddressAtIndex(msg.sender, 0); + } + + function advanceEpoch() + external + { + currentEpoch += 1; + } + + /// @dev Create a test pool. + function createTestPool( + bytes32 poolId, + uint96 operatorStake, + uint96 membersStake + ) + external + { + TestPool storage pool = _testPools[poolId]; + pool.operatorStake = operatorStake; + pool.membersStake = membersStake; + } + + function getAggregatedStatsForCurrentEpoch() + external + view + returns (IStructs.AggregatedStats memory) + { + return aggregatedStatsByEpoch[currentEpoch]; + } + + /// @dev Overridden to use test pools. + function getTotalStakeDelegatedToPool(bytes32 poolId) + public + view + returns (IStructs.StoredBalance memory balance) + { + TestPool memory pool = _testPools[poolId]; + uint96 stake = pool.operatorStake + pool.membersStake; + return IStructs.StoredBalance({ + currentEpoch: currentEpoch.downcastToUint64(), + currentEpochBalance: stake, + nextEpochBalance: stake + }); + } + + /// @dev Overridden to use test pools. + function getStakeDelegatedToPoolByOwner(address, bytes32 poolId) + public + view + returns (IStructs.StoredBalance memory balance) + { + TestPool memory pool = _testPools[poolId]; + return IStructs.StoredBalance({ + currentEpoch: currentEpoch.downcastToUint64(), + currentEpochBalance: pool.operatorStake, + nextEpochBalance: pool.operatorStake + }); + } + + function getWethContract() + public + view + returns (IEtherToken wethContract) + { + return testWeth; + } +} + diff --git a/contracts/integrations/contracts/test/TestWethIntegration.sol b/contracts/integrations/contracts/test/TestWethIntegration.sol new file mode 100644 index 0000000000..22d0a7f4ac --- /dev/null +++ b/contracts/integrations/contracts/test/TestWethIntegration.sol @@ -0,0 +1,24 @@ +/* + + Copyright 2020 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.6.5; + +import "@0x/contracts-zero-ex/contracts/test/TestWeth.sol"; + + +contract TestWethIntegration is TestWeth {} diff --git a/contracts/integrations/package.json b/contracts/integrations/package.json index b04ae0f635..74339ca32b 100644 --- a/contracts/integrations/package.json +++ b/contracts/integrations/package.json @@ -38,7 +38,7 @@ }, "config": { "publicInterfaceContracts": "TestFramework", - "abis": "./test/generated-artifacts/@(ChainlinkStopLimit|IChainlinkAggregator|TestChainlinkAggregator|TestContractWrapper|TestDydxUser|TestEth2Dai|TestEth2DaiBridge|TestFramework|TestMainnetAggregatorFills|TestSignatureValidationWallet|TestUniswapBridge|TestUniswapExchange|TestUniswapExchangeFactory).json", + "abis": "./test/generated-artifacts/@(ChainlinkStopLimit|IChainlinkAggregator|TestChainlinkAggregator|TestContractWrapper|TestDydxUser|TestEth2Dai|TestEth2DaiBridge|TestFixinProtocolFeesIntegration|TestFramework|TestMainnetAggregatorFills|TestSignatureValidationWallet|TestStaking|TestUniswapBridge|TestUniswapExchange|TestUniswapExchangeFactory|TestWethIntegration).json", "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually." }, "repository": { diff --git a/contracts/integrations/test/artifacts.ts b/contracts/integrations/test/artifacts.ts index e15210daa0..ec44e03bb6 100644 --- a/contracts/integrations/test/artifacts.ts +++ b/contracts/integrations/test/artifacts.ts @@ -12,12 +12,15 @@ import * as TestContractWrapper from '../test/generated-artifacts/TestContractWr import * as TestDydxUser from '../test/generated-artifacts/TestDydxUser.json'; import * as TestEth2Dai from '../test/generated-artifacts/TestEth2Dai.json'; import * as TestEth2DaiBridge from '../test/generated-artifacts/TestEth2DaiBridge.json'; +import * as TestFixinProtocolFeesIntegration from '../test/generated-artifacts/TestFixinProtocolFeesIntegration.json'; import * as TestFramework from '../test/generated-artifacts/TestFramework.json'; import * as TestMainnetAggregatorFills from '../test/generated-artifacts/TestMainnetAggregatorFills.json'; import * as TestSignatureValidationWallet from '../test/generated-artifacts/TestSignatureValidationWallet.json'; +import * as TestStaking from '../test/generated-artifacts/TestStaking.json'; import * as TestUniswapBridge from '../test/generated-artifacts/TestUniswapBridge.json'; import * as TestUniswapExchange from '../test/generated-artifacts/TestUniswapExchange.json'; import * as TestUniswapExchangeFactory from '../test/generated-artifacts/TestUniswapExchangeFactory.json'; +import * as TestWethIntegration from '../test/generated-artifacts/TestWethIntegration.json'; export const artifacts = { ChainlinkStopLimit: ChainlinkStopLimit as ContractArtifact, IChainlinkAggregator: IChainlinkAggregator as ContractArtifact, @@ -26,10 +29,13 @@ export const artifacts = { TestDydxUser: TestDydxUser as ContractArtifact, TestEth2Dai: TestEth2Dai as ContractArtifact, TestEth2DaiBridge: TestEth2DaiBridge as ContractArtifact, + TestFixinProtocolFeesIntegration: TestFixinProtocolFeesIntegration as ContractArtifact, TestFramework: TestFramework as ContractArtifact, TestMainnetAggregatorFills: TestMainnetAggregatorFills as ContractArtifact, TestSignatureValidationWallet: TestSignatureValidationWallet as ContractArtifact, + TestStaking: TestStaking as ContractArtifact, TestUniswapBridge: TestUniswapBridge as ContractArtifact, TestUniswapExchange: TestUniswapExchange as ContractArtifact, TestUniswapExchangeFactory: TestUniswapExchangeFactory as ContractArtifact, + TestWethIntegration: TestWethIntegration as ContractArtifact, }; diff --git a/contracts/integrations/test/wrappers.ts b/contracts/integrations/test/wrappers.ts index 853d6dd18a..943a45404c 100644 --- a/contracts/integrations/test/wrappers.ts +++ b/contracts/integrations/test/wrappers.ts @@ -10,9 +10,12 @@ export * from '../test/generated-wrappers/test_contract_wrapper'; export * from '../test/generated-wrappers/test_dydx_user'; export * from '../test/generated-wrappers/test_eth2_dai'; export * from '../test/generated-wrappers/test_eth2_dai_bridge'; +export * from '../test/generated-wrappers/test_fixin_protocol_fees_integration'; export * from '../test/generated-wrappers/test_framework'; export * from '../test/generated-wrappers/test_mainnet_aggregator_fills'; export * from '../test/generated-wrappers/test_signature_validation_wallet'; +export * from '../test/generated-wrappers/test_staking'; export * from '../test/generated-wrappers/test_uniswap_bridge'; export * from '../test/generated-wrappers/test_uniswap_exchange'; export * from '../test/generated-wrappers/test_uniswap_exchange_factory'; +export * from '../test/generated-wrappers/test_weth_integration'; diff --git a/contracts/integrations/test/zero-ex/protocol_fees_staking_test.ts b/contracts/integrations/test/zero-ex/protocol_fees_staking_test.ts new file mode 100644 index 0000000000..3ec33cfb9e --- /dev/null +++ b/contracts/integrations/test/zero-ex/protocol_fees_staking_test.ts @@ -0,0 +1,85 @@ +import { blockchainTests, constants, expect } from '@0x/contracts-test-utils'; +import { BigNumber, hexUtils } from '@0x/utils'; + +import { artifacts } from '../artifacts'; +import { + TestFixinProtocolFeesIntegrationContract, + TestStakingContract, + TestWethIntegrationContract, +} from '../wrappers'; + +blockchainTests.resets('ProtocolFeeIntegration', env => { + const FEE_MULTIPLIER = 70e3; + let owner: string; + let taker: string; + let protocolFees: TestFixinProtocolFeesIntegrationContract; + let staking: TestStakingContract; + let weth: TestWethIntegrationContract; + let singleFeeAmount: BigNumber; + + before(async () => { + [owner, taker] = await env.getAccountAddressesAsync(); + weth = await TestWethIntegrationContract.deployFrom0xArtifactAsync( + artifacts.TestWethIntegration, + env.provider, + env.txDefaults, + artifacts, + ); + staking = await TestStakingContract.deployFrom0xArtifactAsync( + artifacts.TestStaking, + env.provider, + env.txDefaults, + artifacts, + constants.NULL_ADDRESS, // exchange address, which we don't know yet + weth.address, + ); + protocolFees = await TestFixinProtocolFeesIntegrationContract.deployFrom0xArtifactAsync( + artifacts.TestFixinProtocolFeesIntegration, + env.provider, + { ...env.txDefaults, from: taker }, + artifacts, + weth.address, + staking.address, + FEE_MULTIPLIER, + ); + await staking.addAuthorizedAddress(owner).awaitTransactionSuccessAsync(); + await staking.addExchangeAddress(protocolFees.address).awaitTransactionSuccessAsync({ from: owner }); + await weth.mint(taker, constants.ONE_ETHER).awaitTransactionSuccessAsync(); + await weth.approve(protocolFees.address, constants.ONE_ETHER).awaitTransactionSuccessAsync({ from: taker }); + + singleFeeAmount = await protocolFees.getSingleProtocolFee().callAsync(); + }); + + describe('fee collection integration', () => { + const pool0 = constants.NULL_BYTES32; + const poolId = hexUtils.random(); + + it('should collect fees for pool 0', async () => { + await protocolFees.collectProtocolFee(pool0).awaitTransactionSuccessAsync({ value: singleFeeAmount }); + await protocolFees.transferFeesForPool(pool0).awaitTransactionSuccessAsync(); + + // Fees in the pool bytes32(0) don't get attributed to a pool. + await expect( + (await staking.getStakingPoolStatsThisEpoch(pool0).callAsync()).feesCollected, + ).to.bignumber.equal(constants.ZERO_AMOUNT); + + // Expected amount is singleFeeAmount - 1 because we leave 1 wei of WETH behind for future gas savings. + return expect(await weth.balanceOf(staking.address).callAsync()).to.bignumber.equal( + singleFeeAmount.minus(1), + ); + }); + + it('should collect fees for non-zero pool', async () => { + const eth100 = constants.ONE_ETHER.multipliedBy(100); + await staking.createTestPool(poolId, eth100, eth100).awaitTransactionSuccessAsync(); + + await protocolFees.collectProtocolFee(poolId).awaitTransactionSuccessAsync({ value: singleFeeAmount }); + await protocolFees.transferFeesForPool(poolId).awaitTransactionSuccessAsync(); + + // Expected amount is singleFeeAmount - 1 because we leave 1 wei of WETH behind for future gas savings. + return expect( + (await staking.getStakingPoolStatsThisEpoch(poolId).callAsync()).feesCollected, + ).to.bignumber.equal(singleFeeAmount.minus(1)); + }); + }); +}); diff --git a/contracts/integrations/tsconfig.json b/contracts/integrations/tsconfig.json index 7e86ada82d..580a48601d 100644 --- a/contracts/integrations/tsconfig.json +++ b/contracts/integrations/tsconfig.json @@ -11,11 +11,14 @@ "test/generated-artifacts/TestDydxUser.json", "test/generated-artifacts/TestEth2Dai.json", "test/generated-artifacts/TestEth2DaiBridge.json", + "test/generated-artifacts/TestFixinProtocolFeesIntegration.json", "test/generated-artifacts/TestFramework.json", "test/generated-artifacts/TestMainnetAggregatorFills.json", "test/generated-artifacts/TestSignatureValidationWallet.json", + "test/generated-artifacts/TestStaking.json", "test/generated-artifacts/TestUniswapBridge.json", "test/generated-artifacts/TestUniswapExchange.json", - "test/generated-artifacts/TestUniswapExchangeFactory.json" + "test/generated-artifacts/TestUniswapExchangeFactory.json", + "test/generated-artifacts/TestWethIntegration.json" ] }