diff options
author | Leonid Logvinov <logvinov.leon@gmail.com> | 2018-07-03 20:32:38 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-07-03 20:32:38 +0800 |
commit | 0e690608d39c010740d9fdb9d8e6c12d7e6c4e80 (patch) | |
tree | e03b85876abc0de57755575287b89a79e7824296 /packages/contract-wrappers/src/contract_wrappers | |
parent | 518a2da0275632a5dd61f99a105163ff5a074927 (diff) | |
parent | 09e921a562bea4fa80ec19ce7a9061bbcffbd580 (diff) | |
download | dexon-sol-tools-0e690608d39c010740d9fdb9d8e6c12d7e6c4e80.tar dexon-sol-tools-0e690608d39c010740d9fdb9d8e6c12d7e6c4e80.tar.gz dexon-sol-tools-0e690608d39c010740d9fdb9d8e6c12d7e6c4e80.tar.bz2 dexon-sol-tools-0e690608d39c010740d9fdb9d8e6c12d7e6c4e80.tar.lz dexon-sol-tools-0e690608d39c010740d9fdb9d8e6c12d7e6c4e80.tar.xz dexon-sol-tools-0e690608d39c010740d9fdb9d8e6c12d7e6c4e80.tar.zst dexon-sol-tools-0e690608d39c010740d9fdb9d8e6c12d7e6c4e80.zip |
Merge pull request #782 from 0xProject/feature/contract-wrappers-v2
@0xproject/contract-wrappers V2 refactor. Part 1
Diffstat (limited to 'packages/contract-wrappers/src/contract_wrappers')
-rw-r--r-- | packages/contract-wrappers/src/contract_wrappers/contract_wrapper.ts | 34 | ||||
-rw-r--r-- | packages/contract-wrappers/src/contract_wrappers/erc20_proxy_wrapper.ts (renamed from packages/contract-wrappers/src/contract_wrappers/token_transfer_proxy_wrapper.ts) | 50 | ||||
-rw-r--r-- | packages/contract-wrappers/src/contract_wrappers/erc20_token_wrapper.ts (renamed from packages/contract-wrappers/src/contract_wrappers/token_wrapper.ts) | 142 | ||||
-rw-r--r-- | packages/contract-wrappers/src/contract_wrappers/erc721_proxy_wrapper.ts | 75 | ||||
-rw-r--r-- | packages/contract-wrappers/src/contract_wrappers/erc721_token_wrapper.ts | 479 | ||||
-rw-r--r-- | packages/contract-wrappers/src/contract_wrappers/ether_token_wrapper.ts | 68 | ||||
-rw-r--r-- | packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts | 988 | ||||
-rw-r--r-- | packages/contract-wrappers/src/contract_wrappers/token_registry_wrapper.ts | 133 |
8 files changed, 703 insertions, 1266 deletions
diff --git a/packages/contract-wrappers/src/contract_wrappers/contract_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/contract_wrapper.ts index 04f69bc3d..a88745485 100644 --- a/packages/contract-wrappers/src/contract_wrappers/contract_wrapper.ts +++ b/packages/contract-wrappers/src/contract_wrappers/contract_wrapper.ts @@ -1,14 +1,7 @@ -import { - Artifact, - BlockParamLiteral, - ContractAbi, - FilterObject, - LogEntry, - LogWithDecodedArgs, - RawLog, -} from '@0xproject/types'; -import { intervalUtils } from '@0xproject/utils'; +import { ContractArtifact } from '@0xproject/sol-compiler'; +import { AbiDecoder, intervalUtils } from '@0xproject/utils'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; +import { BlockParamLiteral, ContractAbi, FilterObject, LogEntry, LogWithDecodedArgs, RawLog } from 'ethereum-types'; import { Block, BlockAndLogStreamer } from 'ethereumjs-blockstream'; import * as _ from 'lodash'; @@ -29,9 +22,10 @@ const CONTRACT_NAME_TO_NOT_FOUND_ERROR: { } = { ZRX: ContractWrappersError.ZRXContractDoesNotExist, EtherToken: ContractWrappersError.EtherTokenContractDoesNotExist, - Token: ContractWrappersError.TokenContractDoesNotExist, - TokenRegistry: ContractWrappersError.TokenRegistryContractDoesNotExist, - TokenTransferProxy: ContractWrappersError.TokenTransferProxyContractDoesNotExist, + ERC20Token: ContractWrappersError.ERC20TokenContractDoesNotExist, + ERC20Proxy: ContractWrappersError.ERC20ProxyContractDoesNotExist, + ERC721Token: ContractWrappersError.ERC721TokenContractDoesNotExist, + ERC721Proxy: ContractWrappersError.ERC721ProxyContractDoesNotExist, Exchange: ContractWrappersError.ExchangeContractDoesNotExist, }; @@ -107,14 +101,12 @@ export abstract class ContractWrapper { protected _tryToDecodeLogOrNoop<ArgsType extends ContractEventArgs>( log: LogEntry, ): LogWithDecodedArgs<ArgsType> | RawLog { - if (_.isUndefined(this._web3Wrapper.abiDecoder)) { - throw new Error(InternalContractWrappersError.NoAbiDecoder); - } - const logWithDecodedArgs = this._web3Wrapper.abiDecoder.tryToDecodeLogOrNoop(log); + const abiDecoder = new AbiDecoder([this.abi]); + const logWithDecodedArgs = abiDecoder.tryToDecodeLogOrNoop(log); return logWithDecodedArgs; } protected async _getContractAbiAndAddressFromArtifactsAsync( - artifact: Artifact, + artifact: ContractArtifact, addressIfExists?: string, ): Promise<[ContractAbi, string]> { let contractAddress: string; @@ -128,12 +120,12 @@ export abstract class ContractWrapper { } const doesContractExist = await this._web3Wrapper.doesContractExistAtAddressAsync(contractAddress); if (!doesContractExist) { - throw new Error(CONTRACT_NAME_TO_NOT_FOUND_ERROR[artifact.contract_name]); + throw new Error(CONTRACT_NAME_TO_NOT_FOUND_ERROR[artifact.contractName]); } - const abiAndAddress: [ContractAbi, string] = [artifact.abi, contractAddress]; + const abiAndAddress: [ContractAbi, string] = [artifact.compilerOutput.abi, contractAddress]; return abiAndAddress; } - protected _getContractAddress(artifact: Artifact, addressIfExists?: string): string { + protected _getContractAddress(artifact: ContractArtifact, addressIfExists?: string): string { if (_.isUndefined(addressIfExists)) { const contractAddress = artifact.networks[this._networkId].address; if (_.isUndefined(contractAddress)) { diff --git a/packages/contract-wrappers/src/contract_wrappers/token_transfer_proxy_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/erc20_proxy_wrapper.ts index 5194931d7..839248754 100644 --- a/packages/contract-wrappers/src/contract_wrappers/token_transfer_proxy_wrapper.ts +++ b/packages/contract-wrappers/src/contract_wrappers/erc20_proxy_wrapper.ts @@ -1,75 +1,75 @@ import { Web3Wrapper } from '@0xproject/web3-wrapper'; -import { ContractAbi } from '@0xproject/types'; +import { ContractAbi } from 'ethereum-types'; import * as _ from 'lodash'; import { artifacts } from '../artifacts'; import { assert } from '../utils/assert'; import { ContractWrapper } from './contract_wrapper'; -import { TokenTransferProxyContract } from './generated/token_transfer_proxy'; +import { ERC20ProxyContract } from './generated/erc20_proxy'; /** - * This class includes the functionality related to interacting with the TokenTransferProxy contract. + * This class includes the functionality related to interacting with the ERC20Proxy contract. */ -export class TokenTransferProxyWrapper extends ContractWrapper { - public abi: ContractAbi = artifacts.TokenTransferProxy.abi; - private _tokenTransferProxyContractIfExists?: TokenTransferProxyContract; +export class ERC20ProxyWrapper extends ContractWrapper { + public abi: ContractAbi = artifacts.ERC20Proxy.compilerOutput.abi; + private _erc20ProxyContractIfExists?: ERC20ProxyContract; private _contractAddressIfExists?: string; constructor(web3Wrapper: Web3Wrapper, networkId: number, contractAddressIfExists?: string) { super(web3Wrapper, networkId); this._contractAddressIfExists = contractAddressIfExists; } /** - * Check if the Exchange contract address is authorized by the TokenTransferProxy contract. + * Check if the Exchange contract address is authorized by the ERC20Proxy contract. * @param exchangeContractAddress The hex encoded address of the Exchange contract to call. * @return Whether the exchangeContractAddress is authorized. */ public async isAuthorizedAsync(exchangeContractAddress: string): Promise<boolean> { assert.isETHAddressHex('exchangeContractAddress', exchangeContractAddress); const normalizedExchangeContractAddress = exchangeContractAddress.toLowerCase(); - const tokenTransferProxyContractInstance = await this._getTokenTransferProxyContractAsync(); - const isAuthorized = await tokenTransferProxyContractInstance.authorized.callAsync( - normalizedExchangeContractAddress, - ); + const ERC20ProxyContractInstance = await this._getERC20ProxyContractAsync(); + const isAuthorized = await ERC20ProxyContractInstance.authorized.callAsync(normalizedExchangeContractAddress); return isAuthorized; } /** - * Get the list of all Exchange contract addresses authorized by the TokenTransferProxy contract. + * Get the list of all Exchange contract addresses authorized by the ERC20Proxy contract. * @return The list of authorized addresses. */ public async getAuthorizedAddressesAsync(): Promise<string[]> { - const tokenTransferProxyContractInstance = await this._getTokenTransferProxyContractAsync(); - const authorizedAddresses = await tokenTransferProxyContractInstance.getAuthorizedAddresses.callAsync(); + const ERC20ProxyContractInstance = await this._getERC20ProxyContractAsync(); + const authorizedAddresses = await ERC20ProxyContractInstance.getAuthorizedAddresses.callAsync(); return authorizedAddresses; } /** - * Retrieves the Ethereum address of the TokenTransferProxy contract deployed on the network + * Retrieves the Ethereum address of the ERC20Proxy contract deployed on the network * that the user-passed web3 provider is connected to. - * @returns The Ethereum address of the TokenTransferProxy contract being used. + * @returns The Ethereum address of the ERC20Proxy contract being used. */ public getContractAddress(): string { - const contractAddress = this._getContractAddress(artifacts.TokenTransferProxy, this._contractAddressIfExists); + const contractAddress = this._getContractAddress(artifacts.ERC20Proxy, this._contractAddressIfExists); return contractAddress; } + // HACK: We don't want this method to be visible to the other units within that package but not to the end user. + // TS doesn't give that possibility and therefore we make it private and access it over an any cast. Because of that tslint sees it as unused. // tslint:disable-next-line:no-unused-variable private _invalidateContractInstance(): void { - delete this._tokenTransferProxyContractIfExists; + delete this._erc20ProxyContractIfExists; } - private async _getTokenTransferProxyContractAsync(): Promise<TokenTransferProxyContract> { - if (!_.isUndefined(this._tokenTransferProxyContractIfExists)) { - return this._tokenTransferProxyContractIfExists; + private async _getERC20ProxyContractAsync(): Promise<ERC20ProxyContract> { + if (!_.isUndefined(this._erc20ProxyContractIfExists)) { + return this._erc20ProxyContractIfExists; } const [abi, address] = await this._getContractAbiAndAddressFromArtifactsAsync( - artifacts.TokenTransferProxy, + artifacts.ERC20Proxy, this._contractAddressIfExists, ); - const contractInstance = new TokenTransferProxyContract( + const contractInstance = new ERC20ProxyContract( abi, address, this._web3Wrapper.getProvider(), this._web3Wrapper.getContractDefaults(), ); - this._tokenTransferProxyContractIfExists = contractInstance; - return this._tokenTransferProxyContractIfExists; + this._erc20ProxyContractIfExists = contractInstance; + return this._erc20ProxyContractIfExists; } } diff --git a/packages/contract-wrappers/src/contract_wrappers/token_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/erc20_token_wrapper.ts index d9364715f..f393e4ed1 100644 --- a/packages/contract-wrappers/src/contract_wrappers/token_wrapper.ts +++ b/packages/contract-wrappers/src/contract_wrappers/erc20_token_wrapper.ts @@ -1,10 +1,12 @@ import { schemas } from '@0xproject/json-schemas'; -import { ContractAbi, LogWithDecodedArgs } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; +import { ContractAbi, LogWithDecodedArgs } from 'ethereum-types'; import * as _ from 'lodash'; import { artifacts } from '../artifacts'; +import { methodOptsSchema } from '../schemas/method_opts_schema'; +import { txOptsSchema } from '../schemas/tx_opts_schema'; import { BlockRange, ContractWrappersError, @@ -17,23 +19,25 @@ import { assert } from '../utils/assert'; import { constants } from '../utils/constants'; import { ContractWrapper } from './contract_wrapper'; -import { TokenContract, TokenContractEventArgs, TokenEvents } from './generated/token'; -import { TokenTransferProxyWrapper } from './token_transfer_proxy_wrapper'; +import { ERC20ProxyWrapper } from './erc20_proxy_wrapper'; +import { ERC20TokenContract, ERC20TokenEventArgs, ERC20TokenEvents } from './generated/erc20_token'; + +const removeUndefinedProperties = _.pickBy; /** * This class includes all the functionality related to interacting with ERC20 token contracts. * All ERC20 method calls are supported, along with some convenience methods for getting/setting allowances - * to the 0x Proxy smart contract. + * to the 0x ERC20 Proxy smart contract. */ -export class TokenWrapper extends ContractWrapper { - public abi: ContractAbi = artifacts.Token.abi; +export class ERC20TokenWrapper extends ContractWrapper { + public abi: ContractAbi = artifacts.ERC20Token.compilerOutput.abi; public UNLIMITED_ALLOWANCE_IN_BASE_UNITS = constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS; - private _tokenContractsByAddress: { [address: string]: TokenContract }; - private _tokenTransferProxyWrapper: TokenTransferProxyWrapper; - constructor(web3Wrapper: Web3Wrapper, networkId: number, tokenTransferProxyWrapper: TokenTransferProxyWrapper) { + private _tokenContractsByAddress: { [address: string]: ERC20TokenContract }; + private _erc20ProxyWrapper: ERC20ProxyWrapper; + constructor(web3Wrapper: Web3Wrapper, networkId: number, erc20ProxyWrapper: ERC20ProxyWrapper) { super(web3Wrapper, networkId); this._tokenContractsByAddress = {}; - this._tokenTransferProxyWrapper = tokenTransferProxyWrapper; + this._erc20ProxyWrapper = erc20ProxyWrapper; } /** * Retrieves an owner's ERC20 token balance. @@ -49,6 +53,9 @@ export class TokenWrapper extends ContractWrapper { ): Promise<BigNumber> { assert.isETHAddressHex('ownerAddress', ownerAddress); assert.isETHAddressHex('tokenAddress', tokenAddress); + if (!_.isUndefined(methodOpts)) { + assert.doesConformToSchema('methodOpts', methodOpts, methodOptsSchema); + } const normalizedTokenAddress = tokenAddress.toLowerCase(); const normalizedOwnerAddress = ownerAddress.toLowerCase(); @@ -81,17 +88,24 @@ export class TokenWrapper extends ContractWrapper { assert.isETHAddressHex('spenderAddress', spenderAddress); assert.isETHAddressHex('tokenAddress', tokenAddress); await assert.isSenderAddressAsync('ownerAddress', ownerAddress, this._web3Wrapper); + if (!_.isUndefined(txOpts)) { + assert.doesConformToSchema('txOpts', txOpts, txOptsSchema); + } const normalizedTokenAddress = tokenAddress.toLowerCase(); const normalizedSpenderAddress = spenderAddress.toLowerCase(); const normalizedOwnerAddress = ownerAddress.toLowerCase(); assert.isValidBaseUnitAmount('amountInBaseUnits', amountInBaseUnits); const tokenContract = await this._getTokenContractAsync(normalizedTokenAddress); - const txHash = await tokenContract.approve.sendTransactionAsync(normalizedSpenderAddress, amountInBaseUnits, { - from: normalizedOwnerAddress, - gas: txOpts.gasLimit, - gasPrice: txOpts.gasPrice, - }); + const txHash = await tokenContract.approve.sendTransactionAsync( + normalizedSpenderAddress, + amountInBaseUnits, + removeUndefinedProperties({ + from: normalizedOwnerAddress, + gas: txOpts.gasLimit, + gasPrice: txOpts.gasPrice, + }), + ); return txHash; } /** @@ -112,16 +126,10 @@ export class TokenWrapper extends ContractWrapper { spenderAddress: string, txOpts: TransactionOpts = {}, ): Promise<string> { - assert.isETHAddressHex('ownerAddress', ownerAddress); - assert.isETHAddressHex('tokenAddress', tokenAddress); - assert.isETHAddressHex('spenderAddress', spenderAddress); - const normalizedTokenAddress = tokenAddress.toLowerCase(); - const normalizedOwnerAddress = ownerAddress.toLowerCase(); - const normalizedSpenderAddress = spenderAddress.toLowerCase(); const txHash = await this.setAllowanceAsync( - normalizedTokenAddress, - normalizedOwnerAddress, - normalizedSpenderAddress, + tokenAddress, + ownerAddress, + spenderAddress, this.UNLIMITED_ALLOWANCE_IN_BASE_UNITS, txOpts, ); @@ -144,6 +152,9 @@ export class TokenWrapper extends ContractWrapper { assert.isETHAddressHex('ownerAddress', ownerAddress); assert.isETHAddressHex('tokenAddress', tokenAddress); assert.isETHAddressHex('spenderAddress', spenderAddress); + if (!_.isUndefined(methodOpts)) { + assert.doesConformToSchema('methodOpts', methodOpts, methodOptsSchema); + } const normalizedTokenAddress = tokenAddress.toLowerCase(); const normalizedOwnerAddress = ownerAddress.toLowerCase(); const normalizedSpenderAddress = spenderAddress.toLowerCase(); @@ -172,18 +183,8 @@ export class TokenWrapper extends ContractWrapper { ownerAddress: string, methodOpts?: MethodOpts, ): Promise<BigNumber> { - assert.isETHAddressHex('ownerAddress', ownerAddress); - assert.isETHAddressHex('tokenAddress', tokenAddress); - const normalizedTokenAddress = tokenAddress.toLowerCase(); - const normalizedOwnerAddress = ownerAddress.toLowerCase(); - - const proxyAddress = this._tokenTransferProxyWrapper.getContractAddress(); - const allowanceInBaseUnits = await this.getAllowanceAsync( - normalizedTokenAddress, - normalizedOwnerAddress, - proxyAddress, - methodOpts, - ); + const proxyAddress = this._erc20ProxyWrapper.getContractAddress(); + const allowanceInBaseUnits = await this.getAllowanceAsync(tokenAddress, ownerAddress, proxyAddress, methodOpts); return allowanceInBaseUnits; } /** @@ -202,16 +203,10 @@ export class TokenWrapper extends ContractWrapper { amountInBaseUnits: BigNumber, txOpts: TransactionOpts = {}, ): Promise<string> { - assert.isETHAddressHex('ownerAddress', ownerAddress); - assert.isETHAddressHex('tokenAddress', tokenAddress); - const normalizedTokenAddress = tokenAddress.toLowerCase(); - const normalizedOwnerAddress = ownerAddress.toLowerCase(); - assert.isValidBaseUnitAmount('amountInBaseUnits', amountInBaseUnits); - - const proxyAddress = this._tokenTransferProxyWrapper.getContractAddress(); + const proxyAddress = this._erc20ProxyWrapper.getContractAddress(); const txHash = await this.setAllowanceAsync( - normalizedTokenAddress, - normalizedOwnerAddress, + tokenAddress, + ownerAddress, proxyAddress, amountInBaseUnits, txOpts, @@ -234,13 +229,9 @@ export class TokenWrapper extends ContractWrapper { ownerAddress: string, txOpts: TransactionOpts = {}, ): Promise<string> { - assert.isETHAddressHex('ownerAddress', ownerAddress); - assert.isETHAddressHex('tokenAddress', tokenAddress); - const normalizedTokenAddress = tokenAddress.toLowerCase(); - const normalizedOwnerAddress = ownerAddress.toLowerCase(); const txHash = await this.setProxyAllowanceAsync( - normalizedTokenAddress, - normalizedOwnerAddress, + tokenAddress, + ownerAddress, this.UNLIMITED_ALLOWANCE_IN_BASE_UNITS, txOpts, ); @@ -265,6 +256,9 @@ export class TokenWrapper extends ContractWrapper { assert.isETHAddressHex('tokenAddress', tokenAddress); assert.isETHAddressHex('toAddress', toAddress); await assert.isSenderAddressAsync('fromAddress', fromAddress, this._web3Wrapper); + if (!_.isUndefined(txOpts)) { + assert.doesConformToSchema('txOpts', txOpts, txOptsSchema); + } const normalizedTokenAddress = tokenAddress.toLowerCase(); const normalizedFromAddress = fromAddress.toLowerCase(); const normalizedToAddress = toAddress.toLowerCase(); @@ -277,11 +271,15 @@ export class TokenWrapper extends ContractWrapper { throw new Error(ContractWrappersError.InsufficientBalanceForTransfer); } - const txHash = await tokenContract.transfer.sendTransactionAsync(normalizedToAddress, amountInBaseUnits, { - from: normalizedFromAddress, - gas: txOpts.gasLimit, - gasPrice: txOpts.gasPrice, - }); + const txHash = await tokenContract.transfer.sendTransactionAsync( + normalizedToAddress, + amountInBaseUnits, + removeUndefinedProperties({ + from: normalizedFromAddress, + gas: txOpts.gasLimit, + gasPrice: txOpts.gasPrice, + }), + ); return txHash; } /** @@ -310,6 +308,9 @@ export class TokenWrapper extends ContractWrapper { assert.isETHAddressHex('fromAddress', fromAddress); assert.isETHAddressHex('tokenAddress', tokenAddress); await assert.isSenderAddressAsync('senderAddress', senderAddress, this._web3Wrapper); + if (!_.isUndefined(txOpts)) { + assert.doesConformToSchema('txOpts', txOpts, txOptsSchema); + } const normalizedToAddress = toAddress.toLowerCase(); const normalizedFromAddress = fromAddress.toLowerCase(); const normalizedTokenAddress = tokenAddress.toLowerCase(); @@ -336,11 +337,11 @@ export class TokenWrapper extends ContractWrapper { normalizedFromAddress, normalizedToAddress, amountInBaseUnits, - { + removeUndefinedProperties({ from: normalizedSenderAddress, gas: txOpts.gasLimit, gasPrice: txOpts.gasPrice, - }, + }), ); return txHash; } @@ -353,22 +354,22 @@ export class TokenWrapper extends ContractWrapper { * @param callback Callback that gets called when a log is added/removed * @return Subscription token used later to unsubscribe */ - public subscribe<ArgsType extends TokenContractEventArgs>( + public subscribe<ArgsType extends ERC20TokenEventArgs>( tokenAddress: string, - eventName: TokenEvents, + eventName: ERC20TokenEvents, indexFilterValues: IndexedFilterValues, callback: EventCallback<ArgsType>, ): string { assert.isETHAddressHex('tokenAddress', tokenAddress); const normalizedTokenAddress = tokenAddress.toLowerCase(); - assert.doesBelongToStringEnum('eventName', eventName, TokenEvents); + assert.doesBelongToStringEnum('eventName', eventName, ERC20TokenEvents); assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema); assert.isFunction('callback', callback); const subscriptionToken = this._subscribe<ArgsType>( normalizedTokenAddress, eventName, indexFilterValues, - artifacts.Token.abi, + artifacts.ERC20Token.compilerOutput.abi, callback, ); return subscriptionToken; @@ -378,6 +379,7 @@ export class TokenWrapper extends ContractWrapper { * @param subscriptionToken Subscription token returned by `subscribe()` */ public unsubscribe(subscriptionToken: string): void { + assert.isValidSubscriptionToken('subscriptionToken', subscriptionToken); this._unsubscribe(subscriptionToken); } /** @@ -395,15 +397,15 @@ export class TokenWrapper extends ContractWrapper { * the value is the value you are interested in. E.g `{_from: aUserAddressHex}` * @return Array of logs that match the parameters */ - public async getLogsAsync<ArgsType extends TokenContractEventArgs>( + public async getLogsAsync<ArgsType extends ERC20TokenEventArgs>( tokenAddress: string, - eventName: TokenEvents, + eventName: ERC20TokenEvents, blockRange: BlockRange, indexFilterValues: IndexedFilterValues, ): Promise<Array<LogWithDecodedArgs<ArgsType>>> { assert.isETHAddressHex('tokenAddress', tokenAddress); const normalizedTokenAddress = tokenAddress.toLowerCase(); - assert.doesBelongToStringEnum('eventName', eventName, TokenEvents); + assert.doesBelongToStringEnum('eventName', eventName, ERC20TokenEvents); assert.doesConformToSchema('blockRange', blockRange, schemas.blockRangeSchema); assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema); const logs = await this._getLogsAsync<ArgsType>( @@ -411,26 +413,28 @@ export class TokenWrapper extends ContractWrapper { eventName, blockRange, indexFilterValues, - artifacts.Token.abi, + artifacts.ERC20Token.compilerOutput.abi, ); return logs; } + // HACK: We don't want this method to be visible to the other units within that package but not to the end user. + // TS doesn't give that possibility and therefore we make it private and access it over an any cast. Because of that tslint sees it as unused. // tslint:disable-next-line:no-unused-variable private _invalidateContractInstances(): void { this.unsubscribeAll(); this._tokenContractsByAddress = {}; } - private async _getTokenContractAsync(tokenAddress: string): Promise<TokenContract> { + private async _getTokenContractAsync(tokenAddress: string): Promise<ERC20TokenContract> { const normalizedTokenAddress = tokenAddress.toLowerCase(); let tokenContract = this._tokenContractsByAddress[normalizedTokenAddress]; if (!_.isUndefined(tokenContract)) { return tokenContract; } const [abi, address] = await this._getContractAbiAndAddressFromArtifactsAsync( - artifacts.Token, + artifacts.ERC20Token, normalizedTokenAddress, ); - const contractInstance = new TokenContract( + const contractInstance = new ERC20TokenContract( abi, address, this._web3Wrapper.getProvider(), diff --git a/packages/contract-wrappers/src/contract_wrappers/erc721_proxy_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/erc721_proxy_wrapper.ts new file mode 100644 index 000000000..fba995395 --- /dev/null +++ b/packages/contract-wrappers/src/contract_wrappers/erc721_proxy_wrapper.ts @@ -0,0 +1,75 @@ +import { Web3Wrapper } from '@0xproject/web3-wrapper'; +import { ContractAbi } from 'ethereum-types'; +import * as _ from 'lodash'; + +import { artifacts } from '../artifacts'; +import { assert } from '../utils/assert'; + +import { ContractWrapper } from './contract_wrapper'; +import { ERC721ProxyContract } from './generated/erc721_proxy'; + +/** + * This class includes the functionality related to interacting with the ERC721Proxy contract. + */ +export class ERC721ProxyWrapper extends ContractWrapper { + public abi: ContractAbi = artifacts.ERC20Proxy.compilerOutput.abi; + private _erc721ProxyContractIfExists?: ERC721ProxyContract; + private _contractAddressIfExists?: string; + constructor(web3Wrapper: Web3Wrapper, networkId: number, contractAddressIfExists?: string) { + super(web3Wrapper, networkId); + this._contractAddressIfExists = contractAddressIfExists; + } + /** + * Check if the Exchange contract address is authorized by the ERC721Proxy contract. + * @param exchangeContractAddress The hex encoded address of the Exchange contract to call. + * @return Whether the exchangeContractAddress is authorized. + */ + public async isAuthorizedAsync(exchangeContractAddress: string): Promise<boolean> { + assert.isETHAddressHex('exchangeContractAddress', exchangeContractAddress); + const normalizedExchangeContractAddress = exchangeContractAddress.toLowerCase(); + const ERC721ProxyContractInstance = await this._getERC721ProxyContractAsync(); + const isAuthorized = await ERC721ProxyContractInstance.authorized.callAsync(normalizedExchangeContractAddress); + return isAuthorized; + } + /** + * Get the list of all Exchange contract addresses authorized by the ERC721Proxy contract. + * @return The list of authorized addresses. + */ + public async getAuthorizedAddressesAsync(): Promise<string[]> { + const ERC721ProxyContractInstance = await this._getERC721ProxyContractAsync(); + const authorizedAddresses = await ERC721ProxyContractInstance.getAuthorizedAddresses.callAsync(); + return authorizedAddresses; + } + /** + * Retrieves the Ethereum address of the ERC721Proxy contract deployed on the network + * that the user-passed web3 provider is connected to. + * @returns The Ethereum address of the ERC721Proxy contract being used. + */ + public getContractAddress(): string { + const contractAddress = this._getContractAddress(artifacts.ERC721Proxy, this._contractAddressIfExists); + return contractAddress; + } + // HACK: We don't want this method to be visible to the other units within that package but not to the end user. + // TS doesn't give that possibility and therefore we make it private and access it over an any cast. Because of that tslint sees it as unused. + // tslint:disable-next-line:no-unused-variable + private _invalidateContractInstance(): void { + delete this._erc721ProxyContractIfExists; + } + private async _getERC721ProxyContractAsync(): Promise<ERC721ProxyContract> { + if (!_.isUndefined(this._erc721ProxyContractIfExists)) { + return this._erc721ProxyContractIfExists; + } + const [abi, address] = await this._getContractAbiAndAddressFromArtifactsAsync( + artifacts.ERC721Proxy, + this._contractAddressIfExists, + ); + const contractInstance = new ERC721ProxyContract( + abi, + address, + this._web3Wrapper.getProvider(), + this._web3Wrapper.getContractDefaults(), + ); + this._erc721ProxyContractIfExists = contractInstance; + return this._erc721ProxyContractIfExists; + } +} diff --git a/packages/contract-wrappers/src/contract_wrappers/erc721_token_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/erc721_token_wrapper.ts new file mode 100644 index 000000000..415415fd7 --- /dev/null +++ b/packages/contract-wrappers/src/contract_wrappers/erc721_token_wrapper.ts @@ -0,0 +1,479 @@ +import { schemas } from '@0xproject/json-schemas'; +import { BigNumber } from '@0xproject/utils'; +import { Web3Wrapper } from '@0xproject/web3-wrapper'; +import { ContractAbi, LogWithDecodedArgs } from 'ethereum-types'; +import * as _ from 'lodash'; + +import { constants } from '../../test/utils/constants'; +import { artifacts } from '../artifacts'; +import { methodOptsSchema } from '../schemas/method_opts_schema'; +import { txOptsSchema } from '../schemas/tx_opts_schema'; +import { + BlockRange, + ContractWrappersError, + EventCallback, + IndexedFilterValues, + MethodOpts, + TransactionOpts, +} from '../types'; +import { assert } from '../utils/assert'; + +import { ContractWrapper } from './contract_wrapper'; +import { ERC721ProxyWrapper } from './erc721_proxy_wrapper'; +import { ERC721TokenContract, ERC721TokenEventArgs, ERC721TokenEvents } from './generated/erc721_token'; + +const removeUndefinedProperties = _.pickBy; + +/** + * This class includes all the functionality related to interacting with ERC721 token contracts. + * All ERC721 method calls are supported, along with some convenience methods for getting/setting allowances + * to the 0x ERC721 Proxy smart contract. + */ +export class ERC721TokenWrapper extends ContractWrapper { + public abi: ContractAbi = artifacts.ERC721Token.compilerOutput.abi; + private _tokenContractsByAddress: { [address: string]: ERC721TokenContract }; + private _erc721ProxyWrapper: ERC721ProxyWrapper; + constructor(web3Wrapper: Web3Wrapper, networkId: number, erc721ProxyWrapper: ERC721ProxyWrapper) { + super(web3Wrapper, networkId); + this._tokenContractsByAddress = {}; + this._erc721ProxyWrapper = erc721ProxyWrapper; + } + /** + * Count all NFTs assigned to an owner + * NFTs assigned to the zero address are considered invalid, and this function throws for queries about the zero address. + * @param tokenAddress The hex encoded contract Ethereum address where the ERC721 token is deployed. + * @param ownerAddress The hex encoded user Ethereum address whose balance you would like to check. + * @param methodOpts Optional arguments this method accepts. + * @return The number of NFTs owned by `ownerAddress`, possibly zero + */ + public async getTokenCountAsync( + tokenAddress: string, + ownerAddress: string, + methodOpts?: MethodOpts, + ): Promise<BigNumber> { + assert.isETHAddressHex('ownerAddress', ownerAddress); + assert.isETHAddressHex('tokenAddress', tokenAddress); + if (!_.isUndefined(methodOpts)) { + assert.doesConformToSchema('methodOpts', methodOpts, methodOptsSchema); + } + const normalizedTokenAddress = tokenAddress.toLowerCase(); + const normalizedOwnerAddress = ownerAddress.toLowerCase(); + + const tokenContract = await this._getTokenContractAsync(normalizedTokenAddress); + const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock; + const txData = {}; + let balance = await tokenContract.balanceOf.callAsync(normalizedOwnerAddress, txData, defaultBlock); + // Wrap BigNumbers returned from web3 with our own (later) version of BigNumber + balance = new BigNumber(balance); + return balance; + } + /** + * Find the owner of an NFT + * NFTs assigned to zero address are considered invalid, and queries about them do throw. + * @param tokenAddress The hex encoded contract Ethereum address where the ERC721 token is deployed. + * @param tokenId The identifier for an NFT + * @param methodOpts Optional arguments this method accepts. + * @return The address of the owner of the NFT + */ + public async getOwnerOfAsync(tokenAddress: string, tokenId: BigNumber, methodOpts?: MethodOpts): Promise<string> { + assert.isETHAddressHex('tokenAddress', tokenAddress); + assert.isBigNumber('tokenId', tokenId); + if (!_.isUndefined(methodOpts)) { + assert.doesConformToSchema('methodOpts', methodOpts, methodOptsSchema); + } + const normalizedTokenAddress = tokenAddress.toLowerCase(); + + const tokenContract = await this._getTokenContractAsync(normalizedTokenAddress); + const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock; + const txData = {}; + try { + const tokenOwner = await tokenContract.ownerOf.callAsync(tokenId, txData, defaultBlock); + return tokenOwner; + } catch (err) { + throw new Error(ContractWrappersError.ERC721OwnerNotFound); + } + } + /** + * Query if an address is an authorized operator for all NFT's of `ownerAddress` + * @param tokenAddress The hex encoded contract Ethereum address where the ERC721 token is deployed. + * @param ownerAddress The hex encoded user Ethereum address of the token owner. + * @param operatorAddress The hex encoded user Ethereum address of the operator you'd like to check if approved. + * @param methodOpts Optional arguments this method accepts. + * @return True if `operatorAddress` is an approved operator for `ownerAddress`, false otherwise + */ + public async isApprovedForAllAsync( + tokenAddress: string, + ownerAddress: string, + operatorAddress: string, + methodOpts?: MethodOpts, + ): Promise<boolean> { + assert.isETHAddressHex('tokenAddress', tokenAddress); + assert.isETHAddressHex('ownerAddress', ownerAddress); + assert.isETHAddressHex('operatorAddress', operatorAddress); + if (!_.isUndefined(methodOpts)) { + assert.doesConformToSchema('methodOpts', methodOpts, methodOptsSchema); + } + const normalizedTokenAddress = tokenAddress.toLowerCase(); + const normalizedOwnerAddress = ownerAddress.toLowerCase(); + const normalizedOperatorAddress = operatorAddress.toLowerCase(); + + const tokenContract = await this._getTokenContractAsync(normalizedTokenAddress); + const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock; + const txData = {}; + const isApprovedForAll = await tokenContract.isApprovedForAll.callAsync( + normalizedOwnerAddress, + normalizedOperatorAddress, + txData, + defaultBlock, + ); + return isApprovedForAll; + } + /** + * Query if 0x proxy is an authorized operator for all NFT's of `ownerAddress` + * @param tokenAddress The hex encoded contract Ethereum address where the ERC721 token is deployed. + * @param ownerAddress The hex encoded user Ethereum address of the token owner. + * @param methodOpts Optional arguments this method accepts. + * @return True if `operatorAddress` is an approved operator for `ownerAddress`, false otherwise + */ + public async isProxyApprovedForAllAsync( + tokenAddress: string, + ownerAddress: string, + methodOpts?: MethodOpts, + ): Promise<boolean> { + assert.isETHAddressHex('tokenAddress', tokenAddress); + assert.isETHAddressHex('ownerAddress', ownerAddress); + if (!_.isUndefined(methodOpts)) { + assert.doesConformToSchema('methodOpts', methodOpts, methodOptsSchema); + } + const normalizedTokenAddress = tokenAddress.toLowerCase(); + const normalizedOwnerAddress = ownerAddress.toLowerCase(); + const proxyAddress = this._erc721ProxyWrapper.getContractAddress(); + const isProxyApprovedForAll = await this.isApprovedForAllAsync( + normalizedTokenAddress, + normalizedOwnerAddress, + proxyAddress, + methodOpts, + ); + return isProxyApprovedForAll; + } + /** + * Get the approved address for a single NFT. Returns undefined if no approval was set + * Throws if `_tokenId` is not a valid NFT + * @param tokenAddress The hex encoded contract Ethereum address where the ERC721 token is deployed. + * @param tokenId The identifier for an NFT + * @param methodOpts Optional arguments this method accepts. + * @return The approved address for this NFT, or the undefined if there is none + */ + public async getApprovedIfExistsAsync( + tokenAddress: string, + tokenId: BigNumber, + methodOpts?: MethodOpts, + ): Promise<string | undefined> { + assert.isETHAddressHex('tokenAddress', tokenAddress); + assert.isBigNumber('tokenId', tokenId); + if (!_.isUndefined(methodOpts)) { + assert.doesConformToSchema('methodOpts', methodOpts, methodOptsSchema); + } + const normalizedTokenAddress = tokenAddress.toLowerCase(); + + const tokenContract = await this._getTokenContractAsync(normalizedTokenAddress); + const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock; + const txData = {}; + const approvedAddress = await tokenContract.getApproved.callAsync(tokenId, txData, defaultBlock); + if (approvedAddress === constants.NULL_ADDRESS) { + return undefined; + } + return approvedAddress; + } + /** + * Checks if 0x proxy is approved for a single NFT + * Throws if `_tokenId` is not a valid NFT + * @param tokenAddress The hex encoded contract Ethereum address where the ERC721 token is deployed. + * @param tokenId The identifier for an NFT + * @param methodOpts Optional arguments this method accepts. + * @return True if 0x proxy is approved + */ + public async isProxyApprovedAsync( + tokenAddress: string, + tokenId: BigNumber, + methodOpts?: MethodOpts, + ): Promise<boolean> { + const proxyAddress = this._erc721ProxyWrapper.getContractAddress(); + const approvedAddress = await this.getApprovedIfExistsAsync(tokenAddress, tokenId, methodOpts); + const isProxyApproved = approvedAddress === proxyAddress; + return isProxyApproved; + } + /** + * Enable or disable approval for a third party ("operator") to manage all of `ownerAddress`'s assets. + * Throws if `_tokenId` is not a valid NFT + * Emits the ApprovalForAll event. + * @param tokenAddress The hex encoded contract Ethereum address where the ERC721 token is deployed. + * @param ownerAddress The hex encoded user Ethereum address of the token owner. + * @param operatorAddress The hex encoded user Ethereum address of the operator you'd like to set approval for. + * @param isApproved The boolean variable to set the approval to. + * @param txOpts Transaction parameters. + * @return Transaction hash. + */ + public async setApprovalForAllAsync( + tokenAddress: string, + ownerAddress: string, + operatorAddress: string, + isApproved: boolean, + txOpts: TransactionOpts = {}, + ): Promise<string> { + assert.isETHAddressHex('tokenAddress', tokenAddress); + await assert.isSenderAddressAsync('ownerAddress', ownerAddress, this._web3Wrapper); + assert.isETHAddressHex('operatorAddress', operatorAddress); + assert.isBoolean('isApproved', isApproved); + if (!_.isUndefined(txOpts)) { + assert.doesConformToSchema('txOpts', txOpts, txOptsSchema); + } + const normalizedTokenAddress = tokenAddress.toLowerCase(); + const normalizedOwnerAddress = ownerAddress.toLowerCase(); + const normalizedOperatorAddress = operatorAddress.toLowerCase(); + + const tokenContract = await this._getTokenContractAsync(normalizedTokenAddress); + const txHash = await tokenContract.setApprovalForAll.sendTransactionAsync( + normalizedOperatorAddress, + isApproved, + removeUndefinedProperties({ + gas: txOpts.gasLimit, + gasPrice: txOpts.gasPrice, + from: normalizedOwnerAddress, + }), + ); + return txHash; + } + /** + * Enable or disable approval for a third party ("operator") to manage all of `ownerAddress`'s assets. + * Throws if `_tokenId` is not a valid NFT + * Emits the ApprovalForAll event. + * @param tokenAddress The hex encoded contract Ethereum address where the ERC721 token is deployed. + * @param ownerAddress The hex encoded user Ethereum address of the token owner. + * @param operatorAddress The hex encoded user Ethereum address of the operator you'd like to set approval for. + * @param isApproved The boolean variable to set the approval to. + * @param txOpts Transaction parameters. + * @return Transaction hash. + */ + public async setProxyApprovalForAllAsync( + tokenAddress: string, + ownerAddress: string, + isApproved: boolean, + txOpts: TransactionOpts = {}, + ): Promise<string> { + const proxyAddress = this._erc721ProxyWrapper.getContractAddress(); + const txHash = await this.setApprovalForAllAsync(tokenAddress, ownerAddress, proxyAddress, isApproved, txOpts); + return txHash; + } + /** + * Set or reaffirm the approved address for an NFT + * The zero address indicates there is no approved address. Throws unless `msg.sender` is the current NFT owner, + * or an authorized operator of the current owner. + * Throws if `_tokenId` is not a valid NFT + * Emits the Approval event. + * @param tokenAddress The hex encoded contract Ethereum address where the ERC721 token is deployed. + * @param approvedAddress The hex encoded user Ethereum address you'd like to set approval for. + * @param tokenId The identifier for an NFT + * @param txOpts Transaction parameters. + * @return Transaction hash. + */ + public async setApprovalAsync( + tokenAddress: string, + approvedAddress: string, + tokenId: BigNumber, + txOpts: TransactionOpts = {}, + ): Promise<string> { + assert.isETHAddressHex('tokenAddress', tokenAddress); + assert.isETHAddressHex('approvedAddress', approvedAddress); + assert.isBigNumber('tokenId', tokenId); + if (!_.isUndefined(txOpts)) { + assert.doesConformToSchema('txOpts', txOpts, txOptsSchema); + } + const normalizedTokenAddress = tokenAddress.toLowerCase(); + const normalizedApprovedAddress = approvedAddress.toLowerCase(); + + const tokenContract = await this._getTokenContractAsync(normalizedTokenAddress); + const tokenOwnerAddress = await tokenContract.ownerOf.callAsync(tokenId); + await assert.isSenderAddressAsync('tokenOwnerAddress', tokenOwnerAddress, this._web3Wrapper); + const txHash = await tokenContract.approve.sendTransactionAsync( + normalizedApprovedAddress, + tokenId, + removeUndefinedProperties({ + gas: txOpts.gasLimit, + gasPrice: txOpts.gasPrice, + from: tokenOwnerAddress, + }), + ); + return txHash; + } + /** + * Set or reaffirm 0x proxy as an approved address for an NFT + * Throws unless `msg.sender` is the current NFT owner, or an authorized operator of the current owner. + * Throws if `_tokenId` is not a valid NFT + * Emits the Approval event. + * @param tokenAddress The hex encoded contract Ethereum address where the ERC721 token is deployed. + * @param tokenId The identifier for an NFT + * @param txOpts Transaction parameters. + * @return Transaction hash. + */ + public async setProxyApprovalAsync( + tokenAddress: string, + tokenId: BigNumber, + txOpts: TransactionOpts = {}, + ): Promise<string> { + const proxyAddress = this._erc721ProxyWrapper.getContractAddress(); + const txHash = await this.setApprovalAsync(tokenAddress, proxyAddress, tokenId, txOpts); + return txHash; + } + /** + * Enable or disable approval for a third party ("operator") to manage all of `ownerAddress`'s assets. + * Throws if `_tokenId` is not a valid NFT + * Emits the ApprovalForAll event. + * @param tokenAddress The hex encoded contract Ethereum address where the ERC721 token is deployed. + * @param receiverAddress The hex encoded Ethereum address of the user to send the NFT to. + * @param senderAddress The hex encoded Ethereum address of the user to send the NFT to. + * @param tokenId The identifier for an NFT + * @param txOpts Transaction parameters. + * @return Transaction hash. + */ + public async transferFromAsync( + tokenAddress: string, + receiverAddress: string, + senderAddress: string, + tokenId: BigNumber, + txOpts: TransactionOpts = {}, + ): Promise<string> { + assert.isETHAddressHex('tokenAddress', tokenAddress); + assert.isETHAddressHex('receiverAddress', receiverAddress); + await assert.isSenderAddressAsync('senderAddress', senderAddress, this._web3Wrapper); + if (!_.isUndefined(txOpts)) { + assert.doesConformToSchema('txOpts', txOpts, txOptsSchema); + } + const normalizedTokenAddress = tokenAddress.toLowerCase(); + const normalizedReceiverAddress = receiverAddress.toLowerCase(); + 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); + } + } + const txHash = await tokenContract.transferFrom.sendTransactionAsync( + ownerAddress, + normalizedReceiverAddress, + tokenId, + removeUndefinedProperties({ + gas: txOpts.gasLimit, + gasPrice: txOpts.gasPrice, + from: normalizedSenderAddress, + }), + ); + return txHash; + } + /** + * Subscribe to an event type emitted by the Token contract. + * @param tokenAddress The hex encoded address where the ERC721 token is deployed. + * @param eventName The token contract event you would like to subscribe to. + * @param indexFilterValues An object where the keys are indexed args returned by the event and + * the value is the value you are interested in. E.g `{maker: aUserAddressHex}` + * @param callback Callback that gets called when a log is added/removed + * @return Subscription token used later to unsubscribe + */ + public subscribe<ArgsType extends ERC721TokenEventArgs>( + tokenAddress: string, + eventName: ERC721TokenEvents, + indexFilterValues: IndexedFilterValues, + callback: EventCallback<ArgsType>, + ): string { + assert.isETHAddressHex('tokenAddress', tokenAddress); + const normalizedTokenAddress = tokenAddress.toLowerCase(); + assert.doesBelongToStringEnum('eventName', eventName, ERC721TokenEvents); + assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema); + assert.isFunction('callback', callback); + const subscriptionToken = this._subscribe<ArgsType>( + normalizedTokenAddress, + eventName, + indexFilterValues, + artifacts.ERC721Token.compilerOutput.abi, + callback, + ); + return subscriptionToken; + } + /** + * Cancel a subscription + * @param subscriptionToken Subscription token returned by `subscribe()` + */ + public unsubscribe(subscriptionToken: string): void { + assert.isValidSubscriptionToken('subscriptionToken', subscriptionToken); + this._unsubscribe(subscriptionToken); + } + /** + * Cancels all existing subscriptions + */ + public unsubscribeAll(): void { + super._unsubscribeAll(); + } + /** + * Gets historical logs without creating a subscription + * @param tokenAddress An address of the token that emitted the logs. + * @param eventName The token contract event you would like to subscribe to. + * @param blockRange Block range to get logs from. + * @param indexFilterValues An object where the keys are indexed args returned by the event and + * the value is the value you are interested in. E.g `{_from: aUserAddressHex}` + * @return Array of logs that match the parameters + */ + public async getLogsAsync<ArgsType extends ERC721TokenEventArgs>( + tokenAddress: string, + eventName: ERC721TokenEvents, + blockRange: BlockRange, + indexFilterValues: IndexedFilterValues, + ): Promise<Array<LogWithDecodedArgs<ArgsType>>> { + assert.isETHAddressHex('tokenAddress', tokenAddress); + const normalizedTokenAddress = tokenAddress.toLowerCase(); + assert.doesBelongToStringEnum('eventName', eventName, ERC721TokenEvents); + assert.doesConformToSchema('blockRange', blockRange, schemas.blockRangeSchema); + assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema); + const logs = await this._getLogsAsync<ArgsType>( + normalizedTokenAddress, + eventName, + blockRange, + indexFilterValues, + artifacts.ERC721Token.compilerOutput.abi, + ); + return logs; + } + // HACK: We don't want this method to be visible to the other units within that package but not to the end user. + // TS doesn't give that possibility and therefore we make it private and access it over an any cast. Because of that tslint sees it as unused. + // tslint:disable-next-line:no-unused-variable + private _invalidateContractInstances(): void { + this.unsubscribeAll(); + this._tokenContractsByAddress = {}; + } + private async _getTokenContractAsync(tokenAddress: string): Promise<ERC721TokenContract> { + const normalizedTokenAddress = tokenAddress.toLowerCase(); + let tokenContract = this._tokenContractsByAddress[normalizedTokenAddress]; + if (!_.isUndefined(tokenContract)) { + return tokenContract; + } + const [abi, address] = await this._getContractAbiAndAddressFromArtifactsAsync( + artifacts.ERC721Token, + normalizedTokenAddress, + ); + const contractInstance = new ERC721TokenContract( + abi, + address, + this._web3Wrapper.getProvider(), + this._web3Wrapper.getContractDefaults(), + ); + tokenContract = contractInstance; + this._tokenContractsByAddress[normalizedTokenAddress] = tokenContract; + return tokenContract; + } +} diff --git a/packages/contract-wrappers/src/contract_wrappers/ether_token_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/ether_token_wrapper.ts index 36b7a234a..97872c247 100644 --- a/packages/contract-wrappers/src/contract_wrappers/ether_token_wrapper.ts +++ b/packages/contract-wrappers/src/contract_wrappers/ether_token_wrapper.ts @@ -1,7 +1,7 @@ import { schemas } from '@0xproject/json-schemas'; -import { ContractAbi, LogWithDecodedArgs } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; +import { ContractAbi, LogWithDecodedArgs } from 'ethereum-types'; import * as _ from 'lodash'; import { artifacts } from '../artifacts'; @@ -9,22 +9,24 @@ import { BlockRange, ContractWrappersError, EventCallback, IndexedFilterValues, import { assert } from '../utils/assert'; import { ContractWrapper } from './contract_wrapper'; -import { EtherTokenContract, EtherTokenContractEventArgs, EtherTokenEvents } from './generated/ether_token'; -import { TokenWrapper } from './token_wrapper'; +import { ERC20TokenWrapper } from './erc20_token_wrapper'; +import { WETH9Contract, WETH9EventArgs, WETH9Events } from './generated/weth9'; + +const removeUndefinedProperties = _.pickBy; /** * This class includes all the functionality related to interacting with a wrapped Ether ERC20 token contract. * The caller can convert ETH into the equivalent number of wrapped ETH ERC20 tokens and back. */ export class EtherTokenWrapper extends ContractWrapper { - public abi: ContractAbi = artifacts.EtherToken.abi; + public abi: ContractAbi = artifacts.EtherToken.compilerOutput.abi; private _etherTokenContractsByAddress: { - [address: string]: EtherTokenContract; + [address: string]: WETH9Contract; } = {}; - private _tokenWrapper: TokenWrapper; - constructor(web3Wrapper: Web3Wrapper, networkId: number, tokenWrapper: TokenWrapper) { + private _erc20TokenWrapper: ERC20TokenWrapper; + constructor(web3Wrapper: Web3Wrapper, networkId: number, erc20TokenWrapper: ERC20TokenWrapper) { super(web3Wrapper, networkId); - this._tokenWrapper = tokenWrapper; + this._erc20TokenWrapper = erc20TokenWrapper; } /** * Deposit ETH into the Wrapped ETH smart contract and issues the equivalent number of wrapped ETH tokens @@ -52,12 +54,14 @@ export class EtherTokenWrapper extends ContractWrapper { assert.assert(ethBalanceInWei.gte(amountInWei), ContractWrappersError.InsufficientEthBalanceForDeposit); const wethContract = await this._getEtherTokenContractAsync(normalizedEtherTokenAddress); - const txHash = await wethContract.deposit.sendTransactionAsync({ - from: normalizedDepositorAddress, - value: amountInWei, - gas: txOpts.gasLimit, - gasPrice: txOpts.gasPrice, - }); + const txHash = await wethContract.deposit.sendTransactionAsync( + removeUndefinedProperties({ + from: normalizedDepositorAddress, + value: amountInWei, + gas: txOpts.gasLimit, + gasPrice: txOpts.gasPrice, + }), + ); return txHash; } /** @@ -81,7 +85,7 @@ export class EtherTokenWrapper extends ContractWrapper { const normalizedEtherTokenAddress = etherTokenAddress.toLowerCase(); const normalizedWithdrawerAddress = withdrawer.toLowerCase(); - const WETHBalanceInBaseUnits = await this._tokenWrapper.getBalanceAsync( + const WETHBalanceInBaseUnits = await this._erc20TokenWrapper.getBalanceAsync( normalizedEtherTokenAddress, normalizedWithdrawerAddress, ); @@ -91,11 +95,14 @@ export class EtherTokenWrapper extends ContractWrapper { ); const wethContract = await this._getEtherTokenContractAsync(normalizedEtherTokenAddress); - const txHash = await wethContract.withdraw.sendTransactionAsync(amountInWei, { - from: normalizedWithdrawerAddress, - gas: txOpts.gasLimit, - gasPrice: txOpts.gasPrice, - }); + const txHash = await wethContract.withdraw.sendTransactionAsync( + amountInWei, + removeUndefinedProperties({ + from: normalizedWithdrawerAddress, + gas: txOpts.gasLimit, + gasPrice: txOpts.gasPrice, + }), + ); return txHash; } /** @@ -107,15 +114,15 @@ export class EtherTokenWrapper extends ContractWrapper { * the value is the value you are interested in. E.g `{_owner: aUserAddressHex}` * @return Array of logs that match the parameters */ - public async getLogsAsync<ArgsType extends EtherTokenContractEventArgs>( + public async getLogsAsync<ArgsType extends WETH9EventArgs>( etherTokenAddress: string, - eventName: EtherTokenEvents, + eventName: WETH9Events, blockRange: BlockRange, indexFilterValues: IndexedFilterValues, ): Promise<Array<LogWithDecodedArgs<ArgsType>>> { assert.isETHAddressHex('etherTokenAddress', etherTokenAddress); const normalizedEtherTokenAddress = etherTokenAddress.toLowerCase(); - assert.doesBelongToStringEnum('eventName', eventName, EtherTokenEvents); + assert.doesBelongToStringEnum('eventName', eventName, WETH9Events); assert.doesConformToSchema('blockRange', blockRange, schemas.blockRangeSchema); assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema); const logs = await this._getLogsAsync<ArgsType>( @@ -123,7 +130,7 @@ export class EtherTokenWrapper extends ContractWrapper { eventName, blockRange, indexFilterValues, - artifacts.EtherToken.abi, + artifacts.EtherToken.compilerOutput.abi, ); return logs; } @@ -136,22 +143,22 @@ export class EtherTokenWrapper extends ContractWrapper { * @param callback Callback that gets called when a log is added/removed * @return Subscription token used later to unsubscribe */ - public subscribe<ArgsType extends EtherTokenContractEventArgs>( + public subscribe<ArgsType extends WETH9EventArgs>( etherTokenAddress: string, - eventName: EtherTokenEvents, + eventName: WETH9Events, indexFilterValues: IndexedFilterValues, callback: EventCallback<ArgsType>, ): string { assert.isETHAddressHex('etherTokenAddress', etherTokenAddress); const normalizedEtherTokenAddress = etherTokenAddress.toLowerCase(); - assert.doesBelongToStringEnum('eventName', eventName, EtherTokenEvents); + assert.doesBelongToStringEnum('eventName', eventName, WETH9Events); assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema); assert.isFunction('callback', callback); const subscriptionToken = this._subscribe<ArgsType>( normalizedEtherTokenAddress, eventName, indexFilterValues, - artifacts.EtherToken.abi, + artifacts.EtherToken.compilerOutput.abi, callback, ); return subscriptionToken; @@ -161,6 +168,7 @@ export class EtherTokenWrapper extends ContractWrapper { * @param subscriptionToken Subscription token returned by `subscribe()` */ public unsubscribe(subscriptionToken: string): void { + assert.isValidSubscriptionToken('subscriptionToken', subscriptionToken); this._unsubscribe(subscriptionToken); } /** @@ -187,7 +195,7 @@ export class EtherTokenWrapper extends ContractWrapper { this.unsubscribeAll(); this._etherTokenContractsByAddress = {}; } - private async _getEtherTokenContractAsync(etherTokenAddress: string): Promise<EtherTokenContract> { + private async _getEtherTokenContractAsync(etherTokenAddress: string): Promise<WETH9Contract> { let etherTokenContract = this._etherTokenContractsByAddress[etherTokenAddress]; if (!_.isUndefined(etherTokenContract)) { return etherTokenContract; @@ -196,7 +204,7 @@ export class EtherTokenWrapper extends ContractWrapper { artifacts.EtherToken, etherTokenAddress, ); - const contractInstance = new EtherTokenContract( + const contractInstance = new WETH9Contract( abi, address, this._web3Wrapper.getProvider(), diff --git a/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts deleted file mode 100644 index 8548a06b6..000000000 --- a/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts +++ /dev/null @@ -1,988 +0,0 @@ -import { schemas } from '@0xproject/json-schemas'; -import { formatters, getOrderHashHex, OrderStateUtils } from '@0xproject/order-utils'; -import { - BlockParamLiteral, - ContractAbi, - DecodedLogArgs, - ECSignature, - ExchangeContractErrs, - LogEntry, - LogWithDecodedArgs, - Order, - OrderState, - SignedOrder, -} from '@0xproject/types'; -import { BigNumber } from '@0xproject/utils'; -import { Web3Wrapper } from '@0xproject/web3-wrapper'; -import * as _ from 'lodash'; - -import { artifacts } from '../artifacts'; -import { SimpleBalanceAndProxyAllowanceFetcher } from '../fetchers/simple_balance_and_proxy_allowance_fetcher'; -import { SimpleOrderFilledCancelledFetcher } from '../fetchers/simple_order_filled_cancelled_fetcher'; -import { BalanceAndProxyAllowanceLazyStore } from '../stores/balance_proxy_allowance_lazy_store'; -import { - BlockRange, - EventCallback, - ExchangeContractErrCodes, - IndexedFilterValues, - MethodOpts, - OrderCancellationRequest, - OrderFillRequest, - OrderTransactionOpts, - ValidateOrderFillableOpts, -} from '../types'; -import { assert } from '../utils/assert'; -import { decorators } from '../utils/decorators'; -import { ExchangeTransferSimulator } from '../utils/exchange_transfer_simulator'; -import { OrderValidationUtils } from '../utils/order_validation_utils'; - -import { ContractWrapper } from './contract_wrapper'; -import { - ExchangeContract, - ExchangeContractEventArgs, - ExchangeEvents, - LogErrorContractEventArgs, -} from './generated/exchange'; -import { TokenWrapper } from './token_wrapper'; -const SHOULD_VALIDATE_BY_DEFAULT = true; - -interface ExchangeContractErrCodesToMsgs { - [exchangeContractErrCodes: number]: string; -} - -/** - * This class includes all the functionality related to calling methods and subscribing to - * events of the 0x Exchange smart contract. - */ -export class ExchangeWrapper extends ContractWrapper { - public abi: ContractAbi = artifacts.Exchange.abi; - private _exchangeContractIfExists?: ExchangeContract; - private _orderValidationUtilsIfExists?: OrderValidationUtils; - private _tokenWrapper: TokenWrapper; - private _exchangeContractErrCodesToMsg: ExchangeContractErrCodesToMsgs = { - [ExchangeContractErrCodes.ERROR_FILL_EXPIRED]: ExchangeContractErrs.OrderFillExpired, - [ExchangeContractErrCodes.ERROR_CANCEL_EXPIRED]: ExchangeContractErrs.OrderFillExpired, - [ExchangeContractErrCodes.ERROR_FILL_NO_VALUE]: ExchangeContractErrs.OrderRemainingFillAmountZero, - [ExchangeContractErrCodes.ERROR_CANCEL_NO_VALUE]: ExchangeContractErrs.OrderRemainingFillAmountZero, - [ExchangeContractErrCodes.ERROR_FILL_TRUNCATION]: ExchangeContractErrs.OrderFillRoundingError, - [ExchangeContractErrCodes.ERROR_FILL_BALANCE_ALLOWANCE]: ExchangeContractErrs.FillBalanceAllowanceError, - }; - private _contractAddressIfExists?: string; - private _zrxContractAddressIfExists?: string; - constructor( - web3Wrapper: Web3Wrapper, - networkId: number, - tokenWrapper: TokenWrapper, - contractAddressIfExists?: string, - zrxContractAddressIfExists?: string, - ) { - super(web3Wrapper, networkId); - this._tokenWrapper = tokenWrapper; - this._contractAddressIfExists = contractAddressIfExists; - this._zrxContractAddressIfExists = zrxContractAddressIfExists; - } - /** - * Returns the unavailable takerAmount of an order. Unavailable amount is defined as the total - * amount that has been filled or cancelled. The remaining takerAmount can be calculated by - * subtracting the unavailable amount from the total order takerAmount. - * @param orderHash The hex encoded orderHash for which you would like to retrieve the - * unavailable takerAmount. - * @param methodOpts Optional arguments this method accepts. - * @return The amount of the order (in taker tokens) that has either been filled or cancelled. - */ - public async getUnavailableTakerAmountAsync(orderHash: string, methodOpts?: MethodOpts): Promise<BigNumber> { - assert.doesConformToSchema('orderHash', orderHash, schemas.orderHashSchema); - - const exchangeContract = await this._getExchangeContractAsync(); - const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock; - const txData = {}; - let unavailableTakerTokenAmount = await exchangeContract.getUnavailableTakerTokenAmount.callAsync( - orderHash, - txData, - defaultBlock, - ); - // Wrap BigNumbers returned from web3 with our own (later) version of BigNumber - unavailableTakerTokenAmount = new BigNumber(unavailableTakerTokenAmount); - return unavailableTakerTokenAmount; - } - /** - * Retrieve the takerAmount of an order that has already been filled. - * @param orderHash The hex encoded orderHash for which you would like to retrieve the filled takerAmount. - * @param methodOpts Optional arguments this method accepts. - * @return The amount of the order (in taker tokens) that has already been filled. - */ - public async getFilledTakerAmountAsync(orderHash: string, methodOpts?: MethodOpts): Promise<BigNumber> { - assert.doesConformToSchema('orderHash', orderHash, schemas.orderHashSchema); - - const exchangeContract = await this._getExchangeContractAsync(); - const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock; - const txData = {}; - let fillAmountInBaseUnits = await exchangeContract.filled.callAsync(orderHash, txData, defaultBlock); - // Wrap BigNumbers returned from web3 with our own (later) version of BigNumber - fillAmountInBaseUnits = new BigNumber(fillAmountInBaseUnits); - return fillAmountInBaseUnits; - } - /** - * Retrieve the takerAmount of an order that has been cancelled. - * @param orderHash The hex encoded orderHash for which you would like to retrieve the - * cancelled takerAmount. - * @param methodOpts Optional arguments this method accepts. - * @return The amount of the order (in taker tokens) that has been cancelled. - */ - public async getCancelledTakerAmountAsync(orderHash: string, methodOpts?: MethodOpts): Promise<BigNumber> { - assert.doesConformToSchema('orderHash', orderHash, schemas.orderHashSchema); - - const exchangeContract = await this._getExchangeContractAsync(); - const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock; - const txData = {}; - let cancelledAmountInBaseUnits = await exchangeContract.cancelled.callAsync(orderHash, txData, defaultBlock); - // Wrap BigNumbers returned from web3 with our own (later) version of BigNumber - cancelledAmountInBaseUnits = new BigNumber(cancelledAmountInBaseUnits); - return cancelledAmountInBaseUnits; - } - /** - * Fills a signed order with an amount denominated in baseUnits of the taker token. - * Since the order in which transactions are included in the next block is indeterminate, race-conditions - * could arise where a users balance or allowance changes before the fillOrder executes. Because of this, - * we allow you to specify `shouldThrowOnInsufficientBalanceOrAllowance`. - * If false, the smart contract will not throw if the parties - * do not have sufficient balances/allowances, preserving gas costs. Setting it to true forgoes this check - * and causes the smart contract to throw (using all the gas supplied) instead. - * @param signedOrder An object that conforms to the SignedOrder interface. - * @param fillTakerTokenAmount The amount of the order (in taker tokens baseUnits) that - * you wish to fill. - * @param shouldThrowOnInsufficientBalanceOrAllowance Whether or not you wish for the contract call to throw - * if upon execution the tokens cannot be transferred. - * @param takerAddress The user Ethereum address who would like to fill this order. - * Must be available via the supplied Provider - * passed to 0x.js. - * @param orderTransactionOpts Optional arguments this method accepts. - * @return Transaction hash. - */ - @decorators.asyncZeroExErrorHandler - public async fillOrderAsync( - signedOrder: SignedOrder, - fillTakerTokenAmount: BigNumber, - shouldThrowOnInsufficientBalanceOrAllowance: boolean, - takerAddress: string, - orderTransactionOpts: OrderTransactionOpts = {}, - ): Promise<string> { - assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema); - assert.isValidBaseUnitAmount('fillTakerTokenAmount', fillTakerTokenAmount); - assert.isBoolean('shouldThrowOnInsufficientBalanceOrAllowance', shouldThrowOnInsufficientBalanceOrAllowance); - await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); - const normalizedTakerAddress = takerAddress.toLowerCase(); - - const exchangeInstance = await this._getExchangeContractAsync(); - const shouldValidate = _.isUndefined(orderTransactionOpts.shouldValidate) - ? SHOULD_VALIDATE_BY_DEFAULT - : orderTransactionOpts.shouldValidate; - if (shouldValidate) { - const zrxTokenAddress = this.getZRXTokenAddress(); - const balanceAndProxyAllowanceLazyStore = new BalanceAndProxyAllowanceLazyStore( - this._tokenWrapper, - BlockParamLiteral.Latest, - ); - const exchangeTradeEmulator = new ExchangeTransferSimulator(balanceAndProxyAllowanceLazyStore); - const orderValidationUtils = await this._getOrderValidationUtilsAsync(); - await orderValidationUtils.validateFillOrderThrowIfInvalidAsync( - exchangeTradeEmulator, - signedOrder, - fillTakerTokenAmount, - normalizedTakerAddress, - zrxTokenAddress, - ); - } - - const [orderAddresses, orderValues] = formatters.getOrderAddressesAndValues(signedOrder); - - const txHash: string = await exchangeInstance.fillOrder.sendTransactionAsync( - orderAddresses, - orderValues, - fillTakerTokenAmount, - shouldThrowOnInsufficientBalanceOrAllowance, - signedOrder.ecSignature.v, - signedOrder.ecSignature.r, - signedOrder.ecSignature.s, - { - from: normalizedTakerAddress, - gas: orderTransactionOpts.gasLimit, - gasPrice: orderTransactionOpts.gasPrice, - }, - ); - return txHash; - } - /** - * Sequentially and atomically fills signedOrders up to the specified takerTokenFillAmount. - * If the fill amount is reached - it succeeds and does not fill the rest of the orders. - * If fill amount is not reached - it fills as much of the fill amount as possible and succeeds. - * @param signedOrders The array of signedOrders that you would like to fill until - * takerTokenFillAmount is reached. - * @param fillTakerTokenAmount The total amount of the takerTokens you would like to fill. - * @param shouldThrowOnInsufficientBalanceOrAllowance Whether or not you wish for the contract call to throw if - * upon execution any of the tokens cannot be transferred. - * If set to false, the call will continue to fill subsequent - * signedOrders even when some cannot be filled. - * @param takerAddress The user Ethereum address who would like to fill these - * orders. Must be available via the supplied Provider - * passed to 0x.js. - * @param orderTransactionOpts Optional arguments this method accepts. - * @return Transaction hash. - */ - @decorators.asyncZeroExErrorHandler - public async fillOrdersUpToAsync( - signedOrders: SignedOrder[], - fillTakerTokenAmount: BigNumber, - shouldThrowOnInsufficientBalanceOrAllowance: boolean, - takerAddress: string, - orderTransactionOpts: OrderTransactionOpts = {}, - ): Promise<string> { - assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema); - const takerTokenAddresses = _.map(signedOrders, signedOrder => signedOrder.takerTokenAddress); - assert.hasAtMostOneUniqueValue( - takerTokenAddresses, - ExchangeContractErrs.MultipleTakerTokensInFillUpToDisallowed, - ); - const exchangeContractAddresses = _.map(signedOrders, signedOrder => signedOrder.exchangeContractAddress); - assert.hasAtMostOneUniqueValue( - exchangeContractAddresses, - ExchangeContractErrs.BatchOrdersMustHaveSameExchangeAddress, - ); - assert.isValidBaseUnitAmount('fillTakerTokenAmount', fillTakerTokenAmount); - assert.isBoolean('shouldThrowOnInsufficientBalanceOrAllowance', shouldThrowOnInsufficientBalanceOrAllowance); - await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); - const normalizedTakerAddress = takerAddress.toLowerCase(); - - const shouldValidate = _.isUndefined(orderTransactionOpts.shouldValidate) - ? SHOULD_VALIDATE_BY_DEFAULT - : orderTransactionOpts.shouldValidate; - if (shouldValidate) { - let filledTakerTokenAmount = new BigNumber(0); - const zrxTokenAddress = this.getZRXTokenAddress(); - const balanceAndProxyAllowanceLazyStore = new BalanceAndProxyAllowanceLazyStore( - this._tokenWrapper, - BlockParamLiteral.Latest, - ); - const exchangeTradeEmulator = new ExchangeTransferSimulator(balanceAndProxyAllowanceLazyStore); - const orderValidationUtils = await this._getOrderValidationUtilsAsync(); - for (const signedOrder of signedOrders) { - const singleFilledTakerTokenAmount = await orderValidationUtils.validateFillOrderThrowIfInvalidAsync( - exchangeTradeEmulator, - signedOrder, - fillTakerTokenAmount.minus(filledTakerTokenAmount), - normalizedTakerAddress, - zrxTokenAddress, - ); - filledTakerTokenAmount = filledTakerTokenAmount.plus(singleFilledTakerTokenAmount); - if (filledTakerTokenAmount.eq(fillTakerTokenAmount)) { - break; - } - } - } - - if (_.isEmpty(signedOrders)) { - throw new Error(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem); - } - - const orderAddressesValuesAndSignatureArray = _.map(signedOrders, signedOrder => { - return [ - ...formatters.getOrderAddressesAndValues(signedOrder), - signedOrder.ecSignature.v, - signedOrder.ecSignature.r, - signedOrder.ecSignature.s, - ]; - }); - // We use _.unzip<any> because _.unzip doesn't type check if values have different types :'( - const [orderAddressesArray, orderValuesArray, vArray, rArray, sArray] = _.unzip<any>( - orderAddressesValuesAndSignatureArray, - ); - - const exchangeInstance = await this._getExchangeContractAsync(); - const txHash = await exchangeInstance.fillOrdersUpTo.sendTransactionAsync( - orderAddressesArray, - orderValuesArray, - fillTakerTokenAmount, - shouldThrowOnInsufficientBalanceOrAllowance, - vArray, - rArray, - sArray, - { - from: normalizedTakerAddress, - gas: orderTransactionOpts.gasLimit, - gasPrice: orderTransactionOpts.gasPrice, - }, - ); - return txHash; - } - /** - * Batch version of fillOrderAsync. - * Executes multiple fills atomically in a single transaction. - * If shouldThrowOnInsufficientBalanceOrAllowance is set to false, it will continue filling subsequent orders even - * when earlier ones fail. - * When shouldThrowOnInsufficientBalanceOrAllowance is set to true, if any fill fails, the entire batch fails. - * @param orderFillRequests An array of objects that conform to the - * OrderFillRequest interface. - * @param shouldThrowOnInsufficientBalanceOrAllowance Whether or not you wish for the contract call to throw - * if upon execution any of the tokens cannot be - * transferred. If set to false, the call will continue to - * fill subsequent signedOrders even when some - * cannot be filled. - * @param takerAddress The user Ethereum address who would like to fill - * these orders. Must be available via the supplied - * Provider passed to 0x.js. - * @param orderTransactionOpts Optional arguments this method accepts. - * @return Transaction hash. - */ - @decorators.asyncZeroExErrorHandler - public async batchFillOrdersAsync( - orderFillRequests: OrderFillRequest[], - shouldThrowOnInsufficientBalanceOrAllowance: boolean, - takerAddress: string, - orderTransactionOpts: OrderTransactionOpts = {}, - ): Promise<string> { - assert.doesConformToSchema('orderFillRequests', orderFillRequests, schemas.orderFillRequestsSchema); - const exchangeContractAddresses = _.map( - orderFillRequests, - orderFillRequest => orderFillRequest.signedOrder.exchangeContractAddress, - ); - assert.hasAtMostOneUniqueValue( - exchangeContractAddresses, - ExchangeContractErrs.BatchOrdersMustHaveSameExchangeAddress, - ); - assert.isBoolean('shouldThrowOnInsufficientBalanceOrAllowance', shouldThrowOnInsufficientBalanceOrAllowance); - await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); - const normalizedTakerAddress = takerAddress.toLowerCase(); - const shouldValidate = _.isUndefined(orderTransactionOpts.shouldValidate) - ? SHOULD_VALIDATE_BY_DEFAULT - : orderTransactionOpts.shouldValidate; - if (shouldValidate) { - const zrxTokenAddress = this.getZRXTokenAddress(); - const balanceAndProxyAllowanceLazyStore = new BalanceAndProxyAllowanceLazyStore( - this._tokenWrapper, - BlockParamLiteral.Latest, - ); - const exchangeTradeEmulator = new ExchangeTransferSimulator(balanceAndProxyAllowanceLazyStore); - const orderValidationUtils = await this._getOrderValidationUtilsAsync(); - for (const orderFillRequest of orderFillRequests) { - await orderValidationUtils.validateFillOrderThrowIfInvalidAsync( - exchangeTradeEmulator, - orderFillRequest.signedOrder, - orderFillRequest.takerTokenFillAmount, - normalizedTakerAddress, - zrxTokenAddress, - ); - } - } - if (_.isEmpty(orderFillRequests)) { - throw new Error(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem); - } - - const orderAddressesValuesAmountsAndSignatureArray = _.map(orderFillRequests, orderFillRequest => { - return [ - ...formatters.getOrderAddressesAndValues(orderFillRequest.signedOrder), - orderFillRequest.takerTokenFillAmount, - orderFillRequest.signedOrder.ecSignature.v, - orderFillRequest.signedOrder.ecSignature.r, - orderFillRequest.signedOrder.ecSignature.s, - ]; - }); - // We use _.unzip<any> because _.unzip doesn't type check if values have different types :'( - const [orderAddressesArray, orderValuesArray, fillTakerTokenAmounts, vArray, rArray, sArray] = _.unzip<any>( - orderAddressesValuesAmountsAndSignatureArray, - ); - - const exchangeInstance = await this._getExchangeContractAsync(); - const txHash = await exchangeInstance.batchFillOrders.sendTransactionAsync( - orderAddressesArray, - orderValuesArray, - fillTakerTokenAmounts, - shouldThrowOnInsufficientBalanceOrAllowance, - vArray, - rArray, - sArray, - { - from: normalizedTakerAddress, - gas: orderTransactionOpts.gasLimit, - gasPrice: orderTransactionOpts.gasPrice, - }, - ); - return txHash; - } - /** - * Attempts to fill a specific amount of an order. If the entire amount specified cannot be filled, - * the fill order is abandoned. - * @param signedOrder An object that conforms to the SignedOrder interface. The - * signedOrder you wish to fill. - * @param fillTakerTokenAmount The total amount of the takerTokens you would like to fill. - * @param takerAddress The user Ethereum address who would like to fill this order. - * Must be available via the supplied Provider passed to 0x.js. - * @param orderTransactionOpts Optional arguments this method accepts. - * @return Transaction hash. - */ - @decorators.asyncZeroExErrorHandler - public async fillOrKillOrderAsync( - signedOrder: SignedOrder, - fillTakerTokenAmount: BigNumber, - takerAddress: string, - orderTransactionOpts: OrderTransactionOpts = {}, - ): Promise<string> { - assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema); - assert.isValidBaseUnitAmount('fillTakerTokenAmount', fillTakerTokenAmount); - await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); - const normalizedTakerAddress = takerAddress.toLowerCase(); - - const exchangeInstance = await this._getExchangeContractAsync(); - - const shouldValidate = _.isUndefined(orderTransactionOpts.shouldValidate) - ? SHOULD_VALIDATE_BY_DEFAULT - : orderTransactionOpts.shouldValidate; - if (shouldValidate) { - const zrxTokenAddress = this.getZRXTokenAddress(); - const balanceAndProxyAllowanceLazyStore = new BalanceAndProxyAllowanceLazyStore( - this._tokenWrapper, - BlockParamLiteral.Latest, - ); - const exchangeTradeEmulator = new ExchangeTransferSimulator(balanceAndProxyAllowanceLazyStore); - const orderValidationUtils = await this._getOrderValidationUtilsAsync(); - await orderValidationUtils.validateFillOrKillOrderThrowIfInvalidAsync( - exchangeTradeEmulator, - signedOrder, - fillTakerTokenAmount, - normalizedTakerAddress, - zrxTokenAddress, - ); - } - - const [orderAddresses, orderValues] = formatters.getOrderAddressesAndValues(signedOrder); - const txHash = await exchangeInstance.fillOrKillOrder.sendTransactionAsync( - orderAddresses, - orderValues, - fillTakerTokenAmount, - signedOrder.ecSignature.v, - signedOrder.ecSignature.r, - signedOrder.ecSignature.s, - { - from: normalizedTakerAddress, - gas: orderTransactionOpts.gasLimit, - gasPrice: orderTransactionOpts.gasPrice, - }, - ); - return txHash; - } - /** - * Batch version of fillOrKill. Allows a taker to specify a batch of orders that will either be atomically - * filled (each to the specified fillAmount) or aborted. - * @param orderFillRequests An array of objects that conform to the OrderFillRequest interface. - * @param takerAddress The user Ethereum address who would like to fill there orders. - * Must be available via the supplied Provider passed to 0x.js. - * @param orderTransactionOpts Optional arguments this method accepts. - * @return Transaction hash. - */ - @decorators.asyncZeroExErrorHandler - public async batchFillOrKillAsync( - orderFillRequests: OrderFillRequest[], - takerAddress: string, - orderTransactionOpts: OrderTransactionOpts = {}, - ): Promise<string> { - assert.doesConformToSchema('orderFillRequests', orderFillRequests, schemas.orderFillRequestsSchema); - const exchangeContractAddresses = _.map( - orderFillRequests, - orderFillRequest => orderFillRequest.signedOrder.exchangeContractAddress, - ); - assert.hasAtMostOneUniqueValue( - exchangeContractAddresses, - ExchangeContractErrs.BatchOrdersMustHaveSameExchangeAddress, - ); - await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); - const normalizedTakerAddress = takerAddress.toLowerCase(); - if (_.isEmpty(orderFillRequests)) { - throw new Error(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem); - } - const exchangeInstance = await this._getExchangeContractAsync(); - - const shouldValidate = _.isUndefined(orderTransactionOpts.shouldValidate) - ? SHOULD_VALIDATE_BY_DEFAULT - : orderTransactionOpts.shouldValidate; - if (shouldValidate) { - const zrxTokenAddress = this.getZRXTokenAddress(); - const balanceAndProxyAllowanceLazyStore = new BalanceAndProxyAllowanceLazyStore( - this._tokenWrapper, - BlockParamLiteral.Latest, - ); - const exchangeTradeEmulator = new ExchangeTransferSimulator(balanceAndProxyAllowanceLazyStore); - const orderValidationUtils = await this._getOrderValidationUtilsAsync(); - for (const orderFillRequest of orderFillRequests) { - await orderValidationUtils.validateFillOrKillOrderThrowIfInvalidAsync( - exchangeTradeEmulator, - orderFillRequest.signedOrder, - orderFillRequest.takerTokenFillAmount, - normalizedTakerAddress, - zrxTokenAddress, - ); - } - } - - const orderAddressesValuesAndTakerTokenFillAmounts = _.map(orderFillRequests, request => { - return [ - ...formatters.getOrderAddressesAndValues(request.signedOrder), - request.takerTokenFillAmount, - request.signedOrder.ecSignature.v, - request.signedOrder.ecSignature.r, - request.signedOrder.ecSignature.s, - ]; - }); - - // We use _.unzip<any> because _.unzip doesn't type check if values have different types :'( - const [orderAddresses, orderValues, fillTakerTokenAmounts, vParams, rParams, sParams] = _.unzip<any>( - orderAddressesValuesAndTakerTokenFillAmounts, - ); - const txHash = await exchangeInstance.batchFillOrKillOrders.sendTransactionAsync( - orderAddresses, - orderValues, - fillTakerTokenAmounts, - vParams, - rParams, - sParams, - { - from: normalizedTakerAddress, - gas: orderTransactionOpts.gasLimit, - gasPrice: orderTransactionOpts.gasPrice, - }, - ); - return txHash; - } - /** - * Cancel a given fill amount of an order. Cancellations are cumulative. - * @param order An object that conforms to the Order or SignedOrder interface. - * The order you would like to cancel. - * @param cancelTakerTokenAmount The amount (specified in taker tokens) that you would like to cancel. - * @param transactionOpts Optional arguments this method accepts. - * @return Transaction hash. - */ - @decorators.asyncZeroExErrorHandler - public async cancelOrderAsync( - order: Order | SignedOrder, - cancelTakerTokenAmount: BigNumber, - orderTransactionOpts: OrderTransactionOpts = {}, - ): Promise<string> { - assert.doesConformToSchema('order', order, schemas.orderSchema); - assert.isValidBaseUnitAmount('takerTokenCancelAmount', cancelTakerTokenAmount); - await assert.isSenderAddressAsync('order.maker', order.maker, this._web3Wrapper); - const normalizedMakerAddress = order.maker.toLowerCase(); - - const exchangeInstance = await this._getExchangeContractAsync(); - - const shouldValidate = _.isUndefined(orderTransactionOpts.shouldValidate) - ? SHOULD_VALIDATE_BY_DEFAULT - : orderTransactionOpts.shouldValidate; - if (shouldValidate) { - const orderHash = getOrderHashHex(order); - const unavailableTakerTokenAmount = await this.getUnavailableTakerAmountAsync(orderHash); - OrderValidationUtils.validateCancelOrderThrowIfInvalid( - order, - cancelTakerTokenAmount, - unavailableTakerTokenAmount, - ); - } - - const [orderAddresses, orderValues] = formatters.getOrderAddressesAndValues(order); - const txHash = await exchangeInstance.cancelOrder.sendTransactionAsync( - orderAddresses, - orderValues, - cancelTakerTokenAmount, - { - from: normalizedMakerAddress, - gas: orderTransactionOpts.gasLimit, - gasPrice: orderTransactionOpts.gasPrice, - }, - ); - return txHash; - } - /** - * Batch version of cancelOrderAsync. Atomically cancels multiple orders in a single transaction. - * All orders must be from the same maker. - * @param orderCancellationRequests An array of objects that conform to the OrderCancellationRequest - * interface. - * @param transactionOpts Optional arguments this method accepts. - * @return Transaction hash. - */ - @decorators.asyncZeroExErrorHandler - public async batchCancelOrdersAsync( - orderCancellationRequests: OrderCancellationRequest[], - orderTransactionOpts: OrderTransactionOpts = {}, - ): Promise<string> { - assert.doesConformToSchema( - 'orderCancellationRequests', - orderCancellationRequests, - schemas.orderCancellationRequestsSchema, - ); - const exchangeContractAddresses = _.map( - orderCancellationRequests, - orderCancellationRequest => orderCancellationRequest.order.exchangeContractAddress, - ); - assert.hasAtMostOneUniqueValue( - exchangeContractAddresses, - ExchangeContractErrs.BatchOrdersMustHaveSameExchangeAddress, - ); - const makers = _.map(orderCancellationRequests, cancellationRequest => cancellationRequest.order.maker); - assert.hasAtMostOneUniqueValue(makers, ExchangeContractErrs.MultipleMakersInSingleCancelBatchDisallowed); - const maker = makers[0]; - await assert.isSenderAddressAsync('maker', maker, this._web3Wrapper); - const normalizedMakerAddress = maker.toLowerCase(); - - const shouldValidate = _.isUndefined(orderTransactionOpts.shouldValidate) - ? SHOULD_VALIDATE_BY_DEFAULT - : orderTransactionOpts.shouldValidate; - if (shouldValidate) { - for (const orderCancellationRequest of orderCancellationRequests) { - const orderHash = getOrderHashHex(orderCancellationRequest.order); - const unavailableTakerTokenAmount = await this.getUnavailableTakerAmountAsync(orderHash); - OrderValidationUtils.validateCancelOrderThrowIfInvalid( - orderCancellationRequest.order, - orderCancellationRequest.takerTokenCancelAmount, - unavailableTakerTokenAmount, - ); - } - } - if (_.isEmpty(orderCancellationRequests)) { - throw new Error(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem); - } - const exchangeInstance = await this._getExchangeContractAsync(); - const orderAddressesValuesAndTakerTokenCancelAmounts = _.map(orderCancellationRequests, cancellationRequest => { - return [ - ...formatters.getOrderAddressesAndValues(cancellationRequest.order), - cancellationRequest.takerTokenCancelAmount, - ]; - }); - // We use _.unzip<any> because _.unzip doesn't type check if values have different types :'( - const [orderAddresses, orderValues, cancelTakerTokenAmounts] = _.unzip<any>( - orderAddressesValuesAndTakerTokenCancelAmounts, - ); - const txHash = await exchangeInstance.batchCancelOrders.sendTransactionAsync( - orderAddresses, - orderValues, - cancelTakerTokenAmounts, - { - from: normalizedMakerAddress, - gas: orderTransactionOpts.gasLimit, - gasPrice: orderTransactionOpts.gasPrice, - }, - ); - return txHash; - } - /** - * Subscribe to an event type emitted by the Exchange contract. - * @param eventName The exchange contract event you would like to subscribe to. - * @param indexFilterValues An object where the keys are indexed args returned by the event and - * the value is the value you are interested in. E.g `{maker: aUserAddressHex}` - * @param callback Callback that gets called when a log is added/removed - * @return Subscription token used later to unsubscribe - */ - public subscribe<ArgsType extends ExchangeContractEventArgs>( - eventName: ExchangeEvents, - indexFilterValues: IndexedFilterValues, - callback: EventCallback<ArgsType>, - ): string { - assert.doesBelongToStringEnum('eventName', eventName, ExchangeEvents); - assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema); - assert.isFunction('callback', callback); - const exchangeContractAddress = this.getContractAddress(); - const subscriptionToken = this._subscribe<ArgsType>( - exchangeContractAddress, - eventName, - indexFilterValues, - artifacts.Exchange.abi, - callback, - ); - return subscriptionToken; - } - /** - * Cancel a subscription - * @param subscriptionToken Subscription token returned by `subscribe()` - */ - public unsubscribe(subscriptionToken: string): void { - this._unsubscribe(subscriptionToken); - } - /** - * Cancels all existing subscriptions - */ - public unsubscribeAll(): void { - super._unsubscribeAll(); - } - /** - * Gets historical logs without creating a subscription - * @param eventName The exchange contract event you would like to subscribe to. - * @param blockRange Block range to get logs from. - * @param indexFilterValues An object where the keys are indexed args returned by the event and - * the value is the value you are interested in. E.g `{_from: aUserAddressHex}` - * @return Array of logs that match the parameters - */ - public async getLogsAsync<ArgsType extends ExchangeContractEventArgs>( - eventName: ExchangeEvents, - blockRange: BlockRange, - indexFilterValues: IndexedFilterValues, - ): Promise<Array<LogWithDecodedArgs<ArgsType>>> { - assert.doesBelongToStringEnum('eventName', eventName, ExchangeEvents); - assert.doesConformToSchema('blockRange', blockRange, schemas.blockRangeSchema); - assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema); - const exchangeContractAddress = this.getContractAddress(); - const logs = await this._getLogsAsync<ArgsType>( - exchangeContractAddress, - eventName, - blockRange, - indexFilterValues, - artifacts.Exchange.abi, - ); - return logs; - } - /** - * Retrieves the Ethereum address of the Exchange contract deployed on the network - * that the user-passed web3 provider is connected to. - * @returns The Ethereum address of the Exchange contract being used. - */ - public getContractAddress(): string { - const contractAddress = this._getContractAddress(artifacts.Exchange, this._contractAddressIfExists); - return contractAddress; - } - /** - * Checks if order is still fillable and throws an error otherwise. Useful for orderbook - * pruning where you want to remove stale orders without knowing who the taker will be. - * @param signedOrder An object that conforms to the SignedOrder interface. The - * signedOrder you wish to validate. - * @param opts An object that conforms to the ValidateOrderFillableOpts - * interface. Allows specifying a specific fillTakerTokenAmount - * to validate for. - */ - public async validateOrderFillableOrThrowAsync( - signedOrder: SignedOrder, - opts?: ValidateOrderFillableOpts, - ): Promise<void> { - assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema); - const zrxTokenAddress = this.getZRXTokenAddress(); - const expectedFillTakerTokenAmount = !_.isUndefined(opts) ? opts.expectedFillTakerTokenAmount : undefined; - const balanceAndProxyAllowanceLazyStore = new BalanceAndProxyAllowanceLazyStore( - this._tokenWrapper, - BlockParamLiteral.Latest, - ); - const exchangeTradeEmulator = new ExchangeTransferSimulator(balanceAndProxyAllowanceLazyStore); - const orderValidationUtils = await this._getOrderValidationUtilsAsync(); - await orderValidationUtils.validateOrderFillableOrThrowAsync( - exchangeTradeEmulator, - signedOrder, - zrxTokenAddress, - expectedFillTakerTokenAmount, - ); - } - /** - * Checks if order fill will succeed and throws an error otherwise. - * @param signedOrder An object that conforms to the SignedOrder interface. The - * signedOrder you wish to fill. - * @param fillTakerTokenAmount The total amount of the takerTokens you would like to fill. - * @param takerAddress The user Ethereum address who would like to fill this order. - * Must be available via the supplied Provider passed to 0x.js. - */ - public async validateFillOrderThrowIfInvalidAsync( - signedOrder: SignedOrder, - fillTakerTokenAmount: BigNumber, - takerAddress: string, - ): Promise<void> { - assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema); - assert.isValidBaseUnitAmount('fillTakerTokenAmount', fillTakerTokenAmount); - await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); - const normalizedTakerAddress = takerAddress.toLowerCase(); - const zrxTokenAddress = this.getZRXTokenAddress(); - const balanceAndProxyAllowanceLazyStore = new BalanceAndProxyAllowanceLazyStore( - this._tokenWrapper, - BlockParamLiteral.Latest, - ); - const exchangeTradeEmulator = new ExchangeTransferSimulator(balanceAndProxyAllowanceLazyStore); - const orderValidationUtils = await this._getOrderValidationUtilsAsync(); - await orderValidationUtils.validateFillOrderThrowIfInvalidAsync( - exchangeTradeEmulator, - signedOrder, - fillTakerTokenAmount, - normalizedTakerAddress, - zrxTokenAddress, - ); - } - /** - * Checks if cancelling a given order will succeed and throws an informative error if it won't. - * @param order An object that conforms to the Order or SignedOrder interface. - * The order you would like to cancel. - * @param cancelTakerTokenAmount The amount (specified in taker tokens) that you would like to cancel. - */ - public async validateCancelOrderThrowIfInvalidAsync( - order: Order, - cancelTakerTokenAmount: BigNumber, - ): Promise<void> { - assert.doesConformToSchema('order', order, schemas.orderSchema); - assert.isValidBaseUnitAmount('cancelTakerTokenAmount', cancelTakerTokenAmount); - const orderHash = getOrderHashHex(order); - const unavailableTakerTokenAmount = await this.getUnavailableTakerAmountAsync(orderHash); - OrderValidationUtils.validateCancelOrderThrowIfInvalid( - order, - cancelTakerTokenAmount, - unavailableTakerTokenAmount, - ); - } - /** - * Checks if calling fillOrKill on a given order will succeed and throws an informative error if it won't. - * @param signedOrder An object that conforms to the SignedOrder interface. The - * signedOrder you wish to fill. - * @param fillTakerTokenAmount The total amount of the takerTokens you would like to fill. - * @param takerAddress The user Ethereum address who would like to fill this order. - * Must be available via the supplied Provider passed to 0x.js. - */ - public async validateFillOrKillOrderThrowIfInvalidAsync( - signedOrder: SignedOrder, - fillTakerTokenAmount: BigNumber, - takerAddress: string, - ): Promise<void> { - assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema); - assert.isValidBaseUnitAmount('fillTakerTokenAmount', fillTakerTokenAmount); - await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); - const normalizedTakerAddress = takerAddress.toLowerCase(); - const zrxTokenAddress = this.getZRXTokenAddress(); - const balanceAndProxyAllowanceLazyStore = new BalanceAndProxyAllowanceLazyStore( - this._tokenWrapper, - BlockParamLiteral.Latest, - ); - const exchangeTradeEmulator = new ExchangeTransferSimulator(balanceAndProxyAllowanceLazyStore); - const orderValidationUtils = await this._getOrderValidationUtilsAsync(); - await orderValidationUtils.validateFillOrKillOrderThrowIfInvalidAsync( - exchangeTradeEmulator, - signedOrder, - fillTakerTokenAmount, - normalizedTakerAddress, - zrxTokenAddress, - ); - } - /** - * Checks if rounding error will be > 0.1% when computing makerTokenAmount by doing: - * `(fillTakerTokenAmount * makerTokenAmount) / takerTokenAmount`. - * 0x Protocol does not accept any trades that result in large rounding errors. This means that tokens with few or - * no decimals can only be filled in quantities and ratios that avoid large rounding errors. - * @param fillTakerTokenAmount The amount of the order (in taker tokens baseUnits) that you wish to fill. - * @param takerTokenAmount The order size on the taker side - * @param makerTokenAmount The order size on the maker side - */ - public async isRoundingErrorAsync( - fillTakerTokenAmount: BigNumber, - takerTokenAmount: BigNumber, - makerTokenAmount: BigNumber, - ): Promise<boolean> { - assert.isValidBaseUnitAmount('fillTakerTokenAmount', fillTakerTokenAmount); - assert.isValidBaseUnitAmount('takerTokenAmount', takerTokenAmount); - assert.isValidBaseUnitAmount('makerTokenAmount', makerTokenAmount); - const exchangeInstance = await this._getExchangeContractAsync(); - const isRoundingError = await exchangeInstance.isRoundingError.callAsync( - fillTakerTokenAmount, - takerTokenAmount, - makerTokenAmount, - ); - return isRoundingError; - } - /** - * Checks if logs contain LogError, which is emitted by Exchange contract on transaction failure. - * @param logs Transaction logs as returned by `zeroEx.awaitTransactionMinedAsync` - */ - public throwLogErrorsAsErrors(logs: Array<LogWithDecodedArgs<DecodedLogArgs> | LogEntry>): void { - const errLog = _.find(logs, { - event: ExchangeEvents.LogError, - }); - if (!_.isUndefined(errLog)) { - const logArgs = (errLog as LogWithDecodedArgs<LogErrorContractEventArgs>).args; - const errCode = logArgs.errorId; - const errMessage = this._exchangeContractErrCodesToMsg[errCode]; - throw new Error(errMessage); - } - } - /** - * Gets the latest OrderState of a signedOrder - * @param signedOrder The signedOrder - * @param stateLayer Optional, desired blockchain state layer (defaults to latest). - * @return OrderState of the signedOrder - */ - public async getOrderStateAsync( - signedOrder: SignedOrder, - stateLayer: BlockParamLiteral = BlockParamLiteral.Latest, - ): Promise<OrderState> { - const simpleBalanceAndProxyAllowanceFetcher = new SimpleBalanceAndProxyAllowanceFetcher( - this._tokenWrapper, - stateLayer, - ); - const simpleOrderFilledCancelledFetcher = new SimpleOrderFilledCancelledFetcher(this, stateLayer); - const orderStateUtils = new OrderStateUtils( - simpleBalanceAndProxyAllowanceFetcher, - simpleOrderFilledCancelledFetcher, - ); - const orderState = orderStateUtils.getOrderStateAsync(signedOrder); - return orderState; - } - /** - * Returns the ZRX token address used by the exchange contract. - * @return Address of ZRX token - */ - public getZRXTokenAddress(): string { - const contractAddress = this._getContractAddress(artifacts.ZRX, this._zrxContractAddressIfExists); - return contractAddress; - } - // tslint:disable:no-unused-variable - private _invalidateContractInstances(): void { - this.unsubscribeAll(); - delete this._exchangeContractIfExists; - } - private async _isValidSignatureUsingContractCallAsync( - dataHex: string, - ecSignature: ECSignature, - signerAddressHex: string, - ): Promise<boolean> { - assert.isHexString('dataHex', dataHex); - assert.doesConformToSchema('ecSignature', ecSignature, schemas.ecSignatureSchema); - assert.isETHAddressHex('signerAddressHex', signerAddressHex); - const normalizedSignerAddress = signerAddressHex.toLowerCase(); - - const exchangeInstance = await this._getExchangeContractAsync(); - - const isValidSignature = await exchangeInstance.isValidSignature.callAsync( - normalizedSignerAddress, - dataHex, - ecSignature.v, - ecSignature.r, - ecSignature.s, - ); - return isValidSignature; - } - private async _getOrderHashHexUsingContractCallAsync(order: Order | SignedOrder): Promise<string> { - const exchangeInstance = await this._getExchangeContractAsync(); - const [orderAddresses, orderValues] = formatters.getOrderAddressesAndValues(order); - const orderHashHex = await exchangeInstance.getOrderHash.callAsync(orderAddresses, orderValues); - return orderHashHex; - } - private async _getOrderValidationUtilsAsync(): Promise<OrderValidationUtils> { - if (!_.isUndefined(this._orderValidationUtilsIfExists)) { - return this._orderValidationUtilsIfExists; - } - const exchangeContract = await this._getExchangeContractAsync(); - this._orderValidationUtilsIfExists = new OrderValidationUtils(exchangeContract); - return this._orderValidationUtilsIfExists; - } - // tslint:enable:no-unused-variable - private async _getExchangeContractAsync(): Promise<ExchangeContract> { - if (!_.isUndefined(this._exchangeContractIfExists)) { - return this._exchangeContractIfExists; - } - const [abi, address] = await this._getContractAbiAndAddressFromArtifactsAsync( - artifacts.Exchange, - this._contractAddressIfExists, - ); - const contractInstance = new ExchangeContract( - abi, - address, - this._web3Wrapper.getProvider(), - this._web3Wrapper.getContractDefaults(), - ); - this._exchangeContractIfExists = contractInstance; - return this._exchangeContractIfExists; - } -} // tslint:disable:max-file-line-count diff --git a/packages/contract-wrappers/src/contract_wrappers/token_registry_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/token_registry_wrapper.ts deleted file mode 100644 index 7b558ed69..000000000 --- a/packages/contract-wrappers/src/contract_wrappers/token_registry_wrapper.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { ContractAbi, Token } from '@0xproject/types'; -import { Web3Wrapper } from '@0xproject/web3-wrapper'; -import * as _ from 'lodash'; - -import { artifacts } from '../artifacts'; -import { TokenMetadata } from '../types'; -import { assert } from '../utils/assert'; -import { constants } from '../utils/constants'; - -import { ContractWrapper } from './contract_wrapper'; -import { TokenRegistryContract } from './generated/token_registry'; - -/** - * This class includes all the functionality related to interacting with the 0x Token Registry smart contract. - */ -export class TokenRegistryWrapper extends ContractWrapper { - public abi: ContractAbi = artifacts.TokenRegistry.abi; - private _tokenRegistryContractIfExists?: TokenRegistryContract; - private _contractAddressIfExists?: string; - private static _createTokenFromMetadata(metadata: TokenMetadata): Token | undefined { - if (metadata[0] === constants.NULL_ADDRESS) { - return undefined; - } - const token = { - address: metadata[0], - name: metadata[1], - symbol: metadata[2], - decimals: metadata[3], - }; - return token; - } - constructor(web3Wrapper: Web3Wrapper, networkId: number, contractAddressIfExists?: string) { - super(web3Wrapper, networkId); - this._contractAddressIfExists = contractAddressIfExists; - } - /** - * Retrieves all the tokens currently listed in the Token Registry smart contract - * @return An array of objects that conform to the Token interface. - */ - public async getTokensAsync(): Promise<Token[]> { - const addresses = await this.getTokenAddressesAsync(); - const tokenPromises: Array<Promise<Token | undefined>> = _.map(addresses, async (address: string) => - this.getTokenIfExistsAsync(address), - ); - const tokens = await Promise.all(tokenPromises); - return tokens as Token[]; - } - /** - * Retrieves all the addresses of the tokens currently listed in the Token Registry smart contract - * @return An array of token addresses. - */ - public async getTokenAddressesAsync(): Promise<string[]> { - const tokenRegistryContract = await this._getTokenRegistryContractAsync(); - const addresses = await tokenRegistryContract.getTokenAddresses.callAsync(); - const lowerCaseAddresses = _.map(addresses, address => address.toLowerCase()); - return lowerCaseAddresses; - } - /** - * Retrieves a token by address currently listed in the Token Registry smart contract - * @return An object that conforms to the Token interface or undefined if token not found. - */ - public async getTokenIfExistsAsync(address: string): Promise<Token | undefined> { - assert.isETHAddressHex('address', address); - const normalizedAddress = address.toLowerCase(); - - const tokenRegistryContract = await this._getTokenRegistryContractAsync(); - const metadata = await tokenRegistryContract.getTokenMetaData.callAsync(normalizedAddress); - const token = TokenRegistryWrapper._createTokenFromMetadata(metadata); - return token; - } - public async getTokenAddressBySymbolIfExistsAsync(symbol: string): Promise<string | undefined> { - assert.isString('symbol', symbol); - const tokenRegistryContract = await this._getTokenRegistryContractAsync(); - const addressIfExists = await tokenRegistryContract.getTokenAddressBySymbol.callAsync(symbol); - if (addressIfExists === constants.NULL_ADDRESS) { - return undefined; - } - return addressIfExists; - } - public async getTokenAddressByNameIfExistsAsync(name: string): Promise<string | undefined> { - assert.isString('name', name); - const tokenRegistryContract = await this._getTokenRegistryContractAsync(); - const addressIfExists = await tokenRegistryContract.getTokenAddressByName.callAsync(name); - if (addressIfExists === constants.NULL_ADDRESS) { - return undefined; - } - return addressIfExists; - } - public async getTokenBySymbolIfExistsAsync(symbol: string): Promise<Token | undefined> { - assert.isString('symbol', symbol); - const tokenRegistryContract = await this._getTokenRegistryContractAsync(); - const metadata = await tokenRegistryContract.getTokenBySymbol.callAsync(symbol); - const token = TokenRegistryWrapper._createTokenFromMetadata(metadata); - return token; - } - public async getTokenByNameIfExistsAsync(name: string): Promise<Token | undefined> { - assert.isString('name', name); - const tokenRegistryContract = await this._getTokenRegistryContractAsync(); - const metadata = await tokenRegistryContract.getTokenByName.callAsync(name); - const token = TokenRegistryWrapper._createTokenFromMetadata(metadata); - return token; - } - /** - * Retrieves the Ethereum address of the TokenRegistry contract deployed on the network - * that the user-passed web3 provider is connected to. - * @returns The Ethereum address of the TokenRegistry contract being used. - */ - public getContractAddress(): string { - const contractAddress = this._getContractAddress(artifacts.TokenRegistry, this._contractAddressIfExists); - return contractAddress; - } - // tslint:disable-next-line:no-unused-variable - private _invalidateContractInstance(): void { - delete this._tokenRegistryContractIfExists; - } - private async _getTokenRegistryContractAsync(): Promise<TokenRegistryContract> { - if (!_.isUndefined(this._tokenRegistryContractIfExists)) { - return this._tokenRegistryContractIfExists; - } - const [abi, address] = await this._getContractAbiAndAddressFromArtifactsAsync( - artifacts.TokenRegistry, - this._contractAddressIfExists, - ); - const contractInstance = new TokenRegistryContract( - abi, - address, - this._web3Wrapper.getProvider(), - this._web3Wrapper.getContractDefaults(), - ); - this._tokenRegistryContractIfExists = contractInstance; - return this._tokenRegistryContractIfExists; - } -} |