From 78d81f193f3b9358ab86819f83c76b8bcd52a9c9 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 10 Apr 2018 17:53:34 -0700 Subject: Asset Proxy Dispatcher --- packages/contracts/test/exchange/core.ts | 503 +++++++++++++++++++++++++++- packages/contracts/test/exchange/helpers.ts | 47 ++- packages/contracts/test/exchange/wrapper.ts | 90 ++++- 3 files changed, 620 insertions(+), 20 deletions(-) (limited to 'packages/contracts/test/exchange') diff --git a/packages/contracts/test/exchange/core.ts b/packages/contracts/test/exchange/core.ts index ef3b3b9ee..1277a88f3 100644 --- a/packages/contracts/test/exchange/core.ts +++ b/packages/contracts/test/exchange/core.ts @@ -1,13 +1,18 @@ import { LogWithDecodedArgs, TransactionReceiptWithDecodedLogs, ZeroEx } from '0x.js'; - import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils'; import { BigNumber } from '@0xproject/utils'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; import * as chai from 'chai'; import ethUtil = require('ethereumjs-util'); +import * as _ from 'lodash'; import * as Web3 from 'web3'; +import { AssetProxyDispatcherContract } from '../../src/contract_wrappers/generated/asset_proxy_dispatcher'; +import { DummyERC721TokenContract } from '../../src/contract_wrappers/generated/dummy_e_r_c721_token'; import { DummyTokenContract } from '../../src/contract_wrappers/generated/dummy_token'; +import { ERC20TransferProxyContract } from '../../src/contract_wrappers/generated/e_r_c20_transfer_proxy'; +import { ERC721TransferProxyContract } from '../../src/contract_wrappers/generated/e_r_c721_transfer_proxy'; +import { ERC20TransferProxy_v1Contract } from '../../src/contract_wrappers/generated/erc20transferproxy_v1'; import { CancelContractEventArgs, ExchangeContract, @@ -15,13 +20,25 @@ import { FillContractEventArgs, } from '../../src/contract_wrappers/generated/exchange'; import { TokenTransferProxyContract } from '../../src/contract_wrappers/generated/token_transfer_proxy'; +import { + encodeERC20ProxyMetadata, + encodeERC20ProxyMetadata_V1, + encodeERC721ProxyMetadata, +} from '../../src/utils/asset_proxy_utils'; import { Balances } from '../../src/utils/balances'; import { constants } from '../../src/utils/constants'; import { crypto } from '../../src/utils/crypto'; import { ExchangeWrapper } from '../../src/utils/exchange_wrapper'; import { OrderFactory } from '../../src/utils/order_factory'; import { orderUtils } from '../../src/utils/order_utils'; -import { BalancesByOwner, ContractName, ExchangeContractErrs, SignatureType, SignedOrder } from '../../src/utils/types'; +import { + AssetProxyId, + BalancesByOwner, + ContractName, + ExchangeContractErrs, + SignatureType, + SignedOrder, +} from '../../src/utils/types'; import { chaiSetup } from '../utils/chai_setup'; import { deployer } from '../utils/deployer'; import { provider, web3Wrapper } from '../utils/web3_wrapper'; @@ -35,14 +52,21 @@ describe('Exchange', () => { let tokenOwner: string; let takerAddress: string; let feeRecipientAddress: string; + let assetProxyManagerAddress: string; const INITIAL_BALANCE = ZeroEx.toBaseUnitAmount(new BigNumber(10000), 18); const INITIAL_ALLOWANCE = ZeroEx.toBaseUnitAmount(new BigNumber(10000), 18); let rep: DummyTokenContract; let dgd: DummyTokenContract; let zrx: DummyTokenContract; + let ck: DummyERC721TokenContract; + let et: DummyERC721TokenContract; let exchange: ExchangeContract; let tokenTransferProxy: TokenTransferProxyContract; + let assetProxyDispatcher: AssetProxyDispatcherContract; + let erc20TransferProxyV1: ERC20TransferProxy_v1Contract; + let erc20TransferProxy: ERC20TransferProxyContract; + let erc721TransferProxy: ERC721TransferProxyContract; let signedOrder: SignedOrder; let balances: BalancesByOwner; @@ -50,32 +74,104 @@ describe('Exchange', () => { let dmyBalances: Balances; let orderFactory: OrderFactory; + let erc721TransferProxyInstance: Web3.ContractInstance; + let zeroEx: ZeroEx; before(async () => { const accounts = await web3Wrapper.getAvailableAddressesAsync(); makerAddress = accounts[0]; - [tokenOwner, takerAddress, feeRecipientAddress] = accounts; - const [repInstance, dgdInstance, zrxInstance] = await Promise.all([ + [tokenOwner, takerAddress, feeRecipientAddress, assetProxyManagerAddress] = accounts; + const [repInstance, dgdInstance, zrxInstance, ckInstance, etInstance] = await Promise.all([ deployer.deployAsync(ContractName.DummyToken, constants.DUMMY_TOKEN_ARGS), deployer.deployAsync(ContractName.DummyToken, constants.DUMMY_TOKEN_ARGS), deployer.deployAsync(ContractName.DummyToken, constants.DUMMY_TOKEN_ARGS), + deployer.deployAsync(ContractName.DummyERC721Token, constants.DUMMY_ERC721TOKEN_ARGS), + deployer.deployAsync(ContractName.DummyERC721Token, constants.DUMMY_ERC721TOKEN_ARGS), ]); rep = new DummyTokenContract(repInstance.abi, repInstance.address, provider); dgd = new DummyTokenContract(dgdInstance.abi, dgdInstance.address, provider); zrx = new DummyTokenContract(zrxInstance.abi, zrxInstance.address, provider); + ck = new DummyERC721TokenContract(ckInstance.abi, ckInstance.address, provider); + et = new DummyERC721TokenContract(etInstance.abi, etInstance.address, provider); const tokenTransferProxyInstance = await deployer.deployAsync(ContractName.TokenTransferProxy); tokenTransferProxy = new TokenTransferProxyContract( tokenTransferProxyInstance.abi, tokenTransferProxyInstance.address, provider, ); + + const erc20TransferProxyV1Instance = await deployer.deployAsync(ContractName.ERC20TransferProxy_V1, [ + tokenTransferProxy.address, + ]); + erc20TransferProxyV1 = new ERC20TransferProxy_v1Contract( + erc20TransferProxyV1Instance.abi, + erc20TransferProxyV1Instance.address, + provider, + ); + + const erc20TransferProxyInstance = await deployer.deployAsync(ContractName.ERC20TransferProxy); + erc20TransferProxy = new ERC20TransferProxyContract( + erc20TransferProxyInstance.abi, + erc20TransferProxyInstance.address, + provider, + ); + + erc721TransferProxyInstance = await deployer.deployAsync(ContractName.ERC721TransferProxy); + erc721TransferProxy = new ERC721TransferProxyContract( + erc721TransferProxyInstance.abi, + erc721TransferProxyInstance.address, + provider, + ); + + const assetProxyDispatcherInstance = await deployer.deployAsync(ContractName.AssetProxyDispatcher); + assetProxyDispatcher = new AssetProxyDispatcherContract( + assetProxyDispatcherInstance.abi, + assetProxyDispatcherInstance.address, + provider, + ); + const exchangeInstance = await deployer.deployAsync(ContractName.Exchange, [ zrx.address, - tokenTransferProxy.address, + encodeERC20ProxyMetadata(zrx.address), + assetProxyDispatcher.address, ]); exchange = new ExchangeContract(exchangeInstance.abi, exchangeInstance.address, provider); - await tokenTransferProxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, { from: accounts[0] }); + await assetProxyDispatcher.addAuthorizedAddress.sendTransactionAsync(assetProxyManagerAddress, { + from: accounts[0], + }); + await assetProxyDispatcher.addAuthorizedAddress.sendTransactionAsync(exchange.address, { from: accounts[0] }); + await erc20TransferProxyV1.addAuthorizedAddress.sendTransactionAsync(assetProxyDispatcher.address, { + from: accounts[0], + }); + await erc20TransferProxy.addAuthorizedAddress.sendTransactionAsync(assetProxyDispatcher.address, { + from: accounts[0], + }); + await erc721TransferProxy.addAuthorizedAddress.sendTransactionAsync(assetProxyDispatcher.address, { + from: accounts[0], + }); + await tokenTransferProxy.addAuthorizedAddress.sendTransactionAsync(erc20TransferProxyV1.address, { + from: accounts[0], + }); + const nilAddress = '0x0000000000000000000000000000000000000000'; + await assetProxyDispatcher.setAssetProxy.sendTransactionAsync( + AssetProxyId.ERC20_V1, + erc20TransferProxyV1.address, + nilAddress, + { from: accounts[0] }, + ); + await assetProxyDispatcher.setAssetProxy.sendTransactionAsync( + AssetProxyId.ERC20, + erc20TransferProxy.address, + nilAddress, + { from: accounts[0] }, + ); + await assetProxyDispatcher.setAssetProxy.sendTransactionAsync( + AssetProxyId.ERC721, + erc721TransferProxy.address, + nilAddress, + { from: accounts[0] }, + ); zeroEx = new ZeroEx(provider, { exchangeContractAddress: exchange.address, networkId: constants.TESTRPC_NETWORK_ID, @@ -92,6 +188,8 @@ describe('Exchange', () => { takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(200), 18), makerFee: ZeroEx.toBaseUnitAmount(new BigNumber(1), 18), takerFee: ZeroEx.toBaseUnitAmount(new BigNumber(1), 18), + makerAssetProxyData: encodeERC20ProxyMetadata(rep.address), + takerAssetProxyData: encodeERC20ProxyMetadata(dgd.address), }; const privateKey = constants.TESTRPC_PRIVATE_KEYS[0]; orderFactory = new OrderFactory(privateKey, defaultOrderParams); @@ -103,6 +201,12 @@ describe('Exchange', () => { rep.approve.sendTransactionAsync(tokenTransferProxy.address, INITIAL_ALLOWANCE, { from: takerAddress, }), + rep.approve.sendTransactionAsync(erc20TransferProxy.address, INITIAL_ALLOWANCE, { + from: makerAddress, + }), + rep.approve.sendTransactionAsync(erc20TransferProxy.address, INITIAL_ALLOWANCE, { + from: takerAddress, + }), rep.setBalance.sendTransactionAsync(makerAddress, INITIAL_BALANCE, { from: tokenOwner }), rep.setBalance.sendTransactionAsync(takerAddress, INITIAL_BALANCE, { from: tokenOwner }), dgd.approve.sendTransactionAsync(tokenTransferProxy.address, INITIAL_ALLOWANCE, { @@ -111,6 +215,12 @@ describe('Exchange', () => { dgd.approve.sendTransactionAsync(tokenTransferProxy.address, INITIAL_ALLOWANCE, { from: takerAddress, }), + dgd.approve.sendTransactionAsync(erc20TransferProxy.address, INITIAL_ALLOWANCE, { + from: makerAddress, + }), + dgd.approve.sendTransactionAsync(erc20TransferProxy.address, INITIAL_ALLOWANCE, { + from: takerAddress, + }), dgd.setBalance.sendTransactionAsync(makerAddress, INITIAL_BALANCE, { from: tokenOwner }), dgd.setBalance.sendTransactionAsync(takerAddress, INITIAL_BALANCE, { from: tokenOwner }), zrx.approve.sendTransactionAsync(tokenTransferProxy.address, INITIAL_ALLOWANCE, { @@ -119,8 +229,109 @@ describe('Exchange', () => { zrx.approve.sendTransactionAsync(tokenTransferProxy.address, INITIAL_ALLOWANCE, { from: takerAddress, }), + zrx.approve.sendTransactionAsync(erc20TransferProxy.address, INITIAL_ALLOWANCE, { + from: makerAddress, + }), + zrx.approve.sendTransactionAsync(erc20TransferProxy.address, INITIAL_ALLOWANCE, { + from: takerAddress, + }), zrx.setBalance.sendTransactionAsync(makerAddress, INITIAL_BALANCE, { from: tokenOwner }), zrx.setBalance.sendTransactionAsync(takerAddress, INITIAL_BALANCE, { from: tokenOwner }), + + // Distribute ck ERC721 tokens to maker & taker + // maker owns [0x1010.., ... , 0x4040..] and taker owns [0x5050.., ..., 0x9090..] + ck.setApprovalForAll.sendTransactionAsync(erc721TransferProxy.address, true, { from: makerAddress }), + ck.setApprovalForAll.sendTransactionAsync(erc721TransferProxy.address, true, { from: takerAddress }), + ck.mint.sendTransactionAsync( + makerAddress, + new BigNumber('0x1010101010101010101010101010101010101010101010101010101010101010'), + { from: tokenOwner }, + ), + ck.mint.sendTransactionAsync( + makerAddress, + new BigNumber('0x2020202020202020202020202020202020202020202020202020202020202020'), + { from: tokenOwner }, + ), + ck.mint.sendTransactionAsync( + makerAddress, + new BigNumber('0x3030303030303030303030303030303030303030303030303030303030303030'), + { from: tokenOwner }, + ), + ck.mint.sendTransactionAsync( + makerAddress, + new BigNumber('0x4040404040404040404040404040404040404040404040404040404040404040'), + { from: tokenOwner }, + ), + ck.mint.sendTransactionAsync( + takerAddress, + new BigNumber('0x5050505050505050505050505050505050505050505050505050505050505050'), + { from: tokenOwner }, + ), + ck.mint.sendTransactionAsync( + takerAddress, + new BigNumber('0x6060606060606060606060606060606060606060606060606060606060606060'), + { from: tokenOwner }, + ), + ck.mint.sendTransactionAsync( + takerAddress, + new BigNumber('0x7070707070707070707070707070707070707070707070707070707070707070'), + { from: tokenOwner }, + ), + ck.mint.sendTransactionAsync( + takerAddress, + new BigNumber('0x8080808080808080808080808080808080808080808080808080808080808080'), + { from: tokenOwner }, + ), + ck.mint.sendTransactionAsync( + takerAddress, + new BigNumber('0x9090909090909090909090909090909090909090909090909090909090909090'), + { from: tokenOwner }, + ), + + // Distribute et ERC721 tokens to maker & taker + // maker owns [0x1010.., ... , 0x4040..] and taker owns [0x5050.., ..., 0x9090..] + et.setApprovalForAll.sendTransactionAsync(erc721TransferProxy.address, true, { from: makerAddress }), + et.setApprovalForAll.sendTransactionAsync(erc721TransferProxy.address, true, { from: takerAddress }), + et.mint.sendTransactionAsync( + makerAddress, + new BigNumber('0x1010101010101010101010101010101010101010101010101010101010101010'), + { from: tokenOwner }, + ), + et.mint.sendTransactionAsync( + makerAddress, + new BigNumber('0x2020202020202020202020202020202020202020202020202020202020202020'), + { from: tokenOwner }, + ), + et.mint.sendTransactionAsync( + makerAddress, + new BigNumber('0x3030303030303030303030303030303030303030303030303030303030303030'), + { from: tokenOwner }, + ), + et.mint.sendTransactionAsync( + takerAddress, + new BigNumber('0x5050505050505050505050505050505050505050505050505050505050505050'), + { from: tokenOwner }, + ), + et.mint.sendTransactionAsync( + takerAddress, + new BigNumber('0x6060606060606060606060606060606060606060606060606060606060606060'), + { from: tokenOwner }, + ), + et.mint.sendTransactionAsync( + takerAddress, + new BigNumber('0x7070707070707070707070707070707070707070707070707070707070707070'), + { from: tokenOwner }, + ), + et.mint.sendTransactionAsync( + takerAddress, + new BigNumber('0x8080808080808080808080808080808080808080808080808080808080808080'), + { from: tokenOwner }, + ), + et.mint.sendTransactionAsync( + takerAddress, + new BigNumber('0x9090909090909090909090909090909090909090909090909090909090909090'), + { from: tokenOwner }, + ), ]); }); beforeEach(async () => { @@ -749,4 +960,284 @@ describe('Exchange', () => { ); }); }); + + describe('Testing Exchange of ERC721 Tokens', () => { + it('should successfully exchange a single token between the maker and taker (via fillOrder)', async () => { + // Construct Exchange parameters + const makerTokenId = new BigNumber('0x1010101010101010101010101010101010101010101010101010101010101010'); + const takerTokenId = new BigNumber('0x9090909090909090909090909090909090909090909090909090909090909090'); + signedOrder = orderFactory.newSignedOrder({ + makerTokenAddress: ck.address, + takerTokenAddress: ck.address, + makerTokenAmount: new BigNumber(1), + takerTokenAmount: new BigNumber(1), + makerAssetProxyData: encodeERC721ProxyMetadata(ck.address, makerTokenId), + takerAssetProxyData: encodeERC721ProxyMetadata(ck.address, takerTokenId), + }); + + // Verify pre-conditions + const initialOwnerMakerToken = await ck.ownerOf.callAsync(makerTokenId); + expect(initialOwnerMakerToken).to.be.bignumber.equal(makerAddress); + const initialOwnerTakerToken = await ck.ownerOf.callAsync(takerTokenId); + expect(initialOwnerTakerToken).to.be.bignumber.equal(takerAddress); + + // Call Exchange + const takerTokenFillAmount = signedOrder.takerTokenAmount; + const res = await exWrapper.fillOrderAsync(signedOrder, takerAddress, { takerTokenFillAmount }); + + // Verify post-conditions + const newOwnerMakerToken = await ck.ownerOf.callAsync(makerTokenId); + expect(newOwnerMakerToken).to.be.bignumber.equal(takerAddress); + const newOwnerTakerToken = await ck.ownerOf.callAsync(takerTokenId); + expect(newOwnerTakerToken).to.be.bignumber.equal(makerAddress); + }); + + it('should successfully exchange a single token between the maker and taker (via filleOrderNoThrow)', async () => { + // Construct Exchange parameters + const makerTokenId = new BigNumber('0x1010101010101010101010101010101010101010101010101010101010101010'); + const takerTokenId = new BigNumber('0x9090909090909090909090909090909090909090909090909090909090909090'); + signedOrder = orderFactory.newSignedOrder({ + makerTokenAddress: ck.address, + takerTokenAddress: ck.address, + makerTokenAmount: new BigNumber(1), + takerTokenAmount: new BigNumber(1), + makerAssetProxyData: encodeERC721ProxyMetadata(ck.address, makerTokenId), + takerAssetProxyData: encodeERC721ProxyMetadata(ck.address, takerTokenId), + }); + + // Verify pre-conditions + const initialOwnerMakerToken = await ck.ownerOf.callAsync(makerTokenId); + expect(initialOwnerMakerToken).to.be.bignumber.equal(makerAddress); + const initialOwnerTakerToken = await ck.ownerOf.callAsync(takerTokenId); + expect(initialOwnerTakerToken).to.be.bignumber.equal(takerAddress); + + // Call Exchange + const takerTokenFillAmount = signedOrder.takerTokenAmount; + await exWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress, { takerTokenFillAmount }); + + // Verify post-conditions + const newOwnerMakerToken = await ck.ownerOf.callAsync(makerTokenId); + expect(newOwnerMakerToken).to.be.bignumber.equal(takerAddress); + const newOwnerTakerToken = await ck.ownerOf.callAsync(takerTokenId); + expect(newOwnerTakerToken).to.be.bignumber.equal(makerAddress); + }); + + it('should throw when maker does not own the token with id makerTokenId', async () => { + // Construct Exchange parameters + const makerTokenId = new BigNumber('0x5050505050505050505050505050505050505050505050505050505050505050'); + const takerTokenId = new BigNumber('0x9090909090909090909090909090909090909090909090909090909090909090'); + signedOrder = orderFactory.newSignedOrder({ + makerTokenAddress: ck.address, + takerTokenAddress: ck.address, + makerTokenAmount: new BigNumber(1), + takerTokenAmount: new BigNumber(1), + makerAssetProxyData: encodeERC721ProxyMetadata(ck.address, makerTokenId), + takerAssetProxyData: encodeERC721ProxyMetadata(ck.address, takerTokenId), + }); + + // Verify pre-conditions + const initialOwnerMakerToken = await ck.ownerOf.callAsync(makerTokenId); + expect(initialOwnerMakerToken).to.be.bignumber.not.equal(makerAddress); + const initialOwnerTakerToken = await ck.ownerOf.callAsync(takerTokenId); + expect(initialOwnerTakerToken).to.be.bignumber.equal(takerAddress); + + // Call Exchange + const takerTokenFillAmount = signedOrder.takerTokenAmount; + return expect( + exWrapper.fillOrderAsync(signedOrder, takerAddress, { takerTokenFillAmount }), + ).to.be.rejectedWith(constants.REVERT); + }); + + it('should throw when taker does not own the token with id takerTokenId', async () => { + // Construct Exchange parameters + const makerTokenId = new BigNumber('0x1010101010101010101010101010101010101010101010101010101010101010'); + const takerTokenId = new BigNumber('0x2020202020202020202020202020202020202020202020202020202020202020'); + signedOrder = orderFactory.newSignedOrder({ + makerTokenAddress: ck.address, + takerTokenAddress: ck.address, + makerTokenAmount: new BigNumber(1), + takerTokenAmount: new BigNumber(1), + makerAssetProxyData: encodeERC721ProxyMetadata(ck.address, makerTokenId), + takerAssetProxyData: encodeERC721ProxyMetadata(ck.address, takerTokenId), + }); + + // Verify pre-conditions + const initialOwnerMakerToken = await ck.ownerOf.callAsync(makerTokenId); + expect(initialOwnerMakerToken).to.be.bignumber.equal(makerAddress); + const initialOwnerTakerToken = await ck.ownerOf.callAsync(takerTokenId); + expect(initialOwnerTakerToken).to.be.bignumber.not.equal(takerAddress); + + // Call Exchange + const takerTokenFillAmount = signedOrder.takerTokenAmount; + return expect( + exWrapper.fillOrderAsync(signedOrder, takerAddress, { takerTokenFillAmount }), + ).to.be.rejectedWith(constants.REVERT); + }); + + it('should throw when makerTokenAmount is greater than 1', async () => { + // Construct Exchange parameters + const makerTokenId = new BigNumber('0x1010101010101010101010101010101010101010101010101010101010101010'); + const takerTokenId = new BigNumber('0x9090909090909090909090909090909090909090909090909090909090909090'); + signedOrder = orderFactory.newSignedOrder({ + makerTokenAddress: ck.address, + takerTokenAddress: ck.address, + makerTokenAmount: new BigNumber(2), + takerTokenAmount: new BigNumber(1), + makerAssetProxyData: encodeERC721ProxyMetadata(ck.address, makerTokenId), + takerAssetProxyData: encodeERC721ProxyMetadata(ck.address, takerTokenId), + }); + + // Verify pre-conditions + const initialOwnerMakerToken = await ck.ownerOf.callAsync(makerTokenId); + expect(initialOwnerMakerToken).to.be.bignumber.equal(makerAddress); + const initialOwnerTakerToken = await ck.ownerOf.callAsync(takerTokenId); + expect(initialOwnerTakerToken).to.be.bignumber.equal(takerAddress); + + // Call Exchange + const takerTokenFillAmount = signedOrder.takerTokenAmount; + return expect( + exWrapper.fillOrderAsync(signedOrder, takerAddress, { takerTokenFillAmount }), + ).to.be.rejectedWith(constants.REVERT); + }); + + it('should throw when takerTokenAmount is greater than 1', async () => { + // Construct Exchange parameters + const makerTokenId = new BigNumber('0x1010101010101010101010101010101010101010101010101010101010101010'); + const takerTokenId = new BigNumber('0x9090909090909090909090909090909090909090909090909090909090909090'); + signedOrder = orderFactory.newSignedOrder({ + makerTokenAddress: ck.address, + takerTokenAddress: ck.address, + makerTokenAmount: new BigNumber(1), + takerTokenAmount: new BigNumber(500), + makerAssetProxyData: encodeERC721ProxyMetadata(ck.address, makerTokenId), + takerAssetProxyData: encodeERC721ProxyMetadata(ck.address, takerTokenId), + }); + + // Verify pre-conditions + const initialOwnerMakerToken = await ck.ownerOf.callAsync(makerTokenId); + expect(initialOwnerMakerToken).to.be.bignumber.equal(makerAddress); + const initialOwnerTakerToken = await ck.ownerOf.callAsync(takerTokenId); + expect(initialOwnerTakerToken).to.be.bignumber.equal(takerAddress); + + // Call Exchange + const takerTokenFillAmount = signedOrder.takerTokenAmount; + return expect( + exWrapper.fillOrderAsync(signedOrder, takerAddress, { takerTokenFillAmount }), + ).to.be.rejectedWith(constants.REVERT); + }); + + it('should throw on partial fill', async () => { + // Construct Exchange parameters + const makerTokenId = new BigNumber('0x1010101010101010101010101010101010101010101010101010101010101010'); + const takerTokenId = new BigNumber('0x9090909090909090909090909090909090909090909090909090909090909090'); + signedOrder = orderFactory.newSignedOrder({ + makerTokenAddress: ck.address, + takerTokenAddress: ck.address, + makerTokenAmount: new BigNumber(1), + takerTokenAmount: new BigNumber(0), + makerAssetProxyData: encodeERC721ProxyMetadata(ck.address, makerTokenId), + takerAssetProxyData: encodeERC721ProxyMetadata(ck.address, takerTokenId), + }); + + // Verify pre-conditions + const initialOwnerMakerToken = await ck.ownerOf.callAsync(makerTokenId); + expect(initialOwnerMakerToken).to.be.bignumber.equal(makerAddress); + const initialOwnerTakerToken = await ck.ownerOf.callAsync(takerTokenId); + expect(initialOwnerTakerToken).to.be.bignumber.equal(takerAddress); + + // Call Exchange + const takerTokenFillAmount = signedOrder.takerTokenAmount; + return expect( + exWrapper.fillOrderAsync(signedOrder, takerAddress, { takerTokenFillAmount }), + ).to.be.rejectedWith(constants.REVERT); + }); + + it('should successfully fill order when makerToken is ERC721 and takerToken is ERC20', async () => { + // Construct Exchange parameters + const makerTokenId = new BigNumber('0x1010101010101010101010101010101010101010101010101010101010101010'); + signedOrder = orderFactory.newSignedOrder({ + makerTokenAddress: ck.address, + takerTokenAddress: dgd.address, + makerTokenAmount: new BigNumber(1), + takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18), + makerAssetProxyData: encodeERC721ProxyMetadata(ck.address, makerTokenId), + takerAssetProxyData: encodeERC20ProxyMetadata(dgd.address), + }); + + // Verify pre-conditions + const initialOwnerMakerToken = await ck.ownerOf.callAsync(makerTokenId); + expect(initialOwnerMakerToken).to.be.bignumber.equal(makerAddress); + + // Call Exchange + balances = await dmyBalances.getAsync(); + const takerTokenFillAmount = signedOrder.takerTokenAmount; + await exWrapper.fillOrderAsync(signedOrder, takerAddress, { takerTokenFillAmount }); + + // Verify ERC721 token was transferred from Maker to Taker + const newOwnerMakerToken = await ck.ownerOf.callAsync(makerTokenId); + expect(newOwnerMakerToken).to.be.bignumber.equal(takerAddress); + + // Verify ERC20 tokens were transferred from Taker to Maker & fees were paid correctly + const newBalances = await dmyBalances.getAsync(); + expect(newBalances[makerAddress][signedOrder.takerTokenAddress]).to.be.bignumber.equal( + balances[makerAddress][signedOrder.takerTokenAddress].add(takerTokenFillAmount), + ); + expect(newBalances[takerAddress][signedOrder.takerTokenAddress]).to.be.bignumber.equal( + balances[takerAddress][signedOrder.takerTokenAddress].minus(takerTokenFillAmount), + ); + expect(newBalances[makerAddress][zrx.address]).to.be.bignumber.equal( + balances[makerAddress][zrx.address].minus(signedOrder.makerFee), + ); + expect(newBalances[takerAddress][zrx.address]).to.be.bignumber.equal( + balances[takerAddress][zrx.address].minus(signedOrder.takerFee), + ); + expect(newBalances[feeRecipientAddress][zrx.address]).to.be.bignumber.equal( + balances[feeRecipientAddress][zrx.address].add(signedOrder.makerFee.add(signedOrder.takerFee)), + ); + }); + + it('should successfully fill order when makerToken is ERC20 and takerToken is ERC721', async () => { + // Construct Exchange parameters + const takerTokenId = new BigNumber('0x9090909090909090909090909090909090909090909090909090909090909090'); + signedOrder = orderFactory.newSignedOrder({ + takerTokenAddress: ck.address, + makerTokenAddress: dgd.address, + takerTokenAmount: new BigNumber(1), + makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18), + takerAssetProxyData: encodeERC721ProxyMetadata(ck.address, takerTokenId), + makerAssetProxyData: encodeERC20ProxyMetadata(dgd.address), + }); + + // Verify pre-conditions + const initialOwnerTakerToken = await ck.ownerOf.callAsync(takerTokenId); + expect(initialOwnerTakerToken).to.be.bignumber.equal(takerAddress); + + // Call Exchange + balances = await dmyBalances.getAsync(); + const takerTokenFillAmount = signedOrder.takerTokenAmount; + await exWrapper.fillOrderAsync(signedOrder, takerAddress, { takerTokenFillAmount }); + + // Verify ERC721 token was transferred from Taker to Maker + const newOwnerTakerToken = await ck.ownerOf.callAsync(takerTokenId); + expect(newOwnerTakerToken).to.be.bignumber.equal(makerAddress); + + // Verify ERC20 tokens were transferred from Maker to Taker & fees were paid correctly + const newBalances = await dmyBalances.getAsync(); + expect(newBalances[takerAddress][signedOrder.makerTokenAddress]).to.be.bignumber.equal( + balances[takerAddress][signedOrder.makerTokenAddress].add(signedOrder.makerTokenAmount), + ); + expect(newBalances[makerAddress][signedOrder.makerTokenAddress]).to.be.bignumber.equal( + balances[makerAddress][signedOrder.makerTokenAddress].minus(signedOrder.makerTokenAmount), + ); + expect(newBalances[makerAddress][zrx.address]).to.be.bignumber.equal( + balances[makerAddress][zrx.address].minus(signedOrder.makerFee), + ); + expect(newBalances[takerAddress][zrx.address]).to.be.bignumber.equal( + balances[takerAddress][zrx.address].minus(signedOrder.takerFee), + ); + expect(newBalances[feeRecipientAddress][zrx.address]).to.be.bignumber.equal( + balances[feeRecipientAddress][zrx.address].add(signedOrder.makerFee.add(signedOrder.takerFee)), + ); + }); + }); }); // tslint:disable-line:max-file-line-count diff --git a/packages/contracts/test/exchange/helpers.ts b/packages/contracts/test/exchange/helpers.ts index 4fa55efa1..37e53630e 100644 --- a/packages/contracts/test/exchange/helpers.ts +++ b/packages/contracts/test/exchange/helpers.ts @@ -6,11 +6,16 @@ import * as chai from 'chai'; import ethUtil = require('ethereumjs-util'); import { ExchangeContract } from '../../src/contract_wrappers/generated/exchange'; +import { + encodeERC20ProxyMetadata, + encodeERC20ProxyMetadata_V1, + encodeERC721ProxyMetadata, +} from '../../src/utils/asset_proxy_utils'; import { constants } from '../../src/utils/constants'; import { ExchangeWrapper } from '../../src/utils/exchange_wrapper'; import { OrderFactory } from '../../src/utils/order_factory'; import { orderUtils } from '../../src/utils/order_utils'; -import { ContractName, SignedOrder } from '../../src/utils/types'; +import { AssetProxyId, ContractName, SignedOrder } from '../../src/utils/types'; import { chaiSetup } from '../utils/chai_setup'; import { deployer } from '../utils/deployer'; import { provider, web3Wrapper } from '../utils/web3_wrapper'; @@ -23,6 +28,7 @@ const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); describe('Exchange', () => { let makerAddress: string; let feeRecipientAddress: string; + let assetProxyManagerAddress: string; let signedOrder: SignedOrder; let exchangeWrapper: ExchangeWrapper; @@ -30,9 +36,14 @@ describe('Exchange', () => { before(async () => { const accounts = await web3Wrapper.getAvailableAddressesAsync(); - [makerAddress, feeRecipientAddress] = accounts; + [makerAddress, feeRecipientAddress, assetProxyManagerAddress] = accounts; const tokenRegistry = await deployer.deployAsync(ContractName.TokenRegistry); const tokenTransferProxy = await deployer.deployAsync(ContractName.TokenTransferProxy); + const assetProxyDispatcher = await deployer.deployAsync(ContractName.AssetProxyDispatcher); + const erc20TransferProxyV1 = await deployer.deployAsync(ContractName.ERC20TransferProxy_V1, [ + tokenTransferProxy.address, + ]); + const erc20TransferProxy = await deployer.deployAsync(ContractName.ERC20TransferProxy); const [rep, dgd, zrx] = await Promise.all([ deployer.deployAsync(ContractName.DummyToken, constants.DUMMY_TOKEN_ARGS), deployer.deployAsync(ContractName.DummyToken, constants.DUMMY_TOKEN_ARGS), @@ -40,10 +51,36 @@ describe('Exchange', () => { ]); const exchangeInstance = await deployer.deployAsync(ContractName.Exchange, [ zrx.address, - tokenTransferProxy.address, + AssetProxyId.ERC20, + assetProxyDispatcher.address, ]); const exchange = new ExchangeContract(exchangeInstance.abi, exchangeInstance.address, provider); - await tokenTransferProxy.addAuthorizedAddress(exchange.address, { from: accounts[0] }); + await assetProxyDispatcher.addAuthorizedAddress.sendTransactionAsync(assetProxyManagerAddress, { + from: accounts[0], + }); + await assetProxyDispatcher.addAuthorizedAddress.sendTransactionAsync(exchange.address, { from: accounts[0] }); + await erc20TransferProxyV1.addAuthorizedAddress.sendTransactionAsync(assetProxyDispatcher.address, { + from: accounts[0], + }); + await erc20TransferProxy.addAuthorizedAddress.sendTransactionAsync(assetProxyDispatcher.address, { + from: accounts[0], + }); + await tokenTransferProxy.addAuthorizedAddress.sendTransactionAsync(erc20TransferProxyV1.address, { + from: accounts[0], + }); + const nilAddress = '0x0000000000000000000000000000000000000000'; + await assetProxyDispatcher.setAssetProxy.sendTransactionAsync( + AssetProxyId.ERC20_V1, + erc20TransferProxyV1.address, + nilAddress, + { from: accounts[0] }, + ); + await assetProxyDispatcher.setAssetProxy.sendTransactionAsync( + AssetProxyId.ERC20, + erc20TransferProxy.address, + nilAddress, + { from: accounts[0] }, + ); const zeroEx = new ZeroEx(provider, { networkId: constants.TESTRPC_NETWORK_ID }); exchangeWrapper = new ExchangeWrapper(exchange, zeroEx); const defaultOrderParams = { @@ -56,6 +93,8 @@ describe('Exchange', () => { takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(200), 18), makerFee: ZeroEx.toBaseUnitAmount(new BigNumber(1), 18), takerFee: ZeroEx.toBaseUnitAmount(new BigNumber(1), 18), + makerAssetProxyData: encodeERC20ProxyMetadata(rep.address), + takerAssetProxyData: encodeERC20ProxyMetadata(dgd.address), }; const privateKey = constants.TESTRPC_PRIVATE_KEYS[0]; orderFactory = new OrderFactory(privateKey, defaultOrderParams); diff --git a/packages/contracts/test/exchange/wrapper.ts b/packages/contracts/test/exchange/wrapper.ts index 0ba74e1e9..a58568adb 100644 --- a/packages/contracts/test/exchange/wrapper.ts +++ b/packages/contracts/test/exchange/wrapper.ts @@ -6,15 +6,23 @@ import * as chai from 'chai'; import * as _ from 'lodash'; import * as Web3 from 'web3'; +import { AssetProxyDispatcherContract } from '../../src/contract_wrappers/generated/asset_proxy_dispatcher'; import { DummyTokenContract } from '../../src/contract_wrappers/generated/dummy_token'; +import { ERC20TransferProxyContract } from '../../src/contract_wrappers/generated/e_r_c20_transfer_proxy'; +import { ERC20TransferProxy_v1Contract } from '../../src/contract_wrappers/generated/erc20transferproxy_v1'; import { ExchangeContract } from '../../src/contract_wrappers/generated/exchange'; import { TokenRegistryContract } from '../../src/contract_wrappers/generated/token_registry'; import { TokenTransferProxyContract } from '../../src/contract_wrappers/generated/token_transfer_proxy'; +import { + encodeERC20ProxyMetadata, + encodeERC20ProxyMetadata_V1, + encodeERC721ProxyMetadata, +} from '../../src/utils/asset_proxy_utils'; import { Balances } from '../../src/utils/balances'; import { constants } from '../../src/utils/constants'; import { ExchangeWrapper } from '../../src/utils/exchange_wrapper'; import { OrderFactory } from '../../src/utils/order_factory'; -import { BalancesByOwner, ContractName, SignedOrder } from '../../src/utils/types'; +import { AssetProxyId, BalancesByOwner, ContractName, SignedOrder } from '../../src/utils/types'; import { chaiSetup } from '../utils/chai_setup'; import { deployer } from '../utils/deployer'; import { provider, web3Wrapper } from '../utils/web3_wrapper'; @@ -28,6 +36,7 @@ describe('Exchange', () => { let tokenOwner: string; let takerAddress: string; let feeRecipientAddress: string; + let assetProxyManagerAddress: string; const INITIAL_BALANCE = ZeroEx.toBaseUnitAmount(new BigNumber(10000), 18); const INITIAL_ALLOWANCE = ZeroEx.toBaseUnitAmount(new BigNumber(10000), 18); @@ -38,6 +47,9 @@ describe('Exchange', () => { let exchange: ExchangeContract; let tokenRegistry: TokenRegistryContract; let tokenTransferProxy: TokenTransferProxyContract; + let assetProxyDispatcher: AssetProxyDispatcherContract; + let erc20TransferProxyV1: ERC20TransferProxy_v1Contract; + let erc20TransferProxy: ERC20TransferProxyContract; let balances: BalancesByOwner; @@ -48,7 +60,7 @@ describe('Exchange', () => { before(async () => { const accounts = await web3Wrapper.getAvailableAddressesAsync(); tokenOwner = accounts[0]; - [makerAddress, takerAddress, feeRecipientAddress] = accounts; + [makerAddress, takerAddress, feeRecipientAddress, assetProxyManagerAddress] = accounts; const [repInstance, dgdInstance, zrxInstance] = await Promise.all([ deployer.deployAsync(ContractName.DummyToken, constants.DUMMY_TOKEN_ARGS), deployer.deployAsync(ContractName.DummyToken, constants.DUMMY_TOKEN_ARGS), @@ -65,12 +77,58 @@ describe('Exchange', () => { tokenTransferProxyInstance.address, provider, ); + const erc20TransferProxyV1Instance = await deployer.deployAsync(ContractName.ERC20TransferProxy_V1, [ + tokenTransferProxy.address, + ]); + erc20TransferProxyV1 = new ERC20TransferProxy_v1Contract( + erc20TransferProxyV1Instance.abi, + erc20TransferProxyV1Instance.address, + provider, + ); + const erc20TransferProxyInstance = await deployer.deployAsync(ContractName.ERC20TransferProxy); + erc20TransferProxy = new ERC20TransferProxyContract( + erc20TransferProxyInstance.abi, + erc20TransferProxyInstance.address, + provider, + ); + const assetProxyDispatcherInstance = await deployer.deployAsync(ContractName.AssetProxyDispatcher); + assetProxyDispatcher = new AssetProxyDispatcherContract( + assetProxyDispatcherInstance.abi, + assetProxyDispatcherInstance.address, + provider, + ); const exchangeInstance = await deployer.deployAsync(ContractName.Exchange, [ zrx.address, - tokenTransferProxy.address, + encodeERC20ProxyMetadata(zrx.address), + assetProxyDispatcher.address, ]); exchange = new ExchangeContract(exchangeInstance.abi, exchangeInstance.address, provider); - await tokenTransferProxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, { from: accounts[0] }); + await assetProxyDispatcher.addAuthorizedAddress.sendTransactionAsync(assetProxyManagerAddress, { + from: accounts[0], + }); + await assetProxyDispatcher.addAuthorizedAddress.sendTransactionAsync(exchange.address, { from: accounts[0] }); + await erc20TransferProxyV1.addAuthorizedAddress.sendTransactionAsync(assetProxyDispatcher.address, { + from: accounts[0], + }); + await erc20TransferProxy.addAuthorizedAddress.sendTransactionAsync(assetProxyDispatcher.address, { + from: accounts[0], + }); + await tokenTransferProxy.addAuthorizedAddress.sendTransactionAsync(erc20TransferProxyV1.address, { + from: accounts[0], + }); + const nilAddress = '0x0000000000000000000000000000000000000000'; + await assetProxyDispatcher.setAssetProxy.sendTransactionAsync( + AssetProxyId.ERC20_V1, + erc20TransferProxyV1.address, + nilAddress, + { from: accounts[0] }, + ); + await assetProxyDispatcher.setAssetProxy.sendTransactionAsync( + AssetProxyId.ERC20, + erc20TransferProxy.address, + nilAddress, + { from: accounts[0] }, + ); const zeroEx = new ZeroEx(provider, { networkId: constants.TESTRPC_NETWORK_ID }); exWrapper = new ExchangeWrapper(exchange, zeroEx); @@ -84,6 +142,8 @@ describe('Exchange', () => { takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(200), 18), makerFee: ZeroEx.toBaseUnitAmount(new BigNumber(1), 18), takerFee: ZeroEx.toBaseUnitAmount(new BigNumber(1), 18), + makerAssetProxyData: encodeERC20ProxyMetadata(rep.address), + takerAssetProxyData: encodeERC20ProxyMetadata(dgd.address), }; const privateKey = constants.TESTRPC_PRIVATE_KEYS[0]; @@ -92,14 +152,20 @@ describe('Exchange', () => { await Promise.all([ rep.approve.sendTransactionAsync(tokenTransferProxy.address, INITIAL_ALLOWANCE, { from: makerAddress }), rep.approve.sendTransactionAsync(tokenTransferProxy.address, INITIAL_ALLOWANCE, { from: takerAddress }), + rep.approve.sendTransactionAsync(erc20TransferProxy.address, INITIAL_ALLOWANCE, { from: makerAddress }), + rep.approve.sendTransactionAsync(erc20TransferProxy.address, INITIAL_ALLOWANCE, { from: takerAddress }), rep.setBalance.sendTransactionAsync(makerAddress, INITIAL_BALANCE, { from: tokenOwner }), rep.setBalance.sendTransactionAsync(takerAddress, INITIAL_BALANCE, { from: tokenOwner }), dgd.approve.sendTransactionAsync(tokenTransferProxy.address, INITIAL_ALLOWANCE, { from: makerAddress }), dgd.approve.sendTransactionAsync(tokenTransferProxy.address, INITIAL_ALLOWANCE, { from: takerAddress }), + dgd.approve.sendTransactionAsync(erc20TransferProxy.address, INITIAL_ALLOWANCE, { from: makerAddress }), + dgd.approve.sendTransactionAsync(erc20TransferProxy.address, INITIAL_ALLOWANCE, { from: takerAddress }), dgd.setBalance.sendTransactionAsync(makerAddress, INITIAL_BALANCE, { from: tokenOwner }), dgd.setBalance.sendTransactionAsync(takerAddress, INITIAL_BALANCE, { from: tokenOwner }), zrx.approve.sendTransactionAsync(tokenTransferProxy.address, INITIAL_ALLOWANCE, { from: makerAddress }), zrx.approve.sendTransactionAsync(tokenTransferProxy.address, INITIAL_ALLOWANCE, { from: takerAddress }), + zrx.approve.sendTransactionAsync(erc20TransferProxy.address, INITIAL_ALLOWANCE, { from: makerAddress }), + zrx.approve.sendTransactionAsync(erc20TransferProxy.address, INITIAL_ALLOWANCE, { from: takerAddress }), zrx.setBalance.sendTransactionAsync(makerAddress, INITIAL_BALANCE, { from: tokenOwner }), zrx.setBalance.sendTransactionAsync(takerAddress, INITIAL_BALANCE, { from: tokenOwner }), ]); @@ -246,11 +312,11 @@ describe('Exchange', () => { it('should not change balances if maker allowances are too low to fill order', async () => { const signedOrder = orderFactory.newSignedOrder(); - await rep.approve.sendTransactionAsync(tokenTransferProxy.address, new BigNumber(0), { + await rep.approve.sendTransactionAsync(erc20TransferProxy.address, new BigNumber(0), { from: makerAddress, }); await exWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress); - await rep.approve.sendTransactionAsync(tokenTransferProxy.address, INITIAL_ALLOWANCE, { + await rep.approve.sendTransactionAsync(erc20TransferProxy.address, INITIAL_ALLOWANCE, { from: makerAddress, }); @@ -260,11 +326,11 @@ describe('Exchange', () => { it('should not change balances if taker allowances are too low to fill order', async () => { const signedOrder = orderFactory.newSignedOrder(); - await dgd.approve.sendTransactionAsync(tokenTransferProxy.address, new BigNumber(0), { + await dgd.approve.sendTransactionAsync(erc20TransferProxy.address, new BigNumber(0), { from: takerAddress, }); await exWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress); - await dgd.approve.sendTransactionAsync(tokenTransferProxy.address, INITIAL_ALLOWANCE, { + await dgd.approve.sendTransactionAsync(erc20TransferProxy.address, INITIAL_ALLOWANCE, { from: takerAddress, }); @@ -278,6 +344,7 @@ describe('Exchange', () => { makerTokenAddress: zrx.address, makerTokenAmount: makerZRXBalance, makerFee: new BigNumber(1), + makerAssetProxyData: encodeERC20ProxyMetadata(zrx.address), }); await exWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress); const newBalances = await dmyBalances.getAsync(); @@ -285,11 +352,12 @@ describe('Exchange', () => { }); it('should not change balances if makerTokenAddress is ZRX, makerTokenAmount + makerFee > maker allowance', async () => { - const makerZRXAllowance = await zrx.allowance.callAsync(makerAddress, tokenTransferProxy.address); + const makerZRXAllowance = await zrx.allowance.callAsync(makerAddress, erc20TransferProxy.address); const signedOrder = orderFactory.newSignedOrder({ makerTokenAddress: zrx.address, makerTokenAmount: new BigNumber(makerZRXAllowance), makerFee: new BigNumber(1), + makerAssetProxyData: encodeERC20ProxyMetadata(zrx.address), }); await exWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress); const newBalances = await dmyBalances.getAsync(); @@ -302,6 +370,7 @@ describe('Exchange', () => { takerTokenAddress: zrx.address, takerTokenAmount: takerZRXBalance, takerFee: new BigNumber(1), + takerAssetProxyData: encodeERC20ProxyMetadata(zrx.address), }); await exWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress); const newBalances = await dmyBalances.getAsync(); @@ -309,11 +378,12 @@ describe('Exchange', () => { }); it('should not change balances if takerTokenAddress is ZRX, takerTokenAmount + takerFee > taker allowance', async () => { - const takerZRXAllowance = await zrx.allowance.callAsync(takerAddress, tokenTransferProxy.address); + const takerZRXAllowance = await zrx.allowance.callAsync(takerAddress, erc20TransferProxy.address); const signedOrder = orderFactory.newSignedOrder({ takerTokenAddress: zrx.address, takerTokenAmount: new BigNumber(takerZRXAllowance), takerFee: new BigNumber(1), + takerAssetProxyData: encodeERC20ProxyMetadata(zrx.address), }); await exWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress); const newBalances = await dmyBalances.getAsync(); -- cgit