1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
|
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 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 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 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(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;
}
/**
* 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;
}
}
|