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 { // 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 { 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 { 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 { 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 { 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); } }