diff options
author | Fabio Berger <me@fabioberger.com> | 2019-01-08 21:30:38 +0800 |
---|---|---|
committer | Fabio Berger <me@fabioberger.com> | 2019-01-08 21:30:38 +0800 |
commit | 1631031fa74894143cb6835030b7dcd44d7c3c6b (patch) | |
tree | 06dea01cc64fb42905a5f95c95f4b3e16ecfe744 /packages/contract-wrappers/src | |
parent | 0bcb81d3a918fbcf71d68f42fa661d884d5d74cf (diff) | |
parent | 0ac36cef288deecd36caa601c53d13517eef5ca8 (diff) | |
download | dexon-sol-tools-1631031fa74894143cb6835030b7dcd44d7c3c6b.tar.gz dexon-sol-tools-1631031fa74894143cb6835030b7dcd44d7c3c6b.tar.zst dexon-sol-tools-1631031fa74894143cb6835030b7dcd44d7c3c6b.zip |
Merge branch 'development' into feature/order-watcher/dockerize
* development: (898 commits)
Fixed merge conflict from development
Ran prettier
Doc generation working for changes by dutch auction wrapper
added changelog entry for monorepo-scripts
Hide dutch auction wrapper from docs -- hopefully this will prevent the "must export Web3Wrapper" error from doc generation
relaxed version on contract-extension dependencies
Added NetworkID 50 address for dutch auction wrapper
removed manual updte of package.json version
export dutch auction wrapper types from 0x.js
Export dutch auction wrapper in 0x.js
ran prettier
Minor documentation updates to dutch auction wrapper
`afterAuctionDetails` -> `auctionDetails`
Added @todo for including dutch auction addresses once deployed
Ran prettier & linter
Removed redundant assignment
removed needless newline on contract-wrappers changelog
removed timestamp from changelog for abi-gen-wrappers
added dutch auction address for testnets
removed .only
...
Diffstat (limited to 'packages/contract-wrappers/src')
7 files changed, 267 insertions, 34 deletions
diff --git a/packages/contract-wrappers/src/contract_wrappers.ts b/packages/contract-wrappers/src/contract_wrappers.ts index 0c535bd5c..4e594593e 100644 --- a/packages/contract-wrappers/src/contract_wrappers.ts +++ b/packages/contract-wrappers/src/contract_wrappers.ts @@ -12,6 +12,7 @@ import { Web3Wrapper } from '@0x/web3-wrapper'; import { Provider } from 'ethereum-types'; import * as _ from 'lodash'; +import { DutchAuctionWrapper } from './contract_wrappers/dutch_auction_wrapper'; import { ERC20ProxyWrapper } from './contract_wrappers/erc20_proxy_wrapper'; import { ERC20TokenWrapper } from './contract_wrappers/erc20_token_wrapper'; import { ERC721ProxyWrapper } from './contract_wrappers/erc721_proxy_wrapper'; @@ -65,6 +66,10 @@ export class ContractWrappers { * An instance of the OrderValidatorWrapper class containing methods for interacting with any OrderValidator smart contract. */ public orderValidator: OrderValidatorWrapper; + /** + * An instance of the DutchAuctionWrapper class containing methods for interacting with any DutchAuction smart contract. + */ + public dutchAuction: DutchAuctionWrapper; private readonly _web3Wrapper: Web3Wrapper; /** @@ -141,6 +146,11 @@ export class ContractWrappers { config.networkId, contractAddresses.orderValidator, ); + this.dutchAuction = new DutchAuctionWrapper( + this._web3Wrapper, + config.networkId, + contractAddresses.dutchAuction, + ); } /** * Unsubscribes from all subscriptions for all contracts. diff --git a/packages/contract-wrappers/src/contract_wrappers/dutch_auction_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/dutch_auction_wrapper.ts new file mode 100644 index 000000000..c1aceff47 --- /dev/null +++ b/packages/contract-wrappers/src/contract_wrappers/dutch_auction_wrapper.ts @@ -0,0 +1,182 @@ +import { DutchAuctionContract } from '@0x/abi-gen-wrappers'; +import { DutchAuction } from '@0x/contract-artifacts'; +import { schemas } from '@0x/json-schemas'; +import { assetDataUtils } from '@0x/order-utils'; +import { DutchAuctionDetails, SignedOrder } from '@0x/types'; +import { BigNumber } from '@0x/utils'; +import { Web3Wrapper } from '@0x/web3-wrapper'; +import { ContractAbi } from 'ethereum-types'; +import * as ethAbi from 'ethereumjs-abi'; +import * as ethUtil from 'ethereumjs-util'; +import * as _ from 'lodash'; + +import { orderTxOptsSchema } from '../schemas/order_tx_opts_schema'; +import { txOptsSchema } from '../schemas/tx_opts_schema'; +import { DutchAuctionData, DutchAuctionWrapperError, OrderTransactionOpts } from '../types'; +import { assert } from '../utils/assert'; +import { _getDefaultContractAddresses } from '../utils/contract_addresses'; + +import { ContractWrapper } from './contract_wrapper'; + +export class DutchAuctionWrapper extends ContractWrapper { + public abi: ContractAbi = DutchAuction.compilerOutput.abi; + public address: string; + private _dutchAuctionContractIfExists?: DutchAuctionContract; + /** + * Dutch auction details are encoded with the asset data for a 0x order. This function produces a hex + * encoded assetData string, containing information both about the asset being traded and the + * dutch auction; which is usable in the makerAssetData or takerAssetData fields in a 0x order. + * @param assetData Hex encoded assetData string for the asset being auctioned. + * @param beginTimeSeconds Begin time of the dutch auction. + * @param beginAmount Starting amount being sold in the dutch auction. + * @return The hex encoded assetData string. + */ + public static encodeDutchAuctionAssetData( + assetData: string, + beginTimeSeconds: BigNumber, + beginAmount: BigNumber, + ): string { + const assetDataBuffer = ethUtil.toBuffer(assetData); + const abiEncodedAuctionData = (ethAbi as any).rawEncode( + ['uint256', 'uint256'], + [beginTimeSeconds.toString(), beginAmount.toString()], + ); + const abiEncodedAuctionDataBuffer = ethUtil.toBuffer(abiEncodedAuctionData); + const dutchAuctionDataBuffer = Buffer.concat([assetDataBuffer, abiEncodedAuctionDataBuffer]); + const dutchAuctionData = ethUtil.bufferToHex(dutchAuctionDataBuffer); + return dutchAuctionData; + } + /** + * Dutch auction details are encoded with the asset data for a 0x order. This function decodes a hex + * encoded assetData string, containing information both about the asset being traded and the + * dutch auction. + * @param dutchAuctionData Hex encoded assetData string for the asset being auctioned. + * @return An object containing the auction asset, auction begin time and auction begin amount. + */ + public static decodeDutchAuctionData(dutchAuctionData: string): DutchAuctionData { + const dutchAuctionDataBuffer = ethUtil.toBuffer(dutchAuctionData); + // Decode asset data + const dutchAuctionDataLengthInBytes = 64; + const assetDataBuffer = dutchAuctionDataBuffer.slice( + 0, + dutchAuctionDataBuffer.byteLength - dutchAuctionDataLengthInBytes, + ); + const assetDataHex = ethUtil.bufferToHex(assetDataBuffer); + const assetData = assetDataUtils.decodeAssetDataOrThrow(assetDataHex); + // Decode auction details + const dutchAuctionDetailsBuffer = dutchAuctionDataBuffer.slice( + dutchAuctionDataBuffer.byteLength - dutchAuctionDataLengthInBytes, + ); + const [beginTimeSecondsAsBN, beginAmountAsBN] = ethAbi.rawDecode( + ['uint256', 'uint256'], + dutchAuctionDetailsBuffer, + ); + const beginTimeSeconds = new BigNumber(`0x${beginTimeSecondsAsBN.toString()}`); + const beginAmount = new BigNumber(`0x${beginAmountAsBN.toString()}`); + return { + assetData, + beginTimeSeconds, + beginAmount, + }; + } + /** + * Instantiate DutchAuctionWrapper + * @param web3Wrapper Web3Wrapper instance to use. + * @param networkId Desired networkId. + * @param address The address of the Dutch Auction contract. If undefined, will + * default to the known address corresponding to the networkId. + */ + public constructor(web3Wrapper: Web3Wrapper, networkId: number, address?: string) { + super(web3Wrapper, networkId); + this.address = _.isUndefined(address) ? _getDefaultContractAddresses(networkId).dutchAuction : address; + } + /** + * Matches the buy and sell orders at an amount given the following: the current block time, the auction + * start time and the auction begin amount. The sell order is a an order at the lowest amount + * at the end of the auction. Excess from the match is transferred to the seller. + * Over time the price moves from beginAmount to endAmount given the current block.timestamp. + * @param buyOrder The Buyer's order. This order is for the current expected price of the auction. + * @param sellOrder The Seller's order. This order is for the lowest amount (at the end of the auction). + * @param takerAddress The user Ethereum address who would like to fill this order. Must be available via the supplied + * Provider provided at instantiation. + * @return Transaction hash. + */ + public async matchOrdersAsync( + buyOrder: SignedOrder, + sellOrder: SignedOrder, + takerAddress: string, + orderTransactionOpts: OrderTransactionOpts = { shouldValidate: true }, + ): Promise<string> { + // type assertions + assert.doesConformToSchema('buyOrder', buyOrder, schemas.signedOrderSchema); + assert.doesConformToSchema('sellOrder', sellOrder, schemas.signedOrderSchema); + await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); + assert.doesConformToSchema('orderTransactionOpts', orderTransactionOpts, orderTxOptsSchema, [txOptsSchema]); + const normalizedTakerAddress = takerAddress.toLowerCase(); + // other assertions + if ( + sellOrder.makerAssetData !== buyOrder.takerAssetData || + sellOrder.takerAssetData !== buyOrder.makerAssetData + ) { + throw new Error(DutchAuctionWrapperError.AssetDataMismatch); + } + // get contract + const dutchAuctionInstance = await this._getDutchAuctionContractAsync(); + // validate transaction + if (orderTransactionOpts.shouldValidate) { + await dutchAuctionInstance.matchOrders.callAsync( + buyOrder, + sellOrder, + buyOrder.signature, + sellOrder.signature, + { + from: normalizedTakerAddress, + gas: orderTransactionOpts.gasLimit, + gasPrice: orderTransactionOpts.gasPrice, + nonce: orderTransactionOpts.nonce, + }, + ); + } + // send transaction + const txHash = await dutchAuctionInstance.matchOrders.sendTransactionAsync( + buyOrder, + sellOrder, + buyOrder.signature, + sellOrder.signature, + { + from: normalizedTakerAddress, + gas: orderTransactionOpts.gasLimit, + gasPrice: orderTransactionOpts.gasPrice, + nonce: orderTransactionOpts.nonce, + }, + ); + return txHash; + } + /** + * Fetches the Auction Details for the given order + * @param sellOrder The Seller's order. This order is for the lowest amount (at the end of the auction). + * @return The dutch auction details. + */ + public async getAuctionDetailsAsync(sellOrder: SignedOrder): Promise<DutchAuctionDetails> { + // type assertions + assert.doesConformToSchema('sellOrder', sellOrder, schemas.signedOrderSchema); + // get contract + const dutchAuctionInstance = await this._getDutchAuctionContractAsync(); + // call contract + const auctionDetails = await dutchAuctionInstance.getAuctionDetails.callAsync(sellOrder); + return auctionDetails; + } + private async _getDutchAuctionContractAsync(): Promise<DutchAuctionContract> { + if (!_.isUndefined(this._dutchAuctionContractIfExists)) { + return this._dutchAuctionContractIfExists; + } + const contractInstance = new DutchAuctionContract( + this.abi, + this.address, + this._web3Wrapper.getProvider(), + this._web3Wrapper.getContractDefaults(), + ); + this._dutchAuctionContractIfExists = contractInstance; + return this._dutchAuctionContractIfExists; + } +} diff --git a/packages/contract-wrappers/src/fetchers/asset_balance_and_proxy_allowance_fetcher.ts b/packages/contract-wrappers/src/fetchers/asset_balance_and_proxy_allowance_fetcher.ts index d10cffe57..1ff130a48 100644 --- a/packages/contract-wrappers/src/fetchers/asset_balance_and_proxy_allowance_fetcher.ts +++ b/packages/contract-wrappers/src/fetchers/asset_balance_and_proxy_allowance_fetcher.ts @@ -1,8 +1,7 @@ -// tslint:disable:no-unnecessary-type-assertion import { AbstractBalanceAndProxyAllowanceFetcher, assetDataUtils } from '@0x/order-utils'; -import { AssetProxyId, ERC20AssetData, ERC721AssetData } from '@0x/types'; import { BigNumber } from '@0x/utils'; import { BlockParamLiteral } from 'ethereum-types'; +import * as _ from 'lodash'; import { ERC20TokenWrapper } from '../contract_wrappers/erc20_token_wrapper'; import { ERC721TokenWrapper } from '../contract_wrappers/erc721_token_wrapper'; @@ -18,42 +17,45 @@ export class AssetBalanceAndProxyAllowanceFetcher implements AbstractBalanceAndP } public async getBalanceAsync(assetData: string, userAddress: string): Promise<BigNumber> { const decodedAssetData = assetDataUtils.decodeAssetDataOrThrow(assetData); - if (decodedAssetData.assetProxyId === AssetProxyId.ERC20) { - const decodedERC20AssetData = decodedAssetData as ERC20AssetData; - const balance = await this._erc20Token.getBalanceAsync(decodedERC20AssetData.tokenAddress, userAddress, { + let balance: BigNumber | undefined; + if (assetDataUtils.isERC20AssetData(decodedAssetData)) { + balance = await this._erc20Token.getBalanceAsync(decodedAssetData.tokenAddress, userAddress, { defaultBlock: this._stateLayer, }); - return balance; - } else { - const decodedERC721AssetData = decodedAssetData as ERC721AssetData; + } else if (assetDataUtils.isERC721AssetData(decodedAssetData)) { const tokenOwner = await this._erc721Token.getOwnerOfAsync( - decodedERC721AssetData.tokenAddress, - decodedERC721AssetData.tokenId, + decodedAssetData.tokenAddress, + decodedAssetData.tokenId, { defaultBlock: this._stateLayer, }, ); - const balance = tokenOwner === userAddress ? new BigNumber(1) : new BigNumber(0); - return balance; + balance = tokenOwner === userAddress ? new BigNumber(1) : new BigNumber(0); + } else if (assetDataUtils.isMultiAssetData(decodedAssetData)) { + // The `balance` for MultiAssetData is the total units of the entire `assetData` that are held by the `userAddress`. + for (const [index, nestedAssetDataElement] of decodedAssetData.nestedAssetData.entries()) { + const nestedAmountElement = decodedAssetData.amounts[index]; + const nestedAssetBalance = (await this.getBalanceAsync( + nestedAssetDataElement, + userAddress, + )).dividedToIntegerBy(nestedAmountElement); + if (_.isUndefined(balance) || nestedAssetBalance.lessThan(balance)) { + balance = nestedAssetBalance; + } + } } + return balance as BigNumber; } public async getProxyAllowanceAsync(assetData: string, userAddress: string): Promise<BigNumber> { const decodedAssetData = assetDataUtils.decodeAssetDataOrThrow(assetData); - if (decodedAssetData.assetProxyId === AssetProxyId.ERC20) { - const decodedERC20AssetData = decodedAssetData as ERC20AssetData; - const proxyAllowance = await this._erc20Token.getProxyAllowanceAsync( - decodedERC20AssetData.tokenAddress, - userAddress, - { - defaultBlock: this._stateLayer, - }, - ); - return proxyAllowance; - } else { - const decodedERC721AssetData = decodedAssetData as ERC721AssetData; - + let proxyAllowance: BigNumber | undefined; + if (assetDataUtils.isERC20AssetData(decodedAssetData)) { + proxyAllowance = await this._erc20Token.getProxyAllowanceAsync(decodedAssetData.tokenAddress, userAddress, { + defaultBlock: this._stateLayer, + }); + } else if (assetDataUtils.isERC721AssetData(decodedAssetData)) { const isApprovedForAll = await this._erc721Token.isProxyApprovedForAllAsync( - decodedERC721AssetData.tokenAddress, + decodedAssetData.tokenAddress, userAddress, { defaultBlock: this._stateLayer, @@ -63,15 +65,27 @@ export class AssetBalanceAndProxyAllowanceFetcher implements AbstractBalanceAndP return new BigNumber(this._erc20Token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS); } else { const isApproved = await this._erc721Token.isProxyApprovedAsync( - decodedERC721AssetData.tokenAddress, - decodedERC721AssetData.tokenId, + decodedAssetData.tokenAddress, + decodedAssetData.tokenId, { defaultBlock: this._stateLayer, }, ); - const proxyAllowance = isApproved ? new BigNumber(1) : new BigNumber(0); - return proxyAllowance; + proxyAllowance = isApproved ? new BigNumber(1) : new BigNumber(0); + } + } else if (assetDataUtils.isMultiAssetData(decodedAssetData)) { + // The `proxyAllowance` for MultiAssetData is the total units of the entire `assetData` that the proxies have been approved to spend by the `userAddress`. + for (const [index, nestedAssetDataElement] of decodedAssetData.nestedAssetData.entries()) { + const nestedAmountElement = decodedAssetData.amounts[index]; + const nestedAssetAllowance = (await this.getProxyAllowanceAsync( + nestedAssetDataElement, + userAddress, + )).dividedToIntegerBy(nestedAmountElement); + if (_.isUndefined(proxyAllowance) || nestedAssetAllowance.lessThan(proxyAllowance)) { + proxyAllowance = nestedAssetAllowance; + } } } + return proxyAllowance as BigNumber; } } diff --git a/packages/contract-wrappers/src/index.ts b/packages/contract-wrappers/src/index.ts index d66ff5c9c..69bbe3c91 100644 --- a/packages/contract-wrappers/src/index.ts +++ b/packages/contract-wrappers/src/index.ts @@ -34,6 +34,7 @@ export { ERC20ProxyWrapper } from './contract_wrappers/erc20_proxy_wrapper'; export { ERC721ProxyWrapper } from './contract_wrappers/erc721_proxy_wrapper'; export { ForwarderWrapper } from './contract_wrappers/forwarder_wrapper'; export { OrderValidatorWrapper } from './contract_wrappers/order_validator_wrapper'; +export { DutchAuctionWrapper } from './contract_wrappers/dutch_auction_wrapper'; export { TransactionEncoder } from './utils/transaction_encoder'; @@ -54,9 +55,21 @@ export { OrderAndTraderInfo, TraderInfo, ValidateOrderFillableOpts, + DutchAuctionData, } from './types'; -export { Order, SignedOrder, AssetProxyId } from '@0x/types'; +export { + AssetData, + ERC20AssetData, + ERC721AssetData, + SingleAssetData, + MultiAssetData, + MultiAssetDataWithRecursiveDecoding, + DutchAuctionDetails, + Order, + SignedOrder, + AssetProxyId, +} from '@0x/types'; export { BlockParamLiteral, diff --git a/packages/contract-wrappers/src/types.ts b/packages/contract-wrappers/src/types.ts index 14d4649ae..945ca88cd 100644 --- a/packages/contract-wrappers/src/types.ts +++ b/packages/contract-wrappers/src/types.ts @@ -9,7 +9,7 @@ import { WETH9Events, } from '@0x/abi-gen-wrappers'; import { ContractAddresses } from '@0x/contract-addresses'; -import { OrderState, SignedOrder } from '@0x/types'; +import { AssetData, OrderState, SignedOrder } from '@0x/types'; import { BigNumber } from '@0x/utils'; import { BlockParam, ContractEventArg, DecodedLogArgs, LogEntryEvent, LogWithDecodedArgs } from 'ethereum-types'; @@ -206,3 +206,13 @@ export interface BalanceAndAllowance { balance: BigNumber; allowance: BigNumber; } + +export enum DutchAuctionWrapperError { + AssetDataMismatch = 'ASSET_DATA_MISMATCH', +} + +export interface DutchAuctionData { + assetData: AssetData; + beginTimeSeconds: BigNumber; + beginAmount: BigNumber; +} diff --git a/packages/contract-wrappers/src/utils/constants.ts b/packages/contract-wrappers/src/utils/constants.ts index c587ba526..94afdc112 100644 --- a/packages/contract-wrappers/src/utils/constants.ts +++ b/packages/contract-wrappers/src/utils/constants.ts @@ -14,5 +14,6 @@ export const constants = { ZERO_AMOUNT: new BigNumber(0), ONE_AMOUNT: new BigNumber(1), ETHER_TOKEN_DECIMALS: 18, - USER_DENIED_SIGNATURE_PATTERN: 'User denied transaction signature', + METAMASK_USER_DENIED_SIGNATURE_PATTERN: 'User denied transaction signature', + TRUST_WALLET_USER_DENIED_SIGNATURE_PATTERN: 'cancelled', }; diff --git a/packages/contract-wrappers/src/utils/decorators.ts b/packages/contract-wrappers/src/utils/decorators.ts index a4207ae4c..3acfa3a88 100644 --- a/packages/contract-wrappers/src/utils/decorators.ts +++ b/packages/contract-wrappers/src/utils/decorators.ts @@ -30,7 +30,10 @@ const schemaErrorTransformer = (error: Error) => { }; const signatureRequestErrorTransformer = (error: Error) => { - if (_.includes(error.message, constants.USER_DENIED_SIGNATURE_PATTERN)) { + if ( + _.includes(error.message, constants.METAMASK_USER_DENIED_SIGNATURE_PATTERN) || + _.includes(error.message, constants.TRUST_WALLET_USER_DENIED_SIGNATURE_PATTERN) + ) { const errMsg = ContractWrappersError.SignatureRequestDenied; return new Error(errMsg); } |