diff --git a/contracts/integrations/contracts/test/TestEth2Dai.sol b/contracts/integrations/contracts/test/TestEth2Dai.sol new file mode 100644 index 0000000000..0a38d7d93a --- /dev/null +++ b/contracts/integrations/contracts/test/TestEth2Dai.sol @@ -0,0 +1,51 @@ +/* + + 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-asset-proxy/contracts/src/interfaces/IEth2Dai.sol"; +import "@0x/contracts-erc20/contracts/test/DummyERC20Token.sol"; + + +contract TestEth2Dai is + IEth2Dai +{ + function sellAllAmount( + address sellTokenAddress, + uint256 sellTokenAmount, + address buyTokenAddress, + uint256 minimumFillAmount + ) + external + returns (uint256 fillAmount) + { + DummyERC20Token(sellTokenAddress).transferFrom( + msg.sender, + address(this), + sellTokenAmount + ); + DummyERC20Token buyToken = DummyERC20Token(buyTokenAddress); + buyToken.mint(minimumFillAmount); + buyToken.transfer( + msg.sender, + minimumFillAmount + ); + return minimumFillAmount; + } +} diff --git a/contracts/integrations/contracts/test/TestEth2DaiBridge.sol b/contracts/integrations/contracts/test/TestEth2DaiBridge.sol new file mode 100644 index 0000000000..4029e9f110 --- /dev/null +++ b/contracts/integrations/contracts/test/TestEth2DaiBridge.sol @@ -0,0 +1,45 @@ +/* + + 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-asset-proxy/contracts/src/bridges/Eth2DaiBridge.sol"; +import "@0x/contracts-asset-proxy/contracts/src/interfaces/IEth2Dai.sol"; + + +contract TestEth2DaiBridge is + Eth2DaiBridge +{ + // solhint-disable var-name-mixedcase + address public TEST_ETH2DAI_ADDRESS; + + constructor (address testEth2Dai) + public + { + TEST_ETH2DAI_ADDRESS = testEth2Dai; + } + + function _getEth2DaiContract() + internal + view + returns (IEth2Dai exchange) + { + return IEth2Dai(TEST_ETH2DAI_ADDRESS); + } +} diff --git a/contracts/integrations/contracts/test/TestUniswapBridge.sol b/contracts/integrations/contracts/test/TestUniswapBridge.sol new file mode 100644 index 0000000000..1154218d78 --- /dev/null +++ b/contracts/integrations/contracts/test/TestUniswapBridge.sol @@ -0,0 +1,59 @@ +/* + + 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-asset-proxy/contracts/src/bridges/UniswapBridge.sol"; +import "@0x/contracts-asset-proxy/contracts/src/interfaces/IUniswapExchangeFactory.sol"; +import "@0x/contracts-erc20/contracts/src/interfaces/IEtherToken.sol"; + + +contract TestUniswapBridge is + UniswapBridge +{ + // solhint-disable var-name-mixedcase + address public TEST_WETH_ADDRESS; + address public TEST_UNISWAP_EXCHANGE_FACTORY_ADDRESS; + + constructor ( + address testWeth, + address testUniswapExchangeFactory + ) + public + { + TEST_WETH_ADDRESS = testWeth; + TEST_UNISWAP_EXCHANGE_FACTORY_ADDRESS = testUniswapExchangeFactory; + } + + function getWethContract() + public + view + returns (IEtherToken token) + { + return IEtherToken(TEST_WETH_ADDRESS); + } + + function getUniswapExchangeFactoryContract() + public + view + returns (IUniswapExchangeFactory factory) + { + return IUniswapExchangeFactory(TEST_UNISWAP_EXCHANGE_FACTORY_ADDRESS); + } +} diff --git a/contracts/integrations/contracts/test/TestUniswapExchange.sol b/contracts/integrations/contracts/test/TestUniswapExchange.sol new file mode 100644 index 0000000000..68938de793 --- /dev/null +++ b/contracts/integrations/contracts/test/TestUniswapExchange.sol @@ -0,0 +1,102 @@ +/* + + 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-asset-proxy/contracts/src/interfaces/IUniswapExchange.sol"; +import "@0x/contracts-erc20/contracts/test/DummyERC20Token.sol"; + + +contract TestUniswapExchange is + IUniswapExchange +{ + DummyERC20Token public token; + + constructor(address _tokenAddress) public { + token = DummyERC20Token(_tokenAddress); + } + + // solhint-disable no-empty-blocks + /// @dev Used to receive ETH for testing. + function topUpEth() + external + payable + {} + + function ethToTokenTransferInput( + uint256 minTokensBought, + uint256, /* deadline */ + address recipient + ) + external + payable + returns (uint256 tokensBought) + { + token.mint(minTokensBought); + token.transfer(recipient, minTokensBought); + return minTokensBought; + } + + function tokenToEthSwapInput( + uint256 tokensSold, + uint256 minEthBought, + uint256 /* deadline */ + ) + external + returns (uint256 ethBought) + { + token.transferFrom( + msg.sender, + address(this), + tokensSold + ); + msg.sender.transfer(minEthBought); + return minEthBought; + } + + function tokenToTokenTransferInput( + uint256 tokensSold, + uint256 minTokensBought, + uint256, /* minEthBought */ + uint256, /* deadline */ + address recipient, + address toTokenAddress + ) + external + returns (uint256 tokensBought) + { + token.transferFrom( + msg.sender, + address(this), + tokensSold + ); + DummyERC20Token toToken = DummyERC20Token(toTokenAddress); + toToken.mint(minTokensBought); + toToken.transfer(recipient, minTokensBought); + return minTokensBought; + } + + function toTokenAddress() + external + view + returns (address _tokenAddress) + { + return address(token); + } +} diff --git a/contracts/integrations/contracts/test/TestUniswapExchangeFactory.sol b/contracts/integrations/contracts/test/TestUniswapExchangeFactory.sol new file mode 100644 index 0000000000..0c684a45c5 --- /dev/null +++ b/contracts/integrations/contracts/test/TestUniswapExchangeFactory.sol @@ -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 "@0x/contracts-asset-proxy/contracts/src/bridges/UniswapBridge.sol"; +import "@0x/contracts-asset-proxy/contracts/src/interfaces/IUniswapExchangeFactory.sol"; +import "@0x/contracts-asset-proxy/contracts/src/interfaces/IUniswapExchange.sol"; + + +contract TestUniswapExchangeFactory is + IUniswapExchangeFactory +{ + // Token address to UniswapExchange address. + mapping (address => address) private _testExchanges; + + /// @dev Create a token and exchange (if they don't exist) for a new token + /// and sets the exchange revert and fill behavior. + /// @param tokenAddress The token address. + function addExchange( + address tokenAddress, + address exchangeAddress + ) + external + { + _testExchanges[tokenAddress] = exchangeAddress; + } + + /// @dev `IUniswapExchangeFactory.getExchange` + function getExchange(address tokenAddress) + external + view + returns (IUniswapExchange) + { + return IUniswapExchange(_testExchanges[tokenAddress]); + } +} diff --git a/contracts/integrations/package.json b/contracts/integrations/package.json index dfd203fb98..3abda1353f 100644 --- a/contracts/integrations/package.json +++ b/contracts/integrations/package.json @@ -37,7 +37,7 @@ }, "config": { "publicInterfaceContracts": "TestFramework", - "abis": "./test/generated-artifacts/@(TestFramework).json", + "abis": "./test/generated-artifacts/@(TestEth2Dai|TestEth2DaiBridge|TestFramework|TestUniswapBridge|TestUniswapExchange|TestUniswapExchangeFactory).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 8ea0d872cf..29b07a6d02 100644 --- a/contracts/integrations/test/artifacts.ts +++ b/contracts/integrations/test/artifacts.ts @@ -5,5 +5,17 @@ */ import { ContractArtifact } from 'ethereum-types'; +import * as TestEth2Dai from '../test/generated-artifacts/TestEth2Dai.json'; +import * as TestEth2DaiBridge from '../test/generated-artifacts/TestEth2DaiBridge.json'; import * as TestFramework from '../test/generated-artifacts/TestFramework.json'; -export const artifacts = { TestFramework: TestFramework as ContractArtifact }; +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'; +export const artifacts = { + TestEth2Dai: TestEth2Dai as ContractArtifact, + TestEth2DaiBridge: TestEth2DaiBridge as ContractArtifact, + TestFramework: TestFramework as ContractArtifact, + TestUniswapBridge: TestUniswapBridge as ContractArtifact, + TestUniswapExchange: TestUniswapExchange as ContractArtifact, + TestUniswapExchangeFactory: TestUniswapExchangeFactory as ContractArtifact, +}; diff --git a/contracts/integrations/test/bridges/deploy_eth2dai_bridge.ts b/contracts/integrations/test/bridges/deploy_eth2dai_bridge.ts new file mode 100644 index 0000000000..3702d3030b --- /dev/null +++ b/contracts/integrations/test/bridges/deploy_eth2dai_bridge.ts @@ -0,0 +1,28 @@ +import { artifacts as ERC20Artifacts } from '@0x/contracts-erc20'; +import { BlockchainTestsEnvironment } from '@0x/contracts-test-utils'; + +import { artifacts } from '../artifacts'; +import { DeploymentManager } from '../framework/deployment_manager'; +import { TestEth2DaiBridgeContract, TestEth2DaiContract } from '../wrappers'; + +export async function deployEth2DaiBridgeAsync( + deployment: DeploymentManager, + environment: BlockchainTestsEnvironment, +): Promise<[TestEth2DaiBridgeContract, TestEth2DaiContract]> { + const eth2Dai = await TestEth2DaiContract.deployFrom0xArtifactAsync( + artifacts.TestEth2Dai, + environment.provider, + deployment.txDefaults, + artifacts, + ); + + const eth2DaiBridge = await TestEth2DaiBridgeContract.deployFrom0xArtifactAsync( + artifacts.TestEth2DaiBridge, + environment.provider, + deployment.txDefaults, + { ...ERC20Artifacts, ...artifacts }, + eth2Dai.address, + ); + + return [eth2DaiBridge, eth2Dai]; +} diff --git a/contracts/integrations/test/bridges/deploy_uniswap_bridge.ts b/contracts/integrations/test/bridges/deploy_uniswap_bridge.ts new file mode 100644 index 0000000000..60af9ba323 --- /dev/null +++ b/contracts/integrations/test/bridges/deploy_uniswap_bridge.ts @@ -0,0 +1,47 @@ +import { artifacts as ERC20Artifacts } from '@0x/contracts-erc20'; +import { BlockchainTestsEnvironment } from '@0x/contracts-test-utils'; + +import { artifacts } from '../artifacts'; +import { DeploymentManager } from '../framework/deployment_manager'; +import { + TestUniswapBridgeContract, + TestUniswapExchangeContract, + TestUniswapExchangeFactoryContract, +} from '../wrappers'; + +export async function deployUniswapBridgeAsync( + deployment: DeploymentManager, + environment: BlockchainTestsEnvironment, + tokenAddresses: string[], +): Promise<[TestUniswapBridgeContract, TestUniswapExchangeContract[], TestUniswapExchangeFactoryContract]> { + const uniswapExchangeFactory = await TestUniswapExchangeFactoryContract.deployFrom0xArtifactAsync( + artifacts.TestUniswapExchangeFactory, + environment.provider, + deployment.txDefaults, + artifacts, + ); + + const uniswapExchanges = []; + for (const tokenAddress of tokenAddresses) { + const uniswapExchange = await TestUniswapExchangeContract.deployFrom0xArtifactAsync( + artifacts.TestUniswapExchange, + environment.provider, + deployment.txDefaults, + artifacts, + tokenAddress, + ); + await uniswapExchangeFactory.addExchange(tokenAddress, uniswapExchange.address).awaitTransactionSuccessAsync(); + uniswapExchanges.push(uniswapExchange); + } + + const uniswapBridge = await TestUniswapBridgeContract.deployFrom0xArtifactAsync( + artifacts.TestUniswapBridge, + environment.provider, + deployment.txDefaults, + { ...ERC20Artifacts, ...artifacts }, + deployment.tokens.weth.address, + uniswapExchangeFactory.address, + ); + + return [uniswapBridge, uniswapExchanges, uniswapExchangeFactory]; +} diff --git a/contracts/integrations/test/coordinator/coordinator_test.ts b/contracts/integrations/test/coordinator/coordinator_test.ts index 40dd797619..28e98b6d1e 100644 --- a/contracts/integrations/test/coordinator/coordinator_test.ts +++ b/contracts/integrations/test/coordinator/coordinator_test.ts @@ -109,7 +109,7 @@ blockchainTests.resets('Coordinator integration tests', env => { msgValue?: BigNumber, ): Promise { let remainingValue = msgValue || constants.ZERO_AMOUNT; - const localBalanceStore = LocalBalanceStore.create(devUtils, balanceStore); + const localBalanceStore = LocalBalanceStore.create(balanceStore); // Transaction gas cost localBalanceStore.burnGas(txReceipt.from, DeploymentManager.gasPrice.times(txReceipt.gasUsed)); diff --git a/contracts/integrations/test/exchange/exchange_wrapper_test.ts b/contracts/integrations/test/exchange/exchange_wrapper_test.ts index c80fbb214e..ef057c6539 100644 --- a/contracts/integrations/test/exchange/exchange_wrapper_test.ts +++ b/contracts/integrations/test/exchange/exchange_wrapper_test.ts @@ -112,7 +112,7 @@ blockchainTests.resets('Exchange wrappers', env => { await blockchainBalances.updateBalancesAsync(); - initialLocalBalances = LocalBalanceStore.create(deployment.devUtils, blockchainBalances); + initialLocalBalances = LocalBalanceStore.create(blockchainBalances); wethAssetData = deployment.assetDataEncoder .ERC20Token(deployment.tokens.weth.address) @@ -120,7 +120,7 @@ blockchainTests.resets('Exchange wrappers', env => { }); beforeEach(async () => { - localBalances = LocalBalanceStore.create(deployment.devUtils, initialLocalBalances); + localBalances = LocalBalanceStore.create(initialLocalBalances); }); after(async () => { diff --git a/contracts/integrations/test/exchange/fill_order_wrapper.ts b/contracts/integrations/test/exchange/fill_order_wrapper.ts index cd58ca98cb..f7c349b01a 100644 --- a/contracts/integrations/test/exchange/fill_order_wrapper.ts +++ b/contracts/integrations/test/exchange/fill_order_wrapper.ts @@ -72,7 +72,7 @@ export class FillOrderWrapper { initBalanceStore: BalanceStore, opts: { takerAssetFillAmount?: BigNumber } = {}, ): Promise<[FillResults, FillEventArgs, BalanceStore]> { - const balanceStore = LocalBalanceStore.create(this._devUtils, initBalanceStore); + const balanceStore = LocalBalanceStore.create(initBalanceStore); const takerAssetFillAmount = opts.takerAssetFillAmount !== undefined ? opts.takerAssetFillAmount : signedOrder.takerAssetAmount; // TODO(jalextowle): Change this if the integration tests take protocol fees into account. diff --git a/contracts/integrations/test/exchange/fillorder_test.ts b/contracts/integrations/test/exchange/fillorder_test.ts index 00d83ff85c..7e8bf72457 100644 --- a/contracts/integrations/test/exchange/fillorder_test.ts +++ b/contracts/integrations/test/exchange/fillorder_test.ts @@ -118,7 +118,7 @@ blockchainTests.resets('fillOrder integration tests', env => { msgValue?: BigNumber, ): Promise { let remainingValue = msgValue !== undefined ? msgValue : DeploymentManager.protocolFee; - const localBalanceStore = LocalBalanceStore.create(deployment.devUtils, balanceStore); + const localBalanceStore = LocalBalanceStore.create(balanceStore); // Transaction gas cost localBalanceStore.burnGas(txReceipt.from, DeploymentManager.gasPrice.times(txReceipt.gasUsed)); @@ -266,7 +266,7 @@ blockchainTests.resets('fillOrder integration tests', env => { // Fetch the current balances await balanceStore.updateBalancesAsync(); - const expectedBalances = LocalBalanceStore.create(deployment.devUtils, balanceStore); + const expectedBalances = LocalBalanceStore.create(balanceStore); // End the epoch. This should wrap the staking proxy's ETH balance. const endEpochReceipt = await delegator.endEpochAsync(); diff --git a/contracts/integrations/test/forwarder/bridge_test.ts b/contracts/integrations/test/forwarder/bridge_test.ts new file mode 100644 index 0000000000..58b59082e0 --- /dev/null +++ b/contracts/integrations/test/forwarder/bridge_test.ts @@ -0,0 +1,352 @@ +import { IAssetDataContract } from '@0x/contracts-asset-proxy'; +import { DummyERC721TokenContract } from '@0x/contracts-erc721'; +import { ForwarderContract, ForwarderRevertErrors } from '@0x/contracts-exchange-forwarder'; +import { + blockchainTests, + constants, + getLatestBlockTimestampAsync, + hexConcat, + toBaseUnitAmount, +} from '@0x/contracts-test-utils'; +import { generatePseudoRandomSalt } from '@0x/order-utils'; +import { SignatureType, SignedOrder } from '@0x/types'; +import { AbiEncoder, BigNumber } from '@0x/utils'; + +import { deployEth2DaiBridgeAsync } from '../bridges/deploy_eth2dai_bridge'; +import { deployUniswapBridgeAsync } from '../bridges/deploy_uniswap_bridge'; +import { Actor } from '../framework/actors/base'; +import { FeeRecipient } from '../framework/actors/fee_recipient'; +import { Maker } from '../framework/actors/maker'; +import { Taker } from '../framework/actors/taker'; +import { actorAddressesByName } from '../framework/actors/utils'; +import { BlockchainBalanceStore } from '../framework/balances/blockchain_balance_store'; +import { DeploymentManager } from '../framework/deployment_manager'; + +import { deployForwarderAsync } from './deploy_forwarder'; +import { ForwarderTestFactory } from './forwarder_test_factory'; + +blockchainTests.resets('Forwarder <> ERC20Bridge integration tests', env => { + let deployment: DeploymentManager; + let forwarder: ForwarderContract; + let assetDataEncoder: IAssetDataContract; + let balanceStore: BlockchainBalanceStore; + let testFactory: ForwarderTestFactory; + + let erc721Token: DummyERC721TokenContract; + let nftId: BigNumber; + let makerTokenAssetData: string; + let makerFeeTokenAssetData: string; + let eth2DaiBridgeAssetData: string; + let uniswapBridgeAssetData: string; + + let maker: Maker; + let taker: Taker; + let orderFeeRecipient: FeeRecipient; + let forwarderFeeRecipient: FeeRecipient; + + let eth2DaiBridgeOrder: SignedOrder; + let uniswapBridgeOrder: SignedOrder; + + before(async () => { + assetDataEncoder = new IAssetDataContract(constants.NULL_ADDRESS, env.provider); + deployment = await DeploymentManager.deployAsync(env, { + numErc20TokensToDeploy: 2, + numErc721TokensToDeploy: 1, + numErc1155TokensToDeploy: 0, + }); + const [makerToken, makerFeeToken] = deployment.tokens.erc20; + [erc721Token] = deployment.tokens.erc721; + + forwarder = await deployForwarderAsync(deployment, env); + const [eth2DaiBridge] = await deployEth2DaiBridgeAsync(deployment, env); + const [uniswapBridge, [uniswapMakerTokenExchange]] = await deployUniswapBridgeAsync(deployment, env, [ + makerToken.address, + ]); + + makerTokenAssetData = assetDataEncoder.ERC20Token(makerToken.address).getABIEncodedTransactionData(); + makerFeeTokenAssetData = assetDataEncoder.ERC20Token(makerFeeToken.address).getABIEncodedTransactionData(); + const wethAssetData = assetDataEncoder + .ERC20Token(deployment.tokens.weth.address) + .getABIEncodedTransactionData(); + + const bridgeDataEncoder = AbiEncoder.create([{ name: 'fromTokenAddress', type: 'address' }]); + const bridgeData = bridgeDataEncoder.encode([deployment.tokens.weth.address]); + eth2DaiBridgeAssetData = assetDataEncoder + .ERC20Bridge(makerToken.address, eth2DaiBridge.address, bridgeData) + .getABIEncodedTransactionData(); + uniswapBridgeAssetData = assetDataEncoder + .ERC20Bridge(makerToken.address, uniswapBridge.address, bridgeData) + .getABIEncodedTransactionData(); + + taker = new Taker({ name: 'Taker', deployment }); + orderFeeRecipient = new FeeRecipient({ + name: 'Order fee recipient', + deployment, + }); + forwarderFeeRecipient = new FeeRecipient({ + name: 'Forwarder fee recipient', + deployment, + }); + + const fifteenMinutesInSeconds = 15 * 60; + const currentBlockTimestamp = await getLatestBlockTimestampAsync(); + const orderDefaults = { + chainId: deployment.chainId, + exchangeAddress: deployment.exchange.address, + takerAddress: constants.NULL_ADDRESS, + feeRecipientAddress: orderFeeRecipient.address, + senderAddress: constants.NULL_ADDRESS, + makerAssetAmount: toBaseUnitAmount(2), + takerAssetAmount: toBaseUnitAmount(1), + takerAssetData: wethAssetData, + makerFee: constants.ZERO_AMOUNT, + takerFee: constants.ZERO_AMOUNT, + makerFeeAssetData: makerFeeTokenAssetData, + takerFeeAssetData: wethAssetData, + expirationTimeSeconds: new BigNumber(currentBlockTimestamp).plus(fifteenMinutesInSeconds), + salt: generatePseudoRandomSalt(), + signature: hexConcat(SignatureType.Wallet), + }; + eth2DaiBridgeOrder = { + ...orderDefaults, + makerAddress: eth2DaiBridge.address, + makerAssetData: eth2DaiBridgeAssetData, + }; + uniswapBridgeOrder = { + ...orderDefaults, + makerAddress: uniswapBridge.address, + makerAssetData: uniswapBridgeAssetData, + }; + + maker = new Maker({ + name: 'Maker', + deployment, + orderConfig: { ...orderDefaults, makerFee: toBaseUnitAmount(0.01) }, + }); + await maker.configureERC20TokenAsync(makerToken); + await maker.configureERC20TokenAsync(makerFeeToken); + await forwarder.approveMakerAssetProxy(makerTokenAssetData).awaitTransactionSuccessAsync(); + [nftId] = await maker.configureERC721TokenAsync(erc721Token); + + // We need to top up the TestUniswapExchange with some ETH so that it can perform tokenToEthSwapInput + await uniswapMakerTokenExchange.topUpEth().awaitTransactionSuccessAsync({ + from: forwarderFeeRecipient.address, + value: constants.ONE_ETHER.times(10), + }); + + const tokenOwners = { + ...actorAddressesByName([maker, taker, orderFeeRecipient, forwarderFeeRecipient]), + Forwarder: forwarder.address, + StakingProxy: deployment.staking.stakingProxy.address, + }; + const tokenContracts = { + erc20: { makerToken, makerFeeToken, wETH: deployment.tokens.weth }, + erc721: { erc721Token }, + }; + const tokenIds = { erc721: { [erc721Token.address]: [nftId] } }; + balanceStore = new BlockchainBalanceStore(tokenOwners, tokenContracts, tokenIds); + + testFactory = new ForwarderTestFactory(forwarder, deployment, balanceStore, taker, forwarderFeeRecipient); + }); + + after(async () => { + Actor.count = 0; + }); + + describe('marketSellOrdersWithEth', () => { + it('should fully fill a single Eth2DaiBridge order without a taker fee', async () => { + await testFactory.marketSellTestAsync([eth2DaiBridgeOrder], 1); + }); + it('should partially fill a single Eth2DaiBridge order without a taker fee', async () => { + await testFactory.marketSellTestAsync([eth2DaiBridgeOrder], 0.34); + }); + it('should fill a single Eth2DaiBridge order with a WETH taker fee', async () => { + const order = { + ...eth2DaiBridgeOrder, + takerFee: toBaseUnitAmount(0.01), + }; + await testFactory.marketSellTestAsync([order], 0.78); + }); + it('should fill a single Eth2DaiBridge order with a percentage taker fee', async () => { + const order = { + ...eth2DaiBridgeOrder, + takerFee: toBaseUnitAmount(0.01), + takerFeeAssetData: makerTokenAssetData, + }; + await testFactory.marketSellTestAsync([order], 0.78); + }); + it('should fill an Eth2DaiBridge order along with non-bridge orders, with an affiliate fee', async () => { + const orders = [ + // ERC721 order + await maker.signOrderAsync({ + makerAssetAmount: new BigNumber(1), + makerAssetData: assetDataEncoder + .ERC721Token(erc721Token.address, nftId) + .getABIEncodedTransactionData(), + takerFee: toBaseUnitAmount(0.01), + }), + eth2DaiBridgeOrder, + await maker.signOrderAsync({ makerAssetData: makerTokenAssetData }), // Non-bridge order of the same ERC20 + ]; + await testFactory.marketSellTestAsync(orders, 2.56, { forwarderFeePercentage: 1 }); + }); + it('should fully fill a single UniswapBridge order without a taker fee', async () => { + await testFactory.marketSellTestAsync([uniswapBridgeOrder], 1); + }); + it('should partially fill a single UniswapBridge order without a taker fee', async () => { + await testFactory.marketSellTestAsync([uniswapBridgeOrder], 0.34); + }); + it('should fill a single UniswapBridge order with a WETH taker fee', async () => { + const order = { + ...uniswapBridgeOrder, + takerFee: toBaseUnitAmount(0.01), + }; + await testFactory.marketSellTestAsync([order], 0.78); + }); + it('should fill a single UniswapBridge order with a percentage taker fee', async () => { + const order = { + ...uniswapBridgeOrder, + takerFee: toBaseUnitAmount(0.01), + takerFeeAssetData: makerTokenAssetData, + }; + await testFactory.marketSellTestAsync([order], 0.78); + }); + it('should fill an UniswapBridge order along with non-bridge orders', async () => { + const orders = [ + // ERC721 order + await maker.signOrderAsync({ + makerAssetAmount: new BigNumber(1), + makerAssetData: assetDataEncoder + .ERC721Token(erc721Token.address, nftId) + .getABIEncodedTransactionData(), + takerFee: toBaseUnitAmount(0.01), + }), + uniswapBridgeOrder, + await maker.signOrderAsync({ makerAssetData: makerTokenAssetData }), // Non-bridge order of the same ERC20 + ]; + await testFactory.marketSellTestAsync(orders, 2.56, { forwarderFeePercentage: 1 }); + }); + it('should fill multiple bridge orders', async () => { + await testFactory.marketSellTestAsync([eth2DaiBridgeOrder, uniswapBridgeOrder], 1.23); + }); + it('should revert if the takerFee is denominated in a different token', async () => { + const order = { + ...eth2DaiBridgeOrder, + takerFee: toBaseUnitAmount(0.01), + takerFeeAssetData: makerFeeTokenAssetData, + }; + const expectedError = new ForwarderRevertErrors.UnsupportedFeeError(makerFeeTokenAssetData); + await testFactory.marketSellTestAsync([order], 1.23, { revertError: expectedError }); + }); + }); + describe('marketBuyOrdersWithEth', () => { + it('should fully fill a single Eth2DaiBridge order without a taker fee', async () => { + await testFactory.marketBuyTestAsync([eth2DaiBridgeOrder], 1); + }); + it('should partially fill a single Eth2DaiBridge order without a taker fee', async () => { + await testFactory.marketBuyTestAsync([eth2DaiBridgeOrder], 0.34); + }); + it('should return excess ETH', async () => { + await testFactory.marketBuyTestAsync([eth2DaiBridgeOrder], 1, { ethValueAdjustment: 1 }); + }); + it('should fill a single Eth2DaiBridge order with a WETH taker fee', async () => { + const order = { + ...eth2DaiBridgeOrder, + takerFee: toBaseUnitAmount(0.01), + }; + await testFactory.marketBuyTestAsync([order], 0.78); + }); + it('should fill a single Eth2DaiBridge order with a percentage taker fee', async () => { + const order = { + ...eth2DaiBridgeOrder, + takerFee: toBaseUnitAmount(0.01), + takerFeeAssetData: makerTokenAssetData, + }; + await testFactory.marketBuyTestAsync([order], 0.78); + }); + it('should fill an Eth2DaiBridge order along with non-bridge orders, with an affiliate fee', async () => { + const orders = [ + // ERC721 order + await maker.signOrderAsync({ + makerAssetAmount: new BigNumber(1), + makerAssetData: assetDataEncoder + .ERC721Token(erc721Token.address, nftId) + .getABIEncodedTransactionData(), + takerFee: toBaseUnitAmount(0.01), + }), + eth2DaiBridgeOrder, + await maker.signOrderAsync({ makerAssetData: makerTokenAssetData }), // Non-bridge order of the same ERC20 + ]; + await testFactory.marketBuyTestAsync(orders, 2.56, { forwarderFeePercentage: 1 }); + }); + it('should revert if the amount of ETH sent is too low to fill the makerAssetAmount (Eth2Dai)', async () => { + const expectedError = new ForwarderRevertErrors.CompleteBuyFailedError( + eth2DaiBridgeOrder.makerAssetAmount.times(0.5), + constants.ZERO_AMOUNT, + ); + await testFactory.marketBuyTestAsync([eth2DaiBridgeOrder], 0.5, { + ethValueAdjustment: -2, + revertError: expectedError, + }); + }); + it('should fully fill a single UniswapBridge order without a taker fee', async () => { + await testFactory.marketBuyTestAsync([uniswapBridgeOrder], 1); + }); + it('should partially fill a single UniswapBridge order without a taker fee', async () => { + await testFactory.marketBuyTestAsync([uniswapBridgeOrder], 0.34); + }); + it('should fill a single UniswapBridge order with a WETH taker fee', async () => { + const order = { + ...uniswapBridgeOrder, + takerFee: toBaseUnitAmount(0.01), + }; + await testFactory.marketBuyTestAsync([order], 0.78); + }); + it('should fill a single UniswapBridge order with a percentage taker fee', async () => { + const order = { + ...uniswapBridgeOrder, + takerFee: toBaseUnitAmount(0.01), + takerFeeAssetData: makerTokenAssetData, + }; + await testFactory.marketBuyTestAsync([order], 0.78); + }); + it('should fill an UniswapBridge order along with non-bridge orders', async () => { + const orders = [ + // ERC721 order + await maker.signOrderAsync({ + makerAssetAmount: new BigNumber(1), + makerAssetData: assetDataEncoder + .ERC721Token(erc721Token.address, nftId) + .getABIEncodedTransactionData(), + takerFee: toBaseUnitAmount(0.01), + }), + uniswapBridgeOrder, + await maker.signOrderAsync({ makerAssetData: makerTokenAssetData }), // Non-bridge order of the same ERC20 + ]; + await testFactory.marketBuyTestAsync(orders, 2.56, { forwarderFeePercentage: 1 }); + }); + it('should revert if the amount of ETH sent is too low to fill the makerAssetAmount (Uniswap)', async () => { + const expectedError = new ForwarderRevertErrors.CompleteBuyFailedError( + uniswapBridgeOrder.makerAssetAmount.times(0.5), + constants.ZERO_AMOUNT, + ); + await testFactory.marketBuyTestAsync([uniswapBridgeOrder], 0.5, { + ethValueAdjustment: -2, + revertError: expectedError, + }); + }); + it('should fill multiple bridge orders', async () => { + await testFactory.marketBuyTestAsync([eth2DaiBridgeOrder, uniswapBridgeOrder], 1.23); + }); + it('should revert if the takerFee is denominated in a different token', async () => { + const order = { + ...eth2DaiBridgeOrder, + takerFee: toBaseUnitAmount(0.01), + takerFeeAssetData: makerFeeTokenAssetData, + }; + const expectedError = new ForwarderRevertErrors.UnsupportedFeeError(makerFeeTokenAssetData); + await testFactory.marketBuyTestAsync([order], 1.23, { revertError: expectedError }); + }); + }); +}); +// tslint:disable:max-file-line-count diff --git a/contracts/integrations/test/forwarder/deploy_forwarder.ts b/contracts/integrations/test/forwarder/deploy_forwarder.ts index a2b37df398..9566ab35af 100644 --- a/contracts/integrations/test/forwarder/deploy_forwarder.ts +++ b/contracts/integrations/test/forwarder/deploy_forwarder.ts @@ -17,6 +17,6 @@ export async function deployForwarderAsync( deployment.txDefaults, { ...exchangeArtifacts, ...artifacts }, deployment.exchange.address, - deployment.assetDataEncoder.ERC20Token(deployment.tokens.weth.address).getABIEncodedTransactionData(), + deployment.tokens.weth.address, ); } diff --git a/contracts/integrations/test/forwarder/forwarder_test.ts b/contracts/integrations/test/forwarder/forwarder_test.ts index 438e14c805..16c782a2b5 100644 --- a/contracts/integrations/test/forwarder/forwarder_test.ts +++ b/contracts/integrations/test/forwarder/forwarder_test.ts @@ -110,11 +110,8 @@ blockchainTests('Forwarder integration tests', env => { forwarder, deployment, balanceStore, - maker, taker, - orderFeeRecipient, forwarderFeeRecipient, - devUtils, ); }); @@ -138,7 +135,7 @@ blockchainTests('Forwarder integration tests', env => { env.txDefaults, {}, exchange.address, - wethAssetData, + deployment.tokens.weth.address, ); await expect(deployForwarder).to.revertWith(new ForwarderRevertErrors.UnregisteredAssetProxyError()); }); @@ -202,7 +199,7 @@ blockchainTests('Forwarder integration tests', env => { from: taker.address, }); - const expectedBalances = LocalBalanceStore.create(devUtils, balanceStore); + const expectedBalances = LocalBalanceStore.create(balanceStore); expectedBalances.burnGas(tx.from, DeploymentManager.gasPrice.times(tx.gasUsed)); // Verify balances @@ -521,7 +518,7 @@ blockchainTests('Forwarder integration tests', env => { }); // Compute expected balances - const expectedBalances = LocalBalanceStore.create(devUtils, balanceStore); + const expectedBalances = LocalBalanceStore.create(balanceStore); await expectedBalances.transferAssetAsync( maker.address, taker.address, @@ -578,7 +575,7 @@ blockchainTests('Forwarder integration tests', env => { }); // Compute expected balances - const expectedBalances = LocalBalanceStore.create(devUtils, balanceStore); + const expectedBalances = LocalBalanceStore.create(balanceStore); await expectedBalances.transferAssetAsync( maker.address, taker.address, diff --git a/contracts/integrations/test/forwarder/forwarder_test_factory.ts b/contracts/integrations/test/forwarder/forwarder_test_factory.ts index dfaf8e4d13..4f61a67168 100644 --- a/contracts/integrations/test/forwarder/forwarder_test_factory.ts +++ b/contracts/integrations/test/forwarder/forwarder_test_factory.ts @@ -1,12 +1,19 @@ -import { DevUtilsContract } from '@0x/contracts-dev-utils'; +import { IAssetDataContract } from '@0x/contracts-asset-proxy'; import { ForwarderContract } from '@0x/contracts-exchange-forwarder'; -import { constants, expect, getPercentageOfValue, OrderStatus } from '@0x/contracts-test-utils'; -import { OrderInfo, SignedOrder } from '@0x/types'; +import { + constants, + expect, + getPercentageOfValue, + hexSlice, + Numberish, + OrderStatus, + provider, +} from '@0x/contracts-test-utils'; +import { AssetProxyId, OrderInfo, SignedOrder } from '@0x/types'; import { BigNumber, RevertError } from '@0x/utils'; import { TransactionReceiptWithDecodedLogs } from 'ethereum-types'; import { FeeRecipient } from '../framework/actors/fee_recipient'; -import { Maker } from '../framework/actors/maker'; import { Taker } from '../framework/actors/taker'; import { BlockchainBalanceStore } from '../framework/balances/blockchain_balance_store'; import { LocalBalanceStore } from '../framework/balances/local_balance_store'; @@ -20,7 +27,7 @@ interface ForwarderFillState { } interface MarketSellOptions { - forwarderFeePercentage: BigNumber; + forwarderFeePercentage: Numberish; revertError: RevertError; } @@ -28,16 +35,32 @@ interface MarketBuyOptions extends MarketSellOptions { ethValueAdjustment: number; // Used to provided insufficient/excess ETH } +function isPercentageFee(takerFeeAssetData: string, makerAssetData: string): boolean { + const makerAssetProxyId = hexSlice(makerAssetData, 0, 4); + const takerFeeAssetProxyId = hexSlice(takerFeeAssetData, 0, 4); + if (makerAssetProxyId === AssetProxyId.ERC20Bridge && takerFeeAssetProxyId === AssetProxyId.ERC20) { + const assetDataDecoder = new IAssetDataContract(constants.NULL_ADDRESS, provider); + const [makerTokenAddress] = assetDataDecoder.getABIDecodedTransactionData( + 'ERC20Bridge', + makerAssetData, + ); + const takerFeeTokenAddress = assetDataDecoder.getABIDecodedTransactionData( + 'ERC20Token', + takerFeeAssetData, + ); + return makerTokenAddress === takerFeeTokenAddress; + } else { + return makerAssetData === takerFeeAssetData; + } +} + export class ForwarderTestFactory { constructor( private readonly _forwarder: ForwarderContract, private readonly _deployment: DeploymentManager, private readonly _balanceStore: BlockchainBalanceStore, - private readonly _maker: Maker, private readonly _taker: Taker, - private readonly _orderFeeRecipient: FeeRecipient, private readonly _forwarderFeeRecipient: FeeRecipient, - private readonly _devUtils: DevUtilsContract, ) {} public async marketBuyTestAsync( @@ -164,7 +187,7 @@ export class ForwarderTestFactory { options: Partial, ): Promise { await this._balanceStore.updateBalancesAsync(); - const balances = LocalBalanceStore.create(this._devUtils, this._balanceStore); + const balances = LocalBalanceStore.create(this._balanceStore); const currentTotal = { wethSpentAmount: constants.ZERO_AMOUNT, makerAssetAcquiredAmount: constants.ZERO_AMOUNT, @@ -230,7 +253,7 @@ export class ForwarderTestFactory { let wethSpentAmount = takerAssetAmount.plus(DeploymentManager.protocolFee); let makerAssetAcquiredAmount = makerAssetAmount; - if (order.takerFeeAssetData === order.makerAssetData) { + if (isPercentageFee(order.takerFeeAssetData, order.makerAssetData)) { makerAssetAcquiredAmount = makerAssetAcquiredAmount.minus(takerFee); } else if (order.takerFeeAssetData === order.takerAssetData) { wethSpentAmount = wethSpentAmount.plus(takerFee); @@ -244,29 +267,29 @@ export class ForwarderTestFactory { // Maker -> Forwarder await balances.transferAssetAsync( - this._maker.address, + order.makerAddress, this._forwarder.address, makerAssetAmount, order.makerAssetData, ); // Maker -> Order fee recipient await balances.transferAssetAsync( - this._maker.address, - this._orderFeeRecipient.address, + order.makerAddress, + order.feeRecipientAddress, makerFee, order.makerFeeAssetData, ); // Forwarder -> Maker await balances.transferAssetAsync( this._forwarder.address, - this._maker.address, + order.makerAddress, takerAssetAmount, order.takerAssetData, ); // Forwarder -> Order fee recipient await balances.transferAssetAsync( this._forwarder.address, - this._orderFeeRecipient.address, + order.feeRecipientAddress, takerFee, order.takerFeeAssetData, ); diff --git a/contracts/integrations/test/framework/assertions/stake.ts b/contracts/integrations/test/framework/assertions/stake.ts index 5954a711ce..9783a97092 100644 --- a/contracts/integrations/test/framework/assertions/stake.ts +++ b/contracts/integrations/test/framework/assertions/stake.ts @@ -36,7 +36,7 @@ export function validStakeAssertion( return new FunctionAssertion(stakingWrapper.stake, { before: async (amount: BigNumber, txData: Partial) => { // Simulates the transfer of ZRX from staker to vault - const expectedBalances = LocalBalanceStore.create(deployment.devUtils, balanceStore); + const expectedBalances = LocalBalanceStore.create(balanceStore); await expectedBalances.transferAssetAsync( txData.from as string, zrxVault.address, diff --git a/contracts/integrations/test/framework/assertions/unstake.ts b/contracts/integrations/test/framework/assertions/unstake.ts index 9c854eb18d..5c3e26c193 100644 --- a/contracts/integrations/test/framework/assertions/unstake.ts +++ b/contracts/integrations/test/framework/assertions/unstake.ts @@ -36,7 +36,7 @@ export function validUnstakeAssertion( return new FunctionAssertion(stakingWrapper.unstake, { before: async (amount: BigNumber, txData: Partial) => { // Simulates the transfer of ZRX from vault to staker - const expectedBalances = LocalBalanceStore.create(deployment.devUtils, balanceStore); + const expectedBalances = LocalBalanceStore.create(balanceStore); await expectedBalances.transferAssetAsync( zrxVault.address, txData.from as string, diff --git a/contracts/integrations/test/framework/balances/local_balance_store.ts b/contracts/integrations/test/framework/balances/local_balance_store.ts index ed2a357e07..b6198e8d07 100644 --- a/contracts/integrations/test/framework/balances/local_balance_store.ts +++ b/contracts/integrations/test/framework/balances/local_balance_store.ts @@ -1,5 +1,5 @@ -import { DevUtilsContract } from '@0x/contracts-dev-utils'; -import { constants, Numberish } from '@0x/contracts-test-utils'; +import { IAssetDataContract } from '@0x/contracts-asset-proxy'; +import { constants, hexSlice, Numberish, provider } from '@0x/contracts-test-utils'; import { AssetProxyId } from '@0x/types'; import { BigNumber } from '@0x/utils'; import * as _ from 'lodash'; @@ -8,12 +8,14 @@ import { BalanceStore } from './balance_store'; import { TokenContractsByName, TokenOwnersByName } from './types'; export class LocalBalanceStore extends BalanceStore { + private _assetDataDecoder: IAssetDataContract; + /** * Creates a new balance store based on an existing one. * @param sourceBalanceStore Existing balance store whose values should be copied. */ - public static create(devUtils: DevUtilsContract, sourceBalanceStore?: BalanceStore): LocalBalanceStore { - const localBalanceStore = new LocalBalanceStore(devUtils); + public static create(sourceBalanceStore?: BalanceStore): LocalBalanceStore { + const localBalanceStore = new LocalBalanceStore(); if (sourceBalanceStore !== undefined) { localBalanceStore.cloneFrom(sourceBalanceStore); } @@ -26,11 +28,11 @@ export class LocalBalanceStore extends BalanceStore { * be initialized via `create`. */ protected constructor( - private readonly _devUtils: DevUtilsContract, tokenOwnersByName: TokenOwnersByName = {}, tokenContractsByName: Partial = {}, ) { super(tokenOwnersByName, tokenContractsByName); + this._assetDataDecoder = new IAssetDataContract(constants.NULL_ADDRESS, provider); } /** @@ -78,25 +80,41 @@ export class LocalBalanceStore extends BalanceStore { amount: BigNumber, assetData: string, ): Promise { - if (fromAddress === toAddress) { + if (fromAddress === toAddress || amount.isZero()) { return; } - const assetProxyId = await this._devUtils.decodeAssetProxyId(assetData).callAsync(); + const assetProxyId = hexSlice(assetData, 0, 4); switch (assetProxyId) { case AssetProxyId.ERC20: { - // tslint:disable-next-line:no-unused-variable - const [_proxyId, tokenAddress] = await this._devUtils.decodeERC20AssetData(assetData).callAsync(); + const tokenAddress = this._assetDataDecoder.getABIDecodedTransactionData( + 'ERC20Token', + assetData, + ); _.update(this.balances.erc20, [fromAddress, tokenAddress], balance => balance.minus(amount)); _.update(this.balances.erc20, [toAddress, tokenAddress], balance => (balance || constants.ZERO_AMOUNT).plus(amount), ); break; } + case AssetProxyId.ERC20Bridge: { + const [tokenAddress] = this._assetDataDecoder.getABIDecodedTransactionData<[string]>( + 'ERC20Bridge', + assetData, + ); + // The test bridge contract (TestEth2DaiBridge or TestUniswapBridge) will be the + // fromAddress in this case, and it simply mints the amount of token it needs to transfer. + _.update(this.balances.erc20, [fromAddress, tokenAddress], balance => + (balance || constants.ZERO_AMOUNT).minus(amount), + ); + _.update(this.balances.erc20, [toAddress, tokenAddress], balance => + (balance || constants.ZERO_AMOUNT).plus(amount), + ); + break; + } case AssetProxyId.ERC721: { - // tslint:disable-next-line:no-unused-variable - const [_proxyId, tokenAddress, tokenId] = await this._devUtils - .decodeERC721AssetData(assetData) - .callAsync(); + const [tokenAddress, tokenId] = this._assetDataDecoder.getABIDecodedTransactionData< + [string, BigNumber] + >('ERC721Token', assetData); const fromTokens = _.get(this.balances.erc721, [fromAddress, tokenAddress], []); const toTokens = _.get(this.balances.erc721, [toAddress, tokenAddress], []); if (amount.gte(1)) { @@ -112,12 +130,9 @@ export class LocalBalanceStore extends BalanceStore { break; } case AssetProxyId.ERC1155: { - const [ - _proxyId, // tslint:disable-line:no-unused-variable - tokenAddress, - tokenIds, - tokenValues, - ] = await this._devUtils.decodeERC1155AssetData(assetData).callAsync(); + const [tokenAddress, tokenIds, tokenValues] = this._assetDataDecoder.getABIDecodedTransactionData< + [string, BigNumber[], BigNumber[]] + >('ERC1155Assets', assetData); const fromBalances = { // tslint:disable-next-line:no-inferred-empty-object-type fungible: _.get(this.balances.erc1155, [fromAddress, tokenAddress, 'fungible'], {}), @@ -154,10 +169,9 @@ export class LocalBalanceStore extends BalanceStore { break; } case AssetProxyId.MultiAsset: { - // tslint:disable-next-line:no-unused-variable - const [_proxyId, amounts, nestedAssetData] = await this._devUtils - .decodeMultiAssetData(assetData) - .callAsync(); + const [amounts, nestedAssetData] = this._assetDataDecoder.getABIDecodedTransactionData< + [BigNumber[], string[]] + >('MultiAsset', assetData); for (const [i, amt] of amounts.entries()) { const nestedAmount = amount.times(amt); await this.transferAssetAsync(fromAddress, toAddress, nestedAmount, nestedAssetData[i]); diff --git a/contracts/integrations/test/framework/deployment_manager.ts b/contracts/integrations/test/framework/deployment_manager.ts index e67074d1c0..f1d3a2b87a 100644 --- a/contracts/integrations/test/framework/deployment_manager.ts +++ b/contracts/integrations/test/framework/deployment_manager.ts @@ -1,6 +1,7 @@ import { artifacts as assetProxyArtifacts, ERC1155ProxyContract, + ERC20BridgeProxyContract, ERC20ProxyContract, ERC721ProxyContract, IAssetDataContract, @@ -85,6 +86,7 @@ interface AssetProxyContracts { erc1155Proxy: ERC1155ProxyContract; multiAssetProxy: MultiAssetProxyContract; staticCallProxy: StaticCallProxyContract; + erc20BridgeProxy: ERC20BridgeProxyContract; } // Contract wrappers for all of the staking contracts @@ -189,6 +191,7 @@ export class DeploymentManager { assetProxies.erc721Proxy, assetProxies.erc1155Proxy, assetProxies.multiAssetProxy, + assetProxies.erc20BridgeProxy, exchange, staking.stakingProxy, ]); @@ -232,6 +235,7 @@ export class DeploymentManager { assetProxies.erc1155Proxy.address, assetProxies.multiAssetProxy.address, assetProxies.staticCallProxy.address, + assetProxies.erc20BridgeProxy.address, ], ); @@ -244,13 +248,19 @@ export class DeploymentManager { assetProxies.erc721Proxy.address, assetProxies.erc1155Proxy.address, assetProxies.staticCallProxy.address, + assetProxies.erc20BridgeProxy.address, ], ); // Add the multi-asset proxy as an authorized address of the token proxies. await batchAddAuthorizedAddressAsync( owner, - [assetProxies.erc20Proxy, assetProxies.erc721Proxy, assetProxies.erc1155Proxy], + [ + assetProxies.erc20Proxy, + assetProxies.erc721Proxy, + assetProxies.erc1155Proxy, + assetProxies.erc20BridgeProxy, + ], [assetProxies.multiAssetProxy.address], ); @@ -262,6 +272,7 @@ export class DeploymentManager { assetProxies.erc721Proxy, assetProxies.erc1155Proxy, assetProxies.multiAssetProxy, + assetProxies.erc20BridgeProxy, ], [exchange.address], ); @@ -327,12 +338,19 @@ export class DeploymentManager { txDefaults, assetProxyArtifacts, ); + const erc20BridgeProxy = await ERC20BridgeProxyContract.deployFrom0xArtifactAsync( + assetProxyArtifacts.ERC20BridgeProxy, + environment.provider, + txDefaults, + assetProxyArtifacts, + ); return { erc20Proxy, erc721Proxy, erc1155Proxy, multiAssetProxy, staticCallProxy, + erc20BridgeProxy, }; } diff --git a/contracts/integrations/test/wrappers.ts b/contracts/integrations/test/wrappers.ts index 74a642fc95..5effb14bb4 100644 --- a/contracts/integrations/test/wrappers.ts +++ b/contracts/integrations/test/wrappers.ts @@ -3,4 +3,9 @@ * Warning: This file is auto-generated by contracts-gen. Don't edit manually. * ----------------------------------------------------------------------------- */ +export * from '../test/generated-wrappers/test_eth2_dai'; +export * from '../test/generated-wrappers/test_eth2_dai_bridge'; export * from '../test/generated-wrappers/test_framework'; +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'; diff --git a/contracts/integrations/tsconfig.json b/contracts/integrations/tsconfig.json index 18b5c60dac..0ad453da1c 100644 --- a/contracts/integrations/tsconfig.json +++ b/contracts/integrations/tsconfig.json @@ -2,5 +2,13 @@ "extends": "../../tsconfig", "compilerOptions": { "outDir": "lib", "rootDir": ".", "resolveJsonModule": true }, "include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"], - "files": ["generated-artifacts/TestFramework.json", "test/generated-artifacts/TestFramework.json"] + "files": [ + "generated-artifacts/TestFramework.json", + "test/generated-artifacts/TestEth2Dai.json", + "test/generated-artifacts/TestEth2DaiBridge.json", + "test/generated-artifacts/TestFramework.json", + "test/generated-artifacts/TestUniswapBridge.json", + "test/generated-artifacts/TestUniswapExchange.json", + "test/generated-artifacts/TestUniswapExchangeFactory.json" + ] }