aboutsummaryrefslogtreecommitdiffstats
path: root/packages/contract-wrappers
diff options
context:
space:
mode:
authorFabio Berger <me@fabioberger.com>2019-01-08 21:30:38 +0800
committerFabio Berger <me@fabioberger.com>2019-01-08 21:30:38 +0800
commit1631031fa74894143cb6835030b7dcd44d7c3c6b (patch)
tree06dea01cc64fb42905a5f95c95f4b3e16ecfe744 /packages/contract-wrappers
parent0bcb81d3a918fbcf71d68f42fa661d884d5d74cf (diff)
parent0ac36cef288deecd36caa601c53d13517eef5ca8 (diff)
downloaddexon-sol-tools-1631031fa74894143cb6835030b7dcd44d7c3c6b.tar
dexon-sol-tools-1631031fa74894143cb6835030b7dcd44d7c3c6b.tar.gz
dexon-sol-tools-1631031fa74894143cb6835030b7dcd44d7c3c6b.tar.bz2
dexon-sol-tools-1631031fa74894143cb6835030b7dcd44d7c3c6b.tar.lz
dexon-sol-tools-1631031fa74894143cb6835030b7dcd44d7c3c6b.tar.xz
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')
-rw-r--r--packages/contract-wrappers/CHANGELOG.json21
-rw-r--r--packages/contract-wrappers/package.json2
-rw-r--r--packages/contract-wrappers/src/contract_wrappers.ts10
-rw-r--r--packages/contract-wrappers/src/contract_wrappers/dutch_auction_wrapper.ts182
-rw-r--r--packages/contract-wrappers/src/fetchers/asset_balance_and_proxy_allowance_fetcher.ts74
-rw-r--r--packages/contract-wrappers/src/index.ts15
-rw-r--r--packages/contract-wrappers/src/types.ts12
-rw-r--r--packages/contract-wrappers/src/utils/constants.ts3
-rw-r--r--packages/contract-wrappers/src/utils/decorators.ts5
-rw-r--r--packages/contract-wrappers/test/dutch_auction_wrapper_test.ts128
-rw-r--r--packages/contract-wrappers/test/utils/dutch_auction_utils.ts153
11 files changed, 571 insertions, 34 deletions
diff --git a/packages/contract-wrappers/CHANGELOG.json b/packages/contract-wrappers/CHANGELOG.json
index 9475e5a88..677292030 100644
--- a/packages/contract-wrappers/CHANGELOG.json
+++ b/packages/contract-wrappers/CHANGELOG.json
@@ -1,5 +1,26 @@
[
{
+ "version": "4.2.0",
+ "changes": [
+ {
+ "note": "Added Dutch Auction wrapper",
+ "pr": 1465
+ }
+ ]
+ },
+ {
+ "version": "4.1.4",
+ "changes": [
+ {
+ "note": "Add support for Trust Wallet signature denial error"
+ },
+ {
+ "note": "Add balance and allowance queries for `MultiAssetProxy`",
+ "pr": 1363
+ }
+ ]
+ },
+ {
"version": "4.1.3",
"changes": [
{
diff --git a/packages/contract-wrappers/package.json b/packages/contract-wrappers/package.json
index f3f7301fd..e4de75ab2 100644
--- a/packages/contract-wrappers/package.json
+++ b/packages/contract-wrappers/package.json
@@ -69,6 +69,7 @@
"@0x/assert": "^1.0.20",
"@0x/contract-addresses": "^2.0.0",
"@0x/contract-artifacts": "^1.1.2",
+ "@0x/contracts-test-utils": "^1.0.2",
"@0x/fill-scenarios": "^1.0.16",
"@0x/json-schemas": "^2.1.4",
"@0x/order-utils": "^3.0.7",
@@ -76,6 +77,7 @@
"@0x/typescript-typings": "^3.0.6",
"@0x/utils": "^2.0.8",
"@0x/web3-wrapper": "^3.2.1",
+ "ethereumjs-abi": "0.6.5",
"ethereum-types": "^1.1.4",
"ethereumjs-blockstream": "6.0.0",
"ethereumjs-util": "^5.1.1",
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);
}
diff --git a/packages/contract-wrappers/test/dutch_auction_wrapper_test.ts b/packages/contract-wrappers/test/dutch_auction_wrapper_test.ts
new file mode 100644
index 000000000..d7a6ca015
--- /dev/null
+++ b/packages/contract-wrappers/test/dutch_auction_wrapper_test.ts
@@ -0,0 +1,128 @@
+import { expectTransactionFailedAsync, getLatestBlockTimestampAsync } from '@0x/contracts-test-utils';
+import { BlockchainLifecycle } from '@0x/dev-utils';
+import { assetDataUtils } from '@0x/order-utils';
+import { RevertReason, SignedOrder } from '@0x/types';
+import { BigNumber } from '@0x/utils';
+import * as chai from 'chai';
+import 'mocha';
+
+import { ContractWrappers } from '../src';
+
+import { chaiSetup } from './utils/chai_setup';
+import { constants } from './utils/constants';
+import { DutchAuctionUtils } from './utils/dutch_auction_utils';
+import { migrateOnceAsync } from './utils/migrate';
+import { tokenUtils } from './utils/token_utils';
+import { provider, web3Wrapper } from './utils/web3_wrapper';
+
+chaiSetup.configure();
+const expect = chai.expect;
+const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
+
+// tslint:disable:custom-no-magic-numbers
+describe('DutchAuctionWrapper', () => {
+ const makerAssetAmount = new BigNumber(5);
+ const auctionEndTakerAmount = new BigNumber(10);
+ const auctionBeginTakerAmount = auctionEndTakerAmount.times(2);
+ const tenMinutesInSeconds = 10 * 60;
+ let contractWrappers: ContractWrappers;
+ let exchangeContractAddress: string;
+ let userAddresses: string[];
+ let makerAddress: string;
+ let takerAddress: string;
+ let makerTokenAddress: string;
+ let takerTokenAddress: string;
+ let buyOrder: SignedOrder;
+ let sellOrder: SignedOrder;
+ let makerTokenAssetData: string;
+ let takerTokenAssetData: string;
+ let auctionBeginTimeSeconds: BigNumber;
+ let auctionEndTimeSeconds: BigNumber;
+ before(async () => {
+ // setup contract wrappers & addresses
+ const contractAddresses = await migrateOnceAsync();
+ await blockchainLifecycle.startAsync();
+ const config = {
+ networkId: constants.TESTRPC_NETWORK_ID,
+ contractAddresses,
+ blockPollingIntervalMs: 10,
+ };
+ contractWrappers = new ContractWrappers(provider, config);
+ exchangeContractAddress = contractWrappers.exchange.address;
+ userAddresses = await web3Wrapper.getAvailableAddressesAsync();
+ [, makerAddress, takerAddress] = userAddresses;
+ [makerTokenAddress, takerTokenAddress] = tokenUtils.getDummyERC20TokenAddresses();
+ // construct asset data for tokens being swapped
+ [makerTokenAssetData, takerTokenAssetData] = [
+ assetDataUtils.encodeERC20AssetData(makerTokenAddress),
+ assetDataUtils.encodeERC20AssetData(takerTokenAddress),
+ ];
+ // setup auction details in maker asset data
+ const currentBlockTimestamp: number = await getLatestBlockTimestampAsync();
+ auctionBeginTimeSeconds = new BigNumber(currentBlockTimestamp - tenMinutesInSeconds);
+ auctionEndTimeSeconds = new BigNumber(currentBlockTimestamp + tenMinutesInSeconds);
+ // create auction orders
+ const coinbase = userAddresses[0];
+ const dutchAuctionUtils = new DutchAuctionUtils(
+ web3Wrapper,
+ coinbase,
+ exchangeContractAddress,
+ contractWrappers.erc20Proxy.address,
+ );
+ sellOrder = await dutchAuctionUtils.createSignedSellOrderAsync(
+ auctionBeginTimeSeconds,
+ auctionEndTimeSeconds,
+ auctionBeginTakerAmount,
+ auctionEndTakerAmount,
+ makerAssetAmount,
+ makerTokenAssetData,
+ takerTokenAssetData,
+ makerAddress,
+ constants.NULL_ADDRESS,
+ );
+ buyOrder = await dutchAuctionUtils.createSignedBuyOrderAsync(sellOrder, takerAddress);
+ });
+ after(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
+ beforeEach(async () => {
+ await blockchainLifecycle.startAsync();
+ });
+ afterEach(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
+ describe('#matchOrdersAsync', () => {
+ it('should match two orders', async () => {
+ const txHash = await contractWrappers.dutchAuction.matchOrdersAsync(buyOrder, sellOrder, takerAddress);
+ await web3Wrapper.awaitTransactionSuccessAsync(txHash, constants.AWAIT_TRANSACTION_MINED_MS);
+ });
+ it('should throw when invalid transaction and shouldValidate is true', async () => {
+ // request match with bad buy/sell orders
+ const badSellOrder = buyOrder;
+ const badBuyOrder = sellOrder;
+ return expectTransactionFailedAsync(
+ contractWrappers.dutchAuction.matchOrdersAsync(badBuyOrder, badSellOrder, takerAddress, {
+ shouldValidate: true,
+ }),
+ RevertReason.InvalidAssetData,
+ );
+ });
+ });
+
+ describe('#getAuctionDetailsAsync', () => {
+ it('should get auction details', async () => {
+ // get auction details
+ const auctionDetails = await contractWrappers.dutchAuction.getAuctionDetailsAsync(sellOrder);
+ // run some basic sanity checks on the return value
+ expect(auctionDetails.beginTimeSeconds, 'auctionDetails.beginTimeSeconds').to.be.bignumber.equal(
+ auctionBeginTimeSeconds,
+ );
+ expect(auctionDetails.beginAmount, 'auctionDetails.beginAmount').to.be.bignumber.equal(
+ auctionBeginTakerAmount,
+ );
+ expect(auctionDetails.endTimeSeconds, 'auctionDetails.endTimeSeconds').to.be.bignumber.equal(
+ auctionEndTimeSeconds,
+ );
+ });
+ });
+});
diff --git a/packages/contract-wrappers/test/utils/dutch_auction_utils.ts b/packages/contract-wrappers/test/utils/dutch_auction_utils.ts
new file mode 100644
index 000000000..8e2aef217
--- /dev/null
+++ b/packages/contract-wrappers/test/utils/dutch_auction_utils.ts
@@ -0,0 +1,153 @@
+import { DummyERC20TokenContract } from '@0x/abi-gen-wrappers';
+import * as artifacts from '@0x/contract-artifacts';
+import { assetDataUtils } from '@0x/order-utils';
+import { orderFactory } from '@0x/order-utils/lib/src/order_factory';
+import { SignedOrder } from '@0x/types';
+import { BigNumber } from '@0x/utils';
+import { Web3Wrapper } from '@0x/web3-wrapper';
+
+import { DutchAuctionWrapper } from '../../src/contract_wrappers/dutch_auction_wrapper';
+
+import { constants } from './constants';
+
+export class DutchAuctionUtils {
+ private readonly _web3Wrapper: Web3Wrapper;
+ private readonly _coinbase: string;
+ private readonly _exchangeAddress: string;
+ private readonly _erc20ProxyAddress: string;
+
+ constructor(web3Wrapper: Web3Wrapper, coinbase: string, exchangeAddress: string, erc20ProxyAddress: string) {
+ this._web3Wrapper = web3Wrapper;
+ this._coinbase = coinbase;
+ this._exchangeAddress = exchangeAddress;
+ this._erc20ProxyAddress = erc20ProxyAddress;
+ }
+ public async createSignedSellOrderAsync(
+ auctionBeginTimeSections: BigNumber,
+ acutionEndTimeSeconds: BigNumber,
+ auctionBeginTakerAssetAmount: BigNumber,
+ auctionEndTakerAssetAmount: BigNumber,
+ makerAssetAmount: BigNumber,
+ makerAssetData: string,
+ takerAssetData: string,
+ makerAddress: string,
+ takerAddress: string,
+ senderAddress?: string,
+ makerFee?: BigNumber,
+ takerFee?: BigNumber,
+ feeRecipientAddress?: string,
+ ): Promise<SignedOrder> {
+ // Notes on sell order:
+ // - The `takerAssetAmount` is set to the `auctionEndTakerAssetAmount`, which is the lowest amount the
+ // the seller can expect to receive
+ // - The `makerAssetData` is overloaded to include the auction begin time and begin taker asset amount
+ const makerAssetDataWithAuctionDetails = DutchAuctionWrapper.encodeDutchAuctionAssetData(
+ makerAssetData,
+ auctionBeginTimeSections,
+ auctionBeginTakerAssetAmount,
+ );
+ const signedOrder = await orderFactory.createSignedOrderAsync(
+ this._web3Wrapper.getProvider(),
+ makerAddress,
+ makerAssetAmount,
+ makerAssetDataWithAuctionDetails,
+ auctionEndTakerAssetAmount,
+ takerAssetData,
+ this._exchangeAddress,
+ {
+ takerAddress,
+ senderAddress,
+ makerFee,
+ takerFee,
+ feeRecipientAddress,
+ expirationTimeSeconds: acutionEndTimeSeconds,
+ },
+ );
+ const erc20AssetData = assetDataUtils.decodeERC20AssetData(makerAssetData);
+ await this._increaseERC20BalanceAndAllowanceAsync(erc20AssetData.tokenAddress, makerAddress, makerAssetAmount);
+ return signedOrder;
+ }
+ public async createSignedBuyOrderAsync(
+ sellOrder: SignedOrder,
+ buyerAddress: string,
+ senderAddress?: string,
+ makerFee?: BigNumber,
+ takerFee?: BigNumber,
+ feeRecipientAddress?: string,
+ expirationTimeSeconds?: BigNumber,
+ ): Promise<SignedOrder> {
+ const dutchAuctionData = DutchAuctionWrapper.decodeDutchAuctionData(sellOrder.makerAssetData);
+ // Notes on buy order:
+ // - The `makerAssetAmount` is set to `dutchAuctionData.beginAmount`, which is
+ // the highest amount the buyer would have to pay out at any point during the auction.
+ // - The `takerAssetAmount` is set to the seller's `makerAssetAmount`, as the buyer
+ // receives the entire amount being sold by the seller.
+ // - The `makerAssetData`/`takerAssetData` are reversed from the sell order
+ const signedOrder = await orderFactory.createSignedOrderAsync(
+ this._web3Wrapper.getProvider(),
+ buyerAddress,
+ dutchAuctionData.beginAmount,
+ sellOrder.takerAssetData,
+ sellOrder.makerAssetAmount,
+ sellOrder.makerAssetData,
+ sellOrder.exchangeAddress,
+ {
+ senderAddress,
+ makerFee,
+ takerFee,
+ feeRecipientAddress,
+ expirationTimeSeconds,
+ },
+ );
+ const buyerERC20AssetData = assetDataUtils.decodeERC20AssetData(sellOrder.takerAssetData);
+ await this._increaseERC20BalanceAndAllowanceAsync(
+ buyerERC20AssetData.tokenAddress,
+ buyerAddress,
+ dutchAuctionData.beginAmount,
+ );
+ return signedOrder;
+ }
+ private async _increaseERC20BalanceAndAllowanceAsync(
+ tokenAddress: string,
+ address: string,
+ amount: BigNumber,
+ ): Promise<void> {
+ if (amount.isZero() || address === constants.NULL_ADDRESS) {
+ return; // noop
+ }
+ await Promise.all([
+ this._increaseERC20BalanceAsync(tokenAddress, address, amount),
+ this._increaseERC20AllowanceAsync(tokenAddress, address, amount),
+ ]);
+ }
+ private async _increaseERC20BalanceAsync(tokenAddress: string, address: string, amount: BigNumber): Promise<void> {
+ const erc20Token = new DummyERC20TokenContract(
+ artifacts.DummyERC20Token.compilerOutput.abi,
+ tokenAddress,
+ this._web3Wrapper.getProvider(),
+ this._web3Wrapper.getContractDefaults(),
+ );
+ const txHash = await erc20Token.transfer.sendTransactionAsync(address, amount, {
+ from: this._coinbase,
+ });
+ await this._web3Wrapper.awaitTransactionSuccessAsync(txHash, constants.AWAIT_TRANSACTION_MINED_MS);
+ }
+ private async _increaseERC20AllowanceAsync(
+ tokenAddress: string,
+ address: string,
+ amount: BigNumber,
+ ): Promise<void> {
+ const erc20Token = new DummyERC20TokenContract(
+ artifacts.DummyERC20Token.compilerOutput.abi,
+ tokenAddress,
+ this._web3Wrapper.getProvider(),
+ this._web3Wrapper.getContractDefaults(),
+ );
+ const oldMakerAllowance = await erc20Token.allowance.callAsync(address, this._erc20ProxyAddress);
+ const newMakerAllowance = oldMakerAllowance.plus(amount);
+ const txHash = await erc20Token.approve.sendTransactionAsync(this._erc20ProxyAddress, newMakerAllowance, {
+ from: address,
+ });
+ await this._web3Wrapper.awaitTransactionSuccessAsync(txHash, constants.AWAIT_TRANSACTION_MINED_MS);
+ }
+}