aboutsummaryrefslogtreecommitdiffstats
path: root/packages/contract-wrappers
diff options
context:
space:
mode:
Diffstat (limited to 'packages/contract-wrappers')
-rw-r--r--packages/contract-wrappers/src/contract_wrappers.ts10
-rw-r--r--packages/contract-wrappers/src/contract_wrappers/dutch_auction_wrapper.ts151
-rw-r--r--packages/contract-wrappers/src/index.ts1
-rw-r--r--packages/contract-wrappers/test/dutch_auction_wrapper_test.ts156
4 files changed, 318 insertions, 0 deletions
diff --git a/packages/contract-wrappers/src/contract_wrappers.ts b/packages/contract-wrappers/src/contract_wrappers.ts
index 0c535bd5c..396505866 100644
--- a/packages/contract-wrappers/src/contract_wrappers.ts
+++ b/packages/contract-wrappers/src/contract_wrappers.ts
@@ -20,6 +20,7 @@ import { EtherTokenWrapper } from './contract_wrappers/ether_token_wrapper';
import { ExchangeWrapper } from './contract_wrappers/exchange_wrapper';
import { ForwarderWrapper } from './contract_wrappers/forwarder_wrapper';
import { OrderValidatorWrapper } from './contract_wrappers/order_validator_wrapper';
+import { DutchAuctionWrapper } from './contract_wrappers/dutch_auction_wrapper';
import { ContractWrappersConfigSchema } from './schemas/contract_wrappers_config_schema';
import { ContractWrappersConfig } from './types';
import { assert } from './utils/assert';
@@ -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.orderValidator,
+ );
}
/**
* 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..500e7a63d
--- /dev/null
+++ b/packages/contract-wrappers/src/contract_wrappers/dutch_auction_wrapper.ts
@@ -0,0 +1,151 @@
+import { artifacts as protocolArtifacts } from '@0x/contracts-protocol';
+import { DutchAuctionContract } from '@0x/abi-gen-wrappers';
+import { DutchAuction } from '@0x/contract-artifacts';
+import { LogDecoder } from '@0x/contracts-test-utils';
+import { artifacts as tokensArtifacts } from '@0x/contracts-tokens';
+import { _getDefaultContractAddresses } from '../utils/contract_addresses';
+import { DutchAuctionDetails, SignedOrder } from '@0x/types';
+import { ContractAbi } from 'ethereum-types';
+import { Web3Wrapper } from '@0x/web3-wrapper';
+import { BigNumber } from '@0x/utils';
+import { Provider, TransactionReceiptWithDecodedLogs } from 'ethereum-types';
+import * as _ from 'lodash';
+import ethAbi = require('ethereumjs-abi');
+import { schemas } from '@0x/json-schemas';
+import { assert } from '../utils/assert';
+import ethUtil = require('ethereumjs-util');
+
+import { orderTxOptsSchema } from '../schemas/order_tx_opts_schema';
+import { txOptsSchema } from '../schemas/tx_opts_schema';
+import { OrderTransactionOpts } from '../types';
+import { ContractWrapper } from './contract_wrapper';
+import { ExchangeWrapperError } from '../types';
+
+export class DutchAuctionWrapper extends ContractWrapper {
+ public abi: ContractAbi = DutchAuction.compilerOutput.abi;
+ public address: string;
+ private _dutchAuctionContractIfExists?: DutchAuctionContract;
+ /**
+ * 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.
+ */
+ constructor(
+ web3Wrapper: Web3Wrapper,
+ networkId: number,
+ address: string,
+ ) {
+ super(web3Wrapper, networkId);
+ this.address = 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 from Address the transaction is being sent from.
+ * @return Transaction receipt with decoded logs.
+ */
+ 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(ExchangeWrapperError.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;
+ }
+ /**
+ * Calculates 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 afterAuctionDetails = await dutchAuctionInstance.getAuctionDetails.callAsync(sellOrder);
+ return afterAuctionDetails;
+ }
+ 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;
+ }
+ /**
+ * 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;
+ };
+}
diff --git a/packages/contract-wrappers/src/index.ts b/packages/contract-wrappers/src/index.ts
index d66ff5c9c..5c64dbbc6 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';
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..ad8b3bd31
--- /dev/null
+++ b/packages/contract-wrappers/test/dutch_auction_wrapper_test.ts
@@ -0,0 +1,156 @@
+import { BlockchainLifecycle } from '@0x/dev-utils';
+import { FillScenarios } from '@0x/fill-scenarios';
+import { assetDataUtils } from '@0x/order-utils';
+import { SignedOrder } from '@0x/types';
+import { BigNumber } from '@0x/utils';
+import * as chai from 'chai';
+import 'mocha';
+
+import { ContractWrappers, OrderStatus } from '../src';
+
+import { chaiSetup } from './utils/chai_setup';
+import { constants } from './utils/constants';
+import { migrateOnceAsync } from './utils/migrate';
+import { tokenUtils } from './utils/token_utils';
+import { provider, web3Wrapper } from './utils/web3_wrapper';
+import { getLatestBlockTimestampAsync } from '@0x/contracts-test-utils';
+import { DutchAuction } from '@0x/contract-artifacts';
+import { DutchAuctionWrapper } from '../src/contract_wrappers/dutch_auction_wrapper';
+
+chaiSetup.configure();
+const expect = chai.expect;
+const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
+
+// tslint:disable:custom-no-magic-numbers
+describe.only('DutchAuctionWrapper', () => {
+ const fillableAmount = new BigNumber(5);
+ const tenMinutesInSeconds = 10 * 60;
+ let contractWrappers: ContractWrappers;
+ let fillScenarios: FillScenarios;
+ let exchangeContractAddress: string;
+ let zrxTokenAddress: string;
+ let userAddresses: string[];
+ let makerAddress: string;
+ let takerAddress: string;
+ let makerTokenAddress: string;
+ let takerTokenAddress: string;
+ let makerAssetData: string;
+ let takerAssetData: string;
+ let buyOrder: SignedOrder;
+ let sellOrder: SignedOrder;
+ let makerTokenAssetData: string;
+ let takerTokenAssetData: string;
+ before(async () => {
+ console.log(`BEOGIN DEPLOYINH`);
+ const contractAddresses = await migrateOnceAsync();
+ await blockchainLifecycle.startAsync();
+ const config = {
+ networkId: constants.TESTRPC_NETWORK_ID,
+ contractAddresses,
+ blockPollingIntervalMs: 10,
+ };
+
+ contractWrappers = new ContractWrappers(provider, config);
+ console.log(`DEPLOYINH`);
+ exchangeContractAddress = contractWrappers.exchange.address;
+ userAddresses = await web3Wrapper.getAvailableAddressesAsync();
+ zrxTokenAddress = contractWrappers.exchange.zrxTokenAddress;
+ fillScenarios = new FillScenarios(
+ provider,
+ userAddresses,
+ zrxTokenAddress,
+ exchangeContractAddress,
+ contractWrappers.erc20Proxy.address,
+ contractWrappers.erc721Proxy.address,
+ );
+ [, makerAddress, takerAddress] = userAddresses;
+ [makerTokenAddress] = tokenUtils.getDummyERC20TokenAddresses();
+ takerTokenAddress = contractWrappers.forwarder.etherTokenAddress;
+ // construct asset data for tokens being swapped
+ [makerTokenAssetData, takerTokenAssetData] = [
+ assetDataUtils.encodeERC20AssetData(makerTokenAddress),
+ assetDataUtils.encodeERC20AssetData(takerTokenAddress),
+ ];
+ // encode auction details in maker asset data
+ const auctionBeginAmount = fillableAmount;
+ const currentBlockTimestamp = await getLatestBlockTimestampAsync();
+ const auctionBeginTimeSeconds = new BigNumber(currentBlockTimestamp - tenMinutesInSeconds);
+ makerAssetData = DutchAuctionWrapper.encodeDutchAuctionAssetData(
+ makerTokenAssetData,
+ auctionBeginTimeSeconds,
+ auctionBeginAmount
+ );
+ takerAssetData = takerTokenAssetData;
+ // create sell / buy orders for auction
+ // note that the maker/taker asset datas are swapped in the `buyOrder`
+ sellOrder = await fillScenarios.createFillableSignedOrderAsync(
+ makerAssetData,
+ takerAssetData,
+ makerAddress,
+ constants.NULL_ADDRESS,
+ fillableAmount,
+ );
+ buyOrder = await fillScenarios.createFillableSignedOrderAsync(
+ takerAssetData,
+ makerAssetData,
+ makerAddress,
+ constants.NULL_ADDRESS,
+ fillableAmount,
+ );
+ });
+ 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 expect(
+ await contractWrappers.dutchAuction.matchOrdersAsync(
+ badBuyOrder,
+ badSellOrder,
+ takerAddress,
+ {
+ shouldValidate: true,
+ },
+ ),
+ ).to.be.rejectedWith('COMPLETE_FILL_FAILED');
+ });
+ });
+
+ describe('#getAuctionDetailsAsync', () => {
+ it('should be worth the begin price at the begining of the auction', async () => {
+ // setup auction details
+ const auctionBeginAmount = fillableAmount;
+ const currentBlockTimestamp = await getLatestBlockTimestampAsync();
+ const auctionBeginTimeSeconds = new BigNumber(currentBlockTimestamp + tenMinutesInSeconds);
+ const makerAssetData = DutchAuctionWrapper.encodeDutchAuctionAssetData(
+ makerTokenAssetData,
+ auctionBeginTimeSeconds,
+ auctionBeginAmount
+ );
+ const order = await fillScenarios.createFillableSignedOrderAsync(
+ makerAssetData,
+ takerAssetData,
+ makerAddress,
+ constants.NULL_ADDRESS,
+ fillableAmount,
+ );
+ const auctionDetails = await contractWrappers.dutchAuction.getAuctionDetailsAsync(order);
+ expect(auctionDetails.currentTimeSeconds).to.be.bignumber.lte(auctionBeginTimeSeconds);
+ expect(auctionDetails.currentAmount).to.be.bignumber.equal(auctionBeginAmount);
+ expect(auctionDetails.beginAmount).to.be.bignumber.equal(auctionBeginAmount);
+ });
+ });
+});