diff options
author | Leonid Logvinov <logvinov.leon@gmail.com> | 2018-07-18 16:38:07 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-07-18 16:38:07 +0800 |
commit | ab6bf6edc70d6bfd27cdb7becce838dda8720e39 (patch) | |
tree | a26b8d3c4cfaf7e8d2bb5f65d53b8a056b08985c /packages | |
parent | acff177c547dee049b97e4b051fe22e1efaf992c (diff) | |
parent | f20b496dca2dceb4f53ae20660798f8b8148a265 (diff) | |
download | dexon-sol-tools-ab6bf6edc70d6bfd27cdb7becce838dda8720e39.tar dexon-sol-tools-ab6bf6edc70d6bfd27cdb7becce838dda8720e39.tar.gz dexon-sol-tools-ab6bf6edc70d6bfd27cdb7becce838dda8720e39.tar.bz2 dexon-sol-tools-ab6bf6edc70d6bfd27cdb7becce838dda8720e39.tar.lz dexon-sol-tools-ab6bf6edc70d6bfd27cdb7becce838dda8720e39.tar.xz dexon-sol-tools-ab6bf6edc70d6bfd27cdb7becce838dda8720e39.tar.zst dexon-sol-tools-ab6bf6edc70d6bfd27cdb7becce838dda8720e39.zip |
Merge pull request #879 from 0xProject/feature/order-watcher-erc721-tests
Add ERC721 tests for order watcher v2
Diffstat (limited to 'packages')
17 files changed, 455 insertions, 269 deletions
diff --git a/packages/contract-wrappers/src/contract_wrappers/erc721_token_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/erc721_token_wrapper.ts index b7e5519c4..1133a4085 100644 --- a/packages/contract-wrappers/src/contract_wrappers/erc721_token_wrapper.ts +++ b/packages/contract-wrappers/src/contract_wrappers/erc721_token_wrapper.ts @@ -342,15 +342,17 @@ export class ERC721TokenWrapper extends ContractWrapper { const normalizedSenderAddress = senderAddress.toLowerCase(); const tokenContract = await this._getTokenContractAsync(normalizedTokenAddress); const ownerAddress = await this.getOwnerOfAsync(tokenAddress, tokenId); - const isApprovedForAll = await this.isApprovedForAllAsync( - normalizedTokenAddress, - ownerAddress, - normalizedSenderAddress, - ); - if (!isApprovedForAll) { - const approvedAddress = await this.getApprovedIfExistsAsync(normalizedTokenAddress, tokenId); - if (approvedAddress !== senderAddress) { - throw new Error(ContractWrappersError.ERC721NoApproval); + if (normalizedSenderAddress !== ownerAddress) { + const isApprovedForAll = await this.isApprovedForAllAsync( + normalizedTokenAddress, + ownerAddress, + normalizedSenderAddress, + ); + if (!isApprovedForAll) { + const approvedAddress = await this.getApprovedIfExistsAsync(normalizedTokenAddress, tokenId); + if (approvedAddress !== normalizedSenderAddress) { + throw new Error(ContractWrappersError.ERC721NoApproval); + } } } const txHash = await tokenContract.transferFrom.sendTransactionAsync( diff --git a/packages/contract-wrappers/test/exchange_wrapper_test.ts b/packages/contract-wrappers/test/exchange_wrapper_test.ts index 33da19aee..44f8a5680 100644 --- a/packages/contract-wrappers/test/exchange_wrapper_test.ts +++ b/packages/contract-wrappers/test/exchange_wrapper_test.ts @@ -53,7 +53,6 @@ describe('ExchangeWrapper', () => { await blockchainLifecycle.startAsync(); contractWrappers = new ContractWrappers(provider, config); exchangeContractAddress = contractWrappers.exchange.getContractAddress(); - const erc20ProxyAddress = contractWrappers.erc20Proxy.getContractAddress(); userAddresses = await web3Wrapper.getAvailableAddressesAsync(); zrxTokenAddress = tokenUtils.getProtocolTokenAddress(); fillScenarios = new FillScenarios( @@ -61,7 +60,8 @@ describe('ExchangeWrapper', () => { userAddresses, zrxTokenAddress, exchangeContractAddress, - erc20ProxyAddress, + contractWrappers.erc20Proxy.getContractAddress(), + contractWrappers.erc721Proxy.getContractAddress(), ); [coinbase, makerAddress, takerAddress, feeRecipient, anotherMakerAddress] = userAddresses; [makerTokenAddress, takerTokenAddress] = tokenUtils.getDummyERC20TokenAddresses(); diff --git a/packages/contracts/test/utils/forwarder_wrapper.ts b/packages/contracts/test/utils/forwarder_wrapper.ts index d227420ee..2f41efd26 100644 --- a/packages/contracts/test/utils/forwarder_wrapper.ts +++ b/packages/contracts/test/utils/forwarder_wrapper.ts @@ -145,7 +145,7 @@ export class ForwarderWrapper { feeProportion: number, makerAssetFillAmount: BigNumber, ): Promise<BigNumber> { - const makerAssetData = assetProxyUtils.decodeAssetData(orders[0].makerAssetData); + const makerAssetData = assetProxyUtils.decodeAssetDataOrThrow(orders[0].makerAssetData); const makerAssetToken = makerAssetData.tokenAddress; const params = formatters.createMarketBuyOrders(orders, makerAssetFillAmount); diff --git a/packages/fill-scenarios/package.json b/packages/fill-scenarios/package.json index 12a342415..cc43b4130 100644 --- a/packages/fill-scenarios/package.json +++ b/packages/fill-scenarios/package.json @@ -9,14 +9,14 @@ "build": "yarn pre_build && tsc", "pre_build": "run-s update_artifacts generate_contract_wrappers", "update_artifacts": "for i in ${npm_package_config_contracts}; do copyfiles -u 4 ../migrations/artifacts/2.0.0/$i.json lib/artifacts; done;", - "generate_contract_wrappers": "abi-gen --abis 'lib/artifacts/@(Exchange|ERC20Token).json' --template ../contract_templates/contract.handlebars --partials '../contract_templates/partials/**/*.handlebars' --output src/generated_contract_wrappers --backend ethers", + "generate_contract_wrappers": "abi-gen --abis 'lib/artifacts/@(Exchange|DummyERC20Token|DummyERC721Token).json' --template ../contract_templates/contract.handlebars --partials '../contract_templates/partials/**/*.handlebars' --output src/generated_contract_wrappers --backend ethers", "copy_monorepo_scripts": "copyfiles -u 3 './lib/src/monorepo_scripts/**/*' ./scripts", "clean": "shx rm -rf lib scripts src/generated_contract_wrappers", "lint": "tslint --project . --exclude **/src/generated_contract_wrappers/**/*", "manual:postpublish": "yarn build; node ./scripts/postpublish.js" }, "config": { - "contracts": "Exchange ERC20Token" + "contracts": "Exchange DummyERC20Token DummyERC721Token" }, "license": "Apache-2.0", "repository": { diff --git a/packages/fill-scenarios/src/artifacts.ts b/packages/fill-scenarios/src/artifacts.ts index f75374ba2..7f68ae26a 100644 --- a/packages/fill-scenarios/src/artifacts.ts +++ b/packages/fill-scenarios/src/artifacts.ts @@ -1,9 +1,11 @@ import { ContractArtifact } from '@0xproject/sol-compiler'; -import * as ERC20Token from './artifacts/ERC20Token.json'; +import * as DummyERC20Token from './artifacts/DummyERC20Token.json'; +import * as DummyERC721Token from './artifacts/DummyERC721Token.json'; import * as Exchange from './artifacts/Exchange.json'; export const artifacts = { - ERC20Token: (ERC20Token as any) as ContractArtifact, + DummyERC20Token: (DummyERC20Token as any) as ContractArtifact, + DummyERC721Token: (DummyERC721Token as any) as ContractArtifact, Exchange: (Exchange as any) as ContractArtifact, }; diff --git a/packages/fill-scenarios/src/constants.ts b/packages/fill-scenarios/src/constants.ts index ec2fe744a..5661b059f 100644 --- a/packages/fill-scenarios/src/constants.ts +++ b/packages/fill-scenarios/src/constants.ts @@ -1,3 +1,4 @@ export const constants = { + AWAIT_TRANSACTION_MINED_MS: 0, NULL_ADDRESS: '0x0000000000000000000000000000000000000000', }; diff --git a/packages/fill-scenarios/src/fill_scenarios.ts b/packages/fill-scenarios/src/fill_scenarios.ts new file mode 100644 index 000000000..8294657e7 --- /dev/null +++ b/packages/fill-scenarios/src/fill_scenarios.ts @@ -0,0 +1,298 @@ +import { assetProxyUtils, orderFactory } from '@0xproject/order-utils'; +import { AssetProxyId, ERC721AssetData, OrderWithoutExchangeAddress, SignedOrder } from '@0xproject/types'; +import { BigNumber } from '@0xproject/utils'; +import { Web3Wrapper } from '@0xproject/web3-wrapper'; +import { Provider } from 'ethereum-types'; +import * as _ from 'lodash'; + +import { artifacts } from './artifacts'; +import { constants } from './constants'; +import { DummyERC20TokenContract } from './generated_contract_wrappers/dummy_erc20_token'; +import { DummyERC721TokenContract } from './generated_contract_wrappers/dummy_erc721_token'; +import { ExchangeContract } from './generated_contract_wrappers/exchange'; + +export class FillScenarios { + private _web3Wrapper: Web3Wrapper; + private _userAddresses: string[]; + private _coinbase: string; + private _zrxTokenAddress: string; + private _exchangeAddress: string; + private _erc20ProxyAddress: string; + private _erc721ProxyAddress: string; + constructor( + provider: Provider, + userAddresses: string[], + zrxTokenAddress: string, + exchangeAddress: string, + erc20ProxyAddress: string, + erc721ProxyAddress: string, + ) { + this._web3Wrapper = new Web3Wrapper(provider); + this._userAddresses = userAddresses; + this._coinbase = userAddresses[0]; + this._zrxTokenAddress = zrxTokenAddress; + this._exchangeAddress = exchangeAddress; + this._erc20ProxyAddress = erc20ProxyAddress; + this._erc721ProxyAddress = erc721ProxyAddress; + } + public async createFillableSignedOrderAsync( + makerAssetData: string, + takerAssetData: string, + makerAddress: string, + takerAddress: string, + fillableAmount: BigNumber, + expirationTimeSeconds?: BigNumber, + ): Promise<SignedOrder> { + return this.createAsymmetricFillableSignedOrderAsync( + makerAssetData, + takerAssetData, + makerAddress, + takerAddress, + fillableAmount, + fillableAmount, + expirationTimeSeconds, + ); + } + public async createFillableSignedOrderWithFeesAsync( + makerAssetData: string, + takerAssetData: string, + makerFee: BigNumber, + takerFee: BigNumber, + makerAddress: string, + takerAddress: string, + fillableAmount: BigNumber, + feeRecepientAddress: string, + expirationTimeSeconds?: BigNumber, + ): Promise<SignedOrder> { + return this._createAsymmetricFillableSignedOrderWithFeesAsync( + makerAssetData, + takerAssetData, + makerFee, + takerFee, + makerAddress, + takerAddress, + fillableAmount, + fillableAmount, + feeRecepientAddress, + expirationTimeSeconds, + ); + } + public async createAsymmetricFillableSignedOrderAsync( + makerAssetData: string, + takerAssetData: string, + makerAddress: string, + takerAddress: string, + makerFillableAmount: BigNumber, + takerFillableAmount: BigNumber, + expirationTimeSeconds?: BigNumber, + ): Promise<SignedOrder> { + const makerFee = new BigNumber(0); + const takerFee = new BigNumber(0); + const feeRecepientAddress = constants.NULL_ADDRESS; + return this._createAsymmetricFillableSignedOrderWithFeesAsync( + makerAssetData, + takerAssetData, + makerFee, + takerFee, + makerAddress, + takerAddress, + makerFillableAmount, + takerFillableAmount, + feeRecepientAddress, + expirationTimeSeconds, + ); + } + public async createPartiallyFilledSignedOrderAsync( + makerAssetData: string, + takerAssetData: string, + takerAddress: string, + fillableAmount: BigNumber, + partialFillAmount: BigNumber, + ): Promise<SignedOrder> { + const [makerAddress] = this._userAddresses; + const signedOrder = await this.createAsymmetricFillableSignedOrderAsync( + makerAssetData, + takerAssetData, + makerAddress, + takerAddress, + fillableAmount, + fillableAmount, + ); + const exchangeInstance = new ExchangeContract( + artifacts.Exchange.compilerOutput.abi, + signedOrder.exchangeAddress, + this._web3Wrapper.getProvider(), + this._web3Wrapper.getContractDefaults(), + ); + + const orderWithoutExchangeAddress = _.omit(signedOrder, [ + 'signature', + 'exchangeAddress', + ]) as OrderWithoutExchangeAddress; + + const txHash = await exchangeInstance.fillOrder.sendTransactionAsync( + orderWithoutExchangeAddress, + partialFillAmount, + signedOrder.signature, + { from: takerAddress }, + ); + await this._web3Wrapper.awaitTransactionSuccessAsync(txHash, constants.AWAIT_TRANSACTION_MINED_MS); + return signedOrder; + } + private async _createAsymmetricFillableSignedOrderWithFeesAsync( + makerAssetData: string, + takerAssetData: string, + makerFee: BigNumber, + takerFee: BigNumber, + makerAddress: string, + takerAddress: string, + makerFillableAmount: BigNumber, + takerFillableAmount: BigNumber, + feeRecepientAddress: string, + expirationTimeSeconds?: BigNumber, + ): Promise<SignedOrder> { + const decodedMakerAssetData = assetProxyUtils.decodeAssetDataOrThrow(makerAssetData); + if (decodedMakerAssetData.assetProxyId === AssetProxyId.ERC20) { + await this._increaseERC20BalanceAndAllowanceAsync( + decodedMakerAssetData.tokenAddress, + makerAddress, + makerFillableAmount, + ); + } else { + if (!makerFillableAmount.equals(1)) { + throw new Error(`ERC721 makerFillableAmount should be equal 1. Found: ${makerFillableAmount}`); + } + await this._increaseERC721BalanceAndAllowanceAsync( + decodedMakerAssetData.tokenAddress, + makerAddress, + (decodedMakerAssetData as ERC721AssetData).tokenId, + ); + } + const decodedTakerAssetData = assetProxyUtils.decodeAssetDataOrThrow(takerAssetData); + if (decodedTakerAssetData.assetProxyId === AssetProxyId.ERC20) { + const takerTokenAddress = decodedTakerAssetData.tokenAddress; + await this._increaseERC20BalanceAndAllowanceAsync(takerTokenAddress, takerAddress, takerFillableAmount); + } else { + if (!takerFillableAmount.equals(1)) { + throw new Error(`ERC721 takerFillableAmount should be equal 1. Found: ${takerFillableAmount}`); + } + await this._increaseERC721BalanceAndAllowanceAsync( + decodedTakerAssetData.tokenAddress, + takerAddress, + (decodedMakerAssetData as ERC721AssetData).tokenId, + ); + } + // Fees + await Promise.all([ + this._increaseERC20BalanceAndAllowanceAsync(this._zrxTokenAddress, makerAddress, makerFee), + this._increaseERC20BalanceAndAllowanceAsync(this._zrxTokenAddress, takerAddress, takerFee), + ]); + const senderAddress = constants.NULL_ADDRESS; + + const signedOrder = await orderFactory.createSignedOrderAsync( + this._web3Wrapper.getProvider(), + makerAddress, + takerAddress, + senderAddress, + makerFee, + takerFee, + makerFillableAmount, + makerAssetData, + takerFillableAmount, + takerAssetData, + this._exchangeAddress, + feeRecepientAddress, + expirationTimeSeconds, + ); + return signedOrder; + } + private async _increaseERC721BalanceAndAllowanceAsync( + tokenAddress: string, + address: string, + tokenId: BigNumber, + ): Promise<void> { + await this._increaseERC721BalanceAsync(tokenAddress, address, tokenId); + await this._increaseERC721AllowanceAsync(tokenAddress, address, tokenId); + } + private async _increaseERC721AllowanceAsync( + tokenAddress: string, + address: string, + tokenId: BigNumber, + ): Promise<void> { + const erc721Token = new DummyERC721TokenContract( + artifacts.DummyERC721Token.compilerOutput.abi, + tokenAddress, + this._web3Wrapper.getProvider(), + this._web3Wrapper.getContractDefaults(), + ); + const txHash = await erc721Token.approve.sendTransactionAsync(this._erc721ProxyAddress, tokenId, { + from: address, + }); + await this._web3Wrapper.awaitTransactionSuccessAsync(txHash, constants.AWAIT_TRANSACTION_MINED_MS); + } + private async _increaseERC721BalanceAsync( + tokenAddress: string, + address: string, + tokenId: BigNumber, + ): Promise<void> { + const erc721Token = new DummyERC721TokenContract( + artifacts.DummyERC721Token.compilerOutput.abi, + tokenAddress, + this._web3Wrapper.getProvider(), + this._web3Wrapper.getContractDefaults(), + ); + try { + const currentOwner = await erc721Token.ownerOf.callAsync(tokenId); + if (currentOwner !== address) { + throw new Error(`Token ${tokenAddress}:${tokenId} is already owner by ${currentOwner}`); + } + } catch (err) { + const txHash = await erc721Token.mint.sendTransactionAsync(address, tokenId, { from: this._coinbase }); + await this._web3Wrapper.awaitTransactionSuccessAsync(txHash, constants.AWAIT_TRANSACTION_MINED_MS); + } + } + private async _increaseERC20BalanceAndAllowanceAsync( + tokenAddress: string, + address: string, + amount: BigNumber, + ): Promise<void> { + if (amount.isZero() || address === constants.NULL_ADDRESS) { + return; // noop + } + await Promise.all([ + this._increaseERC20BalanceAsync(tokenAddress, address, amount), + this._increaseERC20AllowanceAsync(tokenAddress, address, amount), + ]); + } + private async _increaseERC20BalanceAsync(tokenAddress: string, address: string, amount: BigNumber): Promise<void> { + const erc20Token = new DummyERC20TokenContract( + artifacts.DummyERC20Token.compilerOutput.abi, + tokenAddress, + this._web3Wrapper.getProvider(), + this._web3Wrapper.getContractDefaults(), + ); + const txHash = await erc20Token.transfer.sendTransactionAsync(address, amount, { + from: this._coinbase, + }); + await this._web3Wrapper.awaitTransactionSuccessAsync(txHash, constants.AWAIT_TRANSACTION_MINED_MS); + } + private async _increaseERC20AllowanceAsync( + tokenAddress: string, + address: string, + amount: BigNumber, + ): Promise<void> { + const erc20Token = new DummyERC20TokenContract( + artifacts.DummyERC20Token.compilerOutput.abi, + tokenAddress, + this._web3Wrapper.getProvider(), + this._web3Wrapper.getContractDefaults(), + ); + const oldMakerAllowance = await erc20Token.allowance.callAsync(address, this._erc20ProxyAddress); + const newMakerAllowance = oldMakerAllowance.plus(amount); + + const txHash = await erc20Token.approve.sendTransactionAsync(this._erc20ProxyAddress, newMakerAllowance, { + from: address, + }); + await this._web3Wrapper.awaitTransactionSuccessAsync(txHash, constants.AWAIT_TRANSACTION_MINED_MS); + } +} diff --git a/packages/fill-scenarios/src/index.ts b/packages/fill-scenarios/src/index.ts index f9b69e1f3..c51145cdb 100644 --- a/packages/fill-scenarios/src/index.ts +++ b/packages/fill-scenarios/src/index.ts @@ -1,222 +1 @@ -import { assetProxyUtils, orderFactory } from '@0xproject/order-utils'; -import { OrderWithoutExchangeAddress, SignedOrder } from '@0xproject/types'; -import { BigNumber } from '@0xproject/utils'; -import { Web3Wrapper } from '@0xproject/web3-wrapper'; -import { Provider } from 'ethereum-types'; -import * as _ from 'lodash'; - -import { artifacts } from './artifacts'; -import { constants } from './constants'; -import { ERC20TokenContract } from './generated_contract_wrappers/erc20_token'; -import { ExchangeContract } from './generated_contract_wrappers/exchange'; - -export class FillScenarios { - private _web3Wrapper: Web3Wrapper; - private _userAddresses: string[]; - private _coinbase: string; - private _zrxTokenAddress: string; - private _exchangeAddress: string; - private _erc20ProxyAddress: string; - constructor( - provider: Provider, - userAddresses: string[], - zrxTokenAddress: string, - exchangeAddress: string, - erc20ProxyAddress: string, - ) { - this._web3Wrapper = new Web3Wrapper(provider); - this._userAddresses = userAddresses; - this._coinbase = userAddresses[0]; - this._zrxTokenAddress = zrxTokenAddress; - this._exchangeAddress = exchangeAddress; - this._erc20ProxyAddress = erc20ProxyAddress; - } - public async createFillableSignedOrderAsync( - makerAssetData: string, - takerAssetData: string, - makerAddress: string, - takerAddress: string, - fillableAmount: BigNumber, - expirationTimeSeconds?: BigNumber, - ): Promise<SignedOrder> { - return this.createAsymmetricFillableSignedOrderAsync( - makerAssetData, - takerAssetData, - makerAddress, - takerAddress, - fillableAmount, - fillableAmount, - expirationTimeSeconds, - ); - } - public async createFillableSignedOrderWithFeesAsync( - makerAssetData: string, - takerAssetData: string, - makerFee: BigNumber, - takerFee: BigNumber, - makerAddress: string, - takerAddress: string, - fillableAmount: BigNumber, - feeRecepientAddress: string, - expirationTimeSeconds?: BigNumber, - ): Promise<SignedOrder> { - return this._createAsymmetricFillableSignedOrderWithFeesAsync( - makerAssetData, - takerAssetData, - makerFee, - takerFee, - makerAddress, - takerAddress, - fillableAmount, - fillableAmount, - feeRecepientAddress, - expirationTimeSeconds, - ); - } - public async createAsymmetricFillableSignedOrderAsync( - makerAssetData: string, - takerAssetData: string, - makerAddress: string, - takerAddress: string, - makerFillableAmount: BigNumber, - takerFillableAmount: BigNumber, - expirationTimeSeconds?: BigNumber, - ): Promise<SignedOrder> { - const makerFee = new BigNumber(0); - const takerFee = new BigNumber(0); - const feeRecepientAddress = constants.NULL_ADDRESS; - return this._createAsymmetricFillableSignedOrderWithFeesAsync( - makerAssetData, - takerAssetData, - makerFee, - takerFee, - makerAddress, - takerAddress, - makerFillableAmount, - takerFillableAmount, - feeRecepientAddress, - expirationTimeSeconds, - ); - } - public async createPartiallyFilledSignedOrderAsync( - makerAssetData: string, - takerAssetData: string, - takerAddress: string, - fillableAmount: BigNumber, - partialFillAmount: BigNumber, - ): Promise<SignedOrder> { - const [makerAddress] = this._userAddresses; - const signedOrder = await this.createAsymmetricFillableSignedOrderAsync( - makerAssetData, - takerAssetData, - makerAddress, - takerAddress, - fillableAmount, - fillableAmount, - ); - const exchangeInstance = new ExchangeContract( - artifacts.Exchange.compilerOutput.abi, - signedOrder.exchangeAddress, - this._web3Wrapper.getProvider(), - this._web3Wrapper.getContractDefaults(), - ); - - const orderWithoutExchangeAddress = _.omit(signedOrder, [ - 'signature', - 'exchangeAddress', - ]) as OrderWithoutExchangeAddress; - - await exchangeInstance.fillOrder.sendTransactionAsync( - orderWithoutExchangeAddress, - partialFillAmount, - signedOrder.signature, - { from: takerAddress }, - ); - return signedOrder; - } - private async _createAsymmetricFillableSignedOrderWithFeesAsync( - makerAssetData: string, - takerAssetData: string, - makerFee: BigNumber, - takerFee: BigNumber, - makerAddress: string, - takerAddress: string, - makerFillableAmount: BigNumber, - takerFillableAmount: BigNumber, - feeRecepientAddress: string, - expirationTimeSeconds?: BigNumber, - ): Promise<SignedOrder> { - const makerERC20AssetData = assetProxyUtils.decodeERC20AssetData(makerAssetData); - const makerTokenAddress = makerERC20AssetData.tokenAddress; - const takerERC20AssetData = assetProxyUtils.decodeERC20AssetData(takerAssetData); - const takerTokenAddress = takerERC20AssetData.tokenAddress; - await Promise.all([ - this._increaseERC20BalanceAndAllowanceAsync(makerTokenAddress, makerAddress, makerFillableAmount), - this._increaseERC20BalanceAndAllowanceAsync(takerTokenAddress, takerAddress, takerFillableAmount), - ]); - await Promise.all([ - this._increaseERC20BalanceAndAllowanceAsync(this._zrxTokenAddress, makerAddress, makerFee), - this._increaseERC20BalanceAndAllowanceAsync(this._zrxTokenAddress, takerAddress, takerFee), - ]); - const senderAddress = constants.NULL_ADDRESS; - - const signedOrder = await orderFactory.createSignedOrderAsync( - this._web3Wrapper.getProvider(), - makerAddress, - takerAddress, - senderAddress, - makerFee, - takerFee, - makerFillableAmount, - makerAssetData, - takerFillableAmount, - takerAssetData, - this._exchangeAddress, - feeRecepientAddress, - expirationTimeSeconds, - ); - return signedOrder; - } - private async _increaseERC20BalanceAndAllowanceAsync( - tokenAddress: string, - address: string, - amount: BigNumber, - ): Promise<void> { - if (amount.isZero() || address === constants.NULL_ADDRESS) { - return; // noop - } - await Promise.all([ - this._increaseERC20BalanceAsync(tokenAddress, address, amount), - this._increaseERC20AllowanceAsync(tokenAddress, address, amount), - ]); - } - private async _increaseERC20BalanceAsync(tokenAddress: string, address: string, amount: BigNumber): Promise<void> { - const token = new ERC20TokenContract( - artifacts.ERC20Token.compilerOutput.abi, - tokenAddress, - this._web3Wrapper.getProvider(), - this._web3Wrapper.getContractDefaults(), - ); - await token.transfer.sendTransactionAsync(address, amount, { - from: this._coinbase, - }); - } - private async _increaseERC20AllowanceAsync( - tokenAddress: string, - address: string, - amount: BigNumber, - ): Promise<void> { - const tokenInstance = new ERC20TokenContract( - artifacts.ERC20Token.compilerOutput.abi, - tokenAddress, - this._web3Wrapper.getProvider(), - this._web3Wrapper.getContractDefaults(), - ); - const oldMakerAllowance = await tokenInstance.allowance.callAsync(address, this._erc20ProxyAddress); - const newMakerAllowance = oldMakerAllowance.plus(amount); - - await tokenInstance.approve.sendTransactionAsync(this._erc20ProxyAddress, newMakerAllowance, { - from: address, - }); - } -} +export { FillScenarios } from './fill_scenarios'; diff --git a/packages/order-utils/src/asset_proxy_utils.ts b/packages/order-utils/src/asset_proxy_utils.ts index 8140ad89d..21acb065a 100644 --- a/packages/order-utils/src/asset_proxy_utils.ts +++ b/packages/order-utils/src/asset_proxy_utils.ts @@ -134,7 +134,7 @@ export const assetProxyUtils = { const assetProxyId = assetProxyUtils.decodeAssetProxyId(encodedAssetProxyId); return assetProxyId; }, - decodeAssetData(assetData: string): ERC20AssetData | ERC721AssetData { + decodeAssetDataOrThrow(assetData: string): ERC20AssetData | ERC721AssetData { const assetProxyId = assetProxyUtils.decodeAssetDataId(assetData); switch (assetProxyId) { case AssetProxyId.ERC20: diff --git a/packages/order-utils/src/store/balance_and_proxy_allowance_lazy_store.ts b/packages/order-utils/src/store/balance_and_proxy_allowance_lazy_store.ts index 8235725ed..711bd2173 100644 --- a/packages/order-utils/src/store/balance_and_proxy_allowance_lazy_store.ts +++ b/packages/order-utils/src/store/balance_and_proxy_allowance_lazy_store.ts @@ -79,7 +79,7 @@ export class BalanceAndProxyAllowanceLazyStore implements AbstractBalanceAndProx public deleteAllERC721ProxyAllowance(tokenAddress: string, userAddress: string): void { for (const assetData in this._proxyAllowance) { if (this._proxyAllowance.hasOwnProperty(assetData)) { - const decodedAssetData = assetProxyUtils.decodeAssetData(assetData); + const decodedAssetData = assetProxyUtils.decodeAssetDataOrThrow(assetData); if ( decodedAssetData.assetProxyId === AssetProxyId.ERC721 && decodedAssetData.tokenAddress === tokenAddress && diff --git a/packages/order-watcher/src/fetchers/asset_balance_and_proxy_allowance_fetcher.ts b/packages/order-watcher/src/fetchers/asset_balance_and_proxy_allowance_fetcher.ts index b1c013928..bfa33c8b9 100644 --- a/packages/order-watcher/src/fetchers/asset_balance_and_proxy_allowance_fetcher.ts +++ b/packages/order-watcher/src/fetchers/asset_balance_and_proxy_allowance_fetcher.ts @@ -14,7 +14,7 @@ export class AssetBalanceAndProxyAllowanceFetcher implements AbstractBalanceAndP this._stateLayer = stateLayer; } public async getBalanceAsync(assetData: string, userAddress: string): Promise<BigNumber> { - const decodedAssetData = assetProxyUtils.decodeAssetData(assetData); + const decodedAssetData = assetProxyUtils.decodeAssetDataOrThrow(assetData); if (decodedAssetData.assetProxyId === AssetProxyId.ERC20) { const decodedERC20AssetData = decodedAssetData as ERC20AssetData; const balance = await this._erc20Token.getBalanceAsync(decodedERC20AssetData.tokenAddress, userAddress, { @@ -35,7 +35,7 @@ export class AssetBalanceAndProxyAllowanceFetcher implements AbstractBalanceAndP } } public async getProxyAllowanceAsync(assetData: string, userAddress: string): Promise<BigNumber> { - const decodedAssetData = assetProxyUtils.decodeAssetData(assetData); + const decodedAssetData = assetProxyUtils.decodeAssetDataOrThrow(assetData); if (decodedAssetData.assetProxyId === AssetProxyId.ERC20) { const decodedERC20AssetData = decodedAssetData as ERC20AssetData; const proxyAllowance = await this._erc20Token.getProxyAllowanceAsync( diff --git a/packages/order-watcher/src/order_watcher/dependent_order_hashes_tracker.ts b/packages/order-watcher/src/order_watcher/dependent_order_hashes_tracker.ts index ae7d5078c..d639242ac 100644 --- a/packages/order-watcher/src/order_watcher/dependent_order_hashes_tracker.ts +++ b/packages/order-watcher/src/order_watcher/dependent_order_hashes_tracker.ts @@ -54,7 +54,7 @@ export class DependentOrderHashesTracker { return dependentOrderHashes; } public getDependentOrderHashesByAssetDataByMaker(makerAddress: string, assetData: string): string[] { - const decodedAssetData = assetProxyUtils.decodeAssetData(assetData); + const decodedAssetData = assetProxyUtils.decodeAssetDataOrThrow(assetData); const dependentOrderHashes = decodedAssetData.assetProxyId === AssetProxyId.ERC20 ? this._getDependentOrderHashesByERC20AssetData(makerAddress, assetData) @@ -62,7 +62,7 @@ export class DependentOrderHashesTracker { return dependentOrderHashes; } public addToDependentOrderHashes(signedOrder: SignedOrder): void { - const decodedMakerAssetData = assetProxyUtils.decodeAssetData(signedOrder.makerAssetData); + const decodedMakerAssetData = assetProxyUtils.decodeAssetDataOrThrow(signedOrder.makerAssetData); if (decodedMakerAssetData.assetProxyId === AssetProxyId.ERC20) { this._addToERC20DependentOrderHashes(signedOrder, (decodedMakerAssetData as ERC20AssetData).tokenAddress); } else { @@ -76,7 +76,7 @@ export class DependentOrderHashesTracker { this._addToMakerDependentOrderHashes(signedOrder); } public removeFromDependentOrderHashes(signedOrder: SignedOrder): void { - const decodedMakerAssetData = assetProxyUtils.decodeAssetData(signedOrder.makerAssetData); + const decodedMakerAssetData = assetProxyUtils.decodeAssetDataOrThrow(signedOrder.makerAssetData); if (decodedMakerAssetData.assetProxyId === AssetProxyId.ERC20) { this._removeFromERC20DependentOrderhashes( signedOrder, diff --git a/packages/order-watcher/src/order_watcher/order_watcher.ts b/packages/order-watcher/src/order_watcher/order_watcher.ts index af479f32d..c43887b77 100644 --- a/packages/order-watcher/src/order_watcher/order_watcher.ts +++ b/packages/order-watcher/src/order_watcher/order_watcher.ts @@ -28,7 +28,7 @@ import { orderHashUtils, OrderStateUtils, } from '@0xproject/order-utils'; -import { ExchangeContractErrs, OrderState, SignedOrder } from '@0xproject/types'; +import { AssetProxyId, ExchangeContractErrs, OrderState, SignedOrder } from '@0xproject/types'; import { errorUtils, intervalUtils } from '@0xproject/utils'; import { BlockParamLiteral, LogEntryEvent, LogWithDecodedArgs, Provider } from 'ethereum-types'; import * as _ from 'lodash'; @@ -142,16 +142,26 @@ export class OrderWatcher { * signature is verified. * @param signedOrder The order you wish to start watching. */ - public addOrder(signedOrder: SignedOrder): void { + public async addOrderAsync(signedOrder: SignedOrder): Promise<void> { assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema); const orderHash = orderHashUtils.getOrderHashHex(signedOrder); - assert.isValidSignatureAsync(this._provider, orderHash, signedOrder.signature, signedOrder.makerAddress); + await assert.isValidSignatureAsync(this._provider, orderHash, signedOrder.signature, signedOrder.makerAddress); const expirationUnixTimestampMs = signedOrder.expirationTimeSeconds.times(MILLISECONDS_IN_A_SECOND); this._expirationWatcher.addOrder(orderHash, expirationUnixTimestampMs); this._orderByOrderHash[orderHash] = signedOrder; this._dependentOrderHashesTracker.addToDependentOrderHashes(signedOrder); + + const orderAssetDatas = [signedOrder.makerAssetData, signedOrder.takerAssetData]; + _.each(orderAssetDatas, assetData => { + const decodedAssetData = assetProxyUtils.decodeAssetDataOrThrow(assetData); + if (decodedAssetData.assetProxyId === AssetProxyId.ERC20) { + this._collisionResistantAbiDecoder.addERC20Token(decodedAssetData.tokenAddress); + } else if (decodedAssetData.assetProxyId === AssetProxyId.ERC721) { + this._collisionResistantAbiDecoder.addERC721Token(decodedAssetData.tokenAddress); + } + }); } /** * Removes an order from the orderWatcher diff --git a/packages/order-watcher/test/expiration_watcher_test.ts b/packages/order-watcher/test/expiration_watcher_test.ts index 3c92ddb63..f765e8278 100644 --- a/packages/order-watcher/test/expiration_watcher_test.ts +++ b/packages/order-watcher/test/expiration_watcher_test.ts @@ -43,7 +43,6 @@ describe('ExpirationWatcher', () => { let expirationWatcher: ExpirationWatcher; before(async () => { await blockchainLifecycle.startAsync(); - const erc20ProxyAddress = contractWrappers.erc20Proxy.getContractAddress(); userAddresses = await web3Wrapper.getAvailableAddressesAsync(); zrxTokenAddress = tokenUtils.getProtocolTokenAddress(); fillScenarios = new FillScenarios( @@ -51,7 +50,8 @@ describe('ExpirationWatcher', () => { userAddresses, zrxTokenAddress, exchangeContractAddress, - erc20ProxyAddress, + contractWrappers.erc20Proxy.getContractAddress(), + contractWrappers.erc721Proxy.getContractAddress(), ); [coinbase, makerAddress, takerAddress, feeRecipient] = userAddresses; const [makerTokenAddress, takerTokenAddress] = tokenUtils.getDummyERC20TokenAddresses(); diff --git a/packages/order-watcher/test/order_watcher_test.ts b/packages/order-watcher/test/order_watcher_test.ts index 6339505ce..cd2b7bbfe 100644 --- a/packages/order-watcher/test/order_watcher_test.ts +++ b/packages/order-watcher/test/order_watcher_test.ts @@ -57,7 +57,6 @@ describe('OrderWatcher', () => { const fillableAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(5), decimals); before(async () => { await blockchainLifecycle.startAsync(); - const erc20ProxyAddress = contractWrappers.erc20Proxy.getContractAddress(); userAddresses = await web3Wrapper.getAvailableAddressesAsync(); zrxTokenAddress = tokenUtils.getProtocolTokenAddress(); exchangeContractAddress = contractWrappers.exchange.getContractAddress(); @@ -66,7 +65,8 @@ describe('OrderWatcher', () => { userAddresses, zrxTokenAddress, exchangeContractAddress, - erc20ProxyAddress, + contractWrappers.erc20Proxy.getContractAddress(), + contractWrappers.erc721Proxy.getContractAddress(), ); [coinbase, makerAddress, takerAddress, feeRecipient] = userAddresses; [makerTokenAddress, takerTokenAddress] = tokenUtils.getDummyERC20TokenAddresses(); @@ -96,7 +96,7 @@ describe('OrderWatcher', () => { fillableAmount, ); const orderHash = orderHashUtils.getOrderHashHex(signedOrder); - orderWatcher.addOrder(signedOrder); + await orderWatcher.addOrderAsync(signedOrder); expect((orderWatcher as any)._orderByOrderHash).to.include({ [orderHash]: signedOrder, }); @@ -156,7 +156,7 @@ describe('OrderWatcher', () => { fillableAmount, ); const orderHash = orderHashUtils.getOrderHashHex(signedOrder); - orderWatcher.addOrder(signedOrder); + await orderWatcher.addOrderAsync(signedOrder); const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => { expect(orderState.isValid).to.be.false(); const invalidOrderState = orderState as OrderStateInvalid; @@ -180,7 +180,7 @@ describe('OrderWatcher', () => { takerAddress, fillableAmount, ); - orderWatcher.addOrder(signedOrder); + await orderWatcher.addOrderAsync(signedOrder); const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((_orderState: OrderState) => { throw new Error('OrderState callback fired for irrelevant order'); }); @@ -209,7 +209,7 @@ describe('OrderWatcher', () => { fillableAmount, ); const orderHash = orderHashUtils.getOrderHashHex(signedOrder); - orderWatcher.addOrder(signedOrder); + await orderWatcher.addOrderAsync(signedOrder); const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => { expect(orderState.isValid).to.be.false(); const invalidOrderState = orderState as OrderStateInvalid; @@ -237,7 +237,7 @@ describe('OrderWatcher', () => { fillableAmount, ); const orderHash = orderHashUtils.getOrderHashHex(signedOrder); - orderWatcher.addOrder(signedOrder); + await orderWatcher.addOrderAsync(signedOrder); const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => { expect(orderState.isValid).to.be.false(); @@ -263,7 +263,7 @@ describe('OrderWatcher', () => { const makerBalance = await contractWrappers.erc20Token.getBalanceAsync(makerTokenAddress, makerAddress); const fillAmountInBaseUnits = new BigNumber(2); const orderHash = orderHashUtils.getOrderHashHex(signedOrder); - orderWatcher.addOrder(signedOrder); + await orderWatcher.addOrderAsync(signedOrder); const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => { expect(orderState.isValid).to.be.true(); @@ -299,7 +299,7 @@ describe('OrderWatcher', () => { takerAddress, ); const callback = callbackErrorReporter.reportNodeCallbackErrors(done)(); - orderWatcher.addOrder(signedOrder); + await orderWatcher.addOrderAsync(signedOrder); orderWatcher.subscribe(callback); await contractWrappers.erc20Token.setProxyAllowanceAsync( zrxTokenAddress, @@ -323,7 +323,7 @@ describe('OrderWatcher', () => { ); const fillAmountInBaseUnits = Web3Wrapper.toBaseUnitAmount(new BigNumber(2), decimals); const orderHash = orderHashUtils.getOrderHashHex(signedOrder); - orderWatcher.addOrder(signedOrder); + await orderWatcher.addOrderAsync(signedOrder); const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => { expect(orderState.isValid).to.be.true(); const validOrderState = orderState as OrderStateValid; @@ -351,7 +351,7 @@ describe('OrderWatcher', () => { ); const changedMakerApprovalAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(3), decimals); - orderWatcher.addOrder(signedOrder); + await orderWatcher.addOrderAsync(signedOrder); const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => { const validOrderState = orderState as OrderStateValid; @@ -388,7 +388,7 @@ describe('OrderWatcher', () => { const remainingAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(1), decimals); const transferAmount = makerBalance.sub(remainingAmount); - orderWatcher.addOrder(signedOrder); + await orderWatcher.addOrderAsync(signedOrder); const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => { expect(orderState.isValid).to.be.true(); @@ -429,7 +429,7 @@ describe('OrderWatcher', () => { const remainingTokenAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(4), decimals); const transferTokenAmount = makerFee.sub(remainingTokenAmount); - orderWatcher.addOrder(signedOrder); + await orderWatcher.addOrderAsync(signedOrder); const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => { const validOrderState = orderState as OrderStateValid; @@ -467,7 +467,7 @@ describe('OrderWatcher', () => { feeRecipient, ); - orderWatcher.addOrder(signedOrder); + await orderWatcher.addOrderAsync(signedOrder); const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => { const validOrderState = orderState as OrderStateValid; @@ -495,7 +495,7 @@ describe('OrderWatcher', () => { fillableAmount, ); const orderHash = orderHashUtils.getOrderHashHex(signedOrder); - orderWatcher.addOrder(signedOrder); + await orderWatcher.addOrderAsync(signedOrder); const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => { expect(orderState.isValid).to.be.false(); @@ -518,7 +518,7 @@ describe('OrderWatcher', () => { fillableAmount, ); const orderHash = orderHashUtils.getOrderHashHex(signedOrder); - orderWatcher.addOrder(signedOrder); + await orderWatcher.addOrderAsync(signedOrder); const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => { expect(orderState.isValid).to.be.false(); @@ -534,5 +534,101 @@ describe('OrderWatcher', () => { ); })().catch(done); }); + describe('erc721', () => { + let makerErc721AssetData: string; + let makerErc721TokenAddress: string; + const tokenId = new BigNumber(42); + [makerErc721TokenAddress] = tokenUtils.getDummyERC721TokenAddresses(); + makerErc721AssetData = assetProxyUtils.encodeERC721AssetData(makerErc721TokenAddress, tokenId); + const fillableErc721Amount = new BigNumber(1); + it('should emit orderStateInvalid when maker allowance set to 0 for watched order', (done: DoneCallback) => { + (async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerErc721AssetData, + takerAssetData, + makerAddress, + takerAddress, + fillableErc721Amount, + ); + const orderHash = orderHashUtils.getOrderHashHex(signedOrder); + await orderWatcher.addOrderAsync(signedOrder); + const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => { + expect(orderState.isValid).to.be.false(); + const invalidOrderState = orderState as OrderStateInvalid; + expect(invalidOrderState.orderHash).to.be.equal(orderHash); + expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.InsufficientMakerAllowance); + }); + orderWatcher.subscribe(callback); + await contractWrappers.erc721Token.setApprovalAsync( + makerErc721TokenAddress, + constants.NULL_ADDRESS, + tokenId, + ); + })().catch(done); + }); + it('should emit orderStateInvalid when maker allowance for all set to 0 for watched order', (done: DoneCallback) => { + (async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerErc721AssetData, + takerAssetData, + makerAddress, + takerAddress, + fillableErc721Amount, + ); + await contractWrappers.erc721Token.setApprovalAsync( + makerErc721TokenAddress, + constants.NULL_ADDRESS, + tokenId, + ); + let isApproved = true; + await contractWrappers.erc721Token.setProxyApprovalForAllAsync( + makerErc721TokenAddress, + makerAddress, + isApproved, + ); + const orderHash = orderHashUtils.getOrderHashHex(signedOrder); + await orderWatcher.addOrderAsync(signedOrder); + const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => { + expect(orderState.isValid).to.be.false(); + const invalidOrderState = orderState as OrderStateInvalid; + expect(invalidOrderState.orderHash).to.be.equal(orderHash); + expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.InsufficientMakerAllowance); + }); + orderWatcher.subscribe(callback); + isApproved = false; + await contractWrappers.erc721Token.setProxyApprovalForAllAsync( + makerErc721TokenAddress, + makerAddress, + isApproved, + ); + })().catch(done); + }); + it('should emit orderStateInvalid when maker moves NFT backing watched order', (done: DoneCallback) => { + (async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerErc721AssetData, + takerAssetData, + makerAddress, + takerAddress, + fillableErc721Amount, + ); + const orderHash = orderHashUtils.getOrderHashHex(signedOrder); + await orderWatcher.addOrderAsync(signedOrder); + const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => { + expect(orderState.isValid).to.be.false(); + const invalidOrderState = orderState as OrderStateInvalid; + expect(invalidOrderState.orderHash).to.be.equal(orderHash); + expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.InsufficientMakerBalance); + }); + orderWatcher.subscribe(callback); + await contractWrappers.erc721Token.transferFromAsync( + makerErc721TokenAddress, + coinbase, + makerAddress, + tokenId, + ); + })().catch(done); + }); + }); }); }); // tslint:disable:max-file-line-count diff --git a/packages/subproviders/tsconfig.json b/packages/subproviders/tsconfig.json index 72dfee80b..e35816553 100644 --- a/packages/subproviders/tsconfig.json +++ b/packages/subproviders/tsconfig.json @@ -1,8 +1,7 @@ { "extends": "../../tsconfig", "compilerOptions": { - "outDir": "lib", - "downlevelIteration": true + "outDir": "lib" }, "include": ["./src/**/*", "./test/**/*"] } diff --git a/packages/tslint-config/tslint.json b/packages/tslint-config/tslint.json index fbc71e140..1adb444e2 100644 --- a/packages/tslint-config/tslint.json +++ b/packages/tslint-config/tslint.json @@ -48,7 +48,6 @@ "no-parameter-reassignment": true, "no-redundant-jsdoc": true, "no-return-await": true, - "no-unused-variable": [true, "check-parameters"], "no-string-throw": true, "no-submodule-imports": false, "no-unnecessary-type-assertion": true, |