feat: Dutch Auction
This commit is contained in:
@@ -25,6 +25,7 @@
|
||||
"DummyERC721Token",
|
||||
"DummyMultipleReturnERC20Token",
|
||||
"DummyNoReturnERC20Token",
|
||||
"DutchAuction",
|
||||
"ERC20Proxy",
|
||||
"ERC20Token",
|
||||
"ERC721Token",
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
},
|
||||
"config": {
|
||||
"abis":
|
||||
"generated-artifacts/@(AssetProxyOwner|DummyERC20Token|DummyERC721Receiver|DummyERC721Token|DummyMultipleReturnERC20Token|DummyNoReturnERC20Token|ERC20Token|ERC20Proxy|ERC721Token|ERC721Proxy|Forwarder|Exchange|ExchangeWrapper|IAssetData|IAssetProxy|InvalidERC721Receiver|MixinAuthorizable|MultiAssetProxy|MultiSigWallet|MultiSigWalletWithTimeLock|OrderValidator|ReentrantERC20Token|TestAssetProxyOwner|TestAssetProxyDispatcher|TestConstants|TestExchangeInternals|TestLibBytes|TestLibs|TestSignatureValidator|TestStaticCallReceiver|Validator|Wallet|Whitelist|WETH9|ZRXToken).json"
|
||||
"generated-artifacts/@(AssetProxyOwner|DummyERC20Token|DummyERC721Receiver|DummyERC721Token|DummyMultipleReturnERC20Token|DummyNoReturnERC20Token|DutchAuction|ERC20Token|ERC20Proxy|ERC721Token|ERC721Proxy|Forwarder|Exchange|ExchangeWrapper|IAssetData|IAssetProxy|InvalidERC721Receiver|MixinAuthorizable|MultiAssetProxy|MultiSigWallet|MultiSigWalletWithTimeLock|OrderValidator|ReentrantERC20Token|TestAssetProxyOwner|TestAssetProxyDispatcher|TestConstants|TestExchangeInternals|TestLibBytes|TestLibs|TestSignatureValidator|TestStaticCallReceiver|Validator|Wallet|Whitelist|WETH9|ZRXToken).json"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -6,6 +6,7 @@ import * as DummyERC721Receiver from '../../generated-artifacts/DummyERC721Recei
|
||||
import * as DummyERC721Token from '../../generated-artifacts/DummyERC721Token.json';
|
||||
import * as DummyMultipleReturnERC20Token from '../../generated-artifacts/DummyMultipleReturnERC20Token.json';
|
||||
import * as DummyNoReturnERC20Token from '../../generated-artifacts/DummyNoReturnERC20Token.json';
|
||||
import * as DutchAuction from '../../generated-artifacts/DutchAuction.json';
|
||||
import * as ERC20Proxy from '../../generated-artifacts/ERC20Proxy.json';
|
||||
import * as ERC20Token from '../../generated-artifacts/ERC20Token.json';
|
||||
import * as ERC721Proxy from '../../generated-artifacts/ERC721Proxy.json';
|
||||
@@ -45,6 +46,7 @@ export const artifacts = {
|
||||
DummyERC721Token: DummyERC721Token as ContractArtifact,
|
||||
DummyMultipleReturnERC20Token: DummyMultipleReturnERC20Token as ContractArtifact,
|
||||
DummyNoReturnERC20Token: DummyNoReturnERC20Token as ContractArtifact,
|
||||
DutchAuction: DutchAuction as ContractArtifact,
|
||||
ERC20Proxy: ERC20Proxy as ContractArtifact,
|
||||
ERC20Token: ERC20Token as ContractArtifact,
|
||||
ERC721Proxy: ERC721Proxy as ContractArtifact,
|
||||
|
||||
@@ -4,6 +4,7 @@ export * from '../../generated-wrappers/dummy_erc721_receiver';
|
||||
export * from '../../generated-wrappers/dummy_erc721_token';
|
||||
export * from '../../generated-wrappers/dummy_multiple_return_erc20_token';
|
||||
export * from '../../generated-wrappers/dummy_no_return_erc20_token';
|
||||
export * from '../../generated-wrappers/dutch_auction';
|
||||
export * from '../../generated-wrappers/erc20_proxy';
|
||||
export * from '../../generated-wrappers/erc721_proxy';
|
||||
export * from '../../generated-wrappers/erc20_token';
|
||||
|
||||
@@ -86,6 +86,7 @@ export enum ContractName {
|
||||
ZRXToken = 'ZRXToken',
|
||||
DummyERC20Token = 'DummyERC20Token',
|
||||
EtherToken = 'WETH9',
|
||||
DutchAuction = 'DutchAuction',
|
||||
AssetProxyOwner = 'AssetProxyOwner',
|
||||
AccountLevels = 'AccountLevels',
|
||||
EtherDelta = 'EtherDelta',
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"./generated-artifacts/DummyERC721Token.json",
|
||||
"./generated-artifacts/DummyMultipleReturnERC20Token.json",
|
||||
"./generated-artifacts/DummyNoReturnERC20Token.json",
|
||||
"./generated-artifacts/DutchAuction.json",
|
||||
"./generated-artifacts/ERC20Proxy.json",
|
||||
"./generated-artifacts/ERC20Token.json",
|
||||
"./generated-artifacts/ERC721Proxy.json",
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
|
||||
Copyright 2018 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity 0.4.24;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "../../protocol/Exchange/interfaces/IExchange.sol";
|
||||
import "../../protocol/Exchange/libs/LibOrder.sol";
|
||||
import "../../tokens/ERC20Token/IERC20Token.sol";
|
||||
import "../../utils/LibBytes/LibBytes.sol";
|
||||
|
||||
|
||||
contract DutchAuction {
|
||||
using LibBytes for bytes;
|
||||
|
||||
// solhint-disable var-name-mixedcase
|
||||
IExchange internal EXCHANGE;
|
||||
|
||||
struct AuctionDetails {
|
||||
uint256 beginTime; // Auction begin time in seconds
|
||||
uint256 endTime; // Auction end time in seconds
|
||||
uint256 beginPrice; // Auction begin price
|
||||
uint256 endPrice; // Auction end price
|
||||
uint256 currentPrice; // Current auction price at block.timestamp
|
||||
uint256 currentTime; // block.timestamp
|
||||
}
|
||||
|
||||
constructor (address _exchange)
|
||||
public
|
||||
{
|
||||
EXCHANGE = IExchange(_exchange);
|
||||
}
|
||||
|
||||
/// @dev Packs the begin time and price parameters of an auction into uint256.
|
||||
/// This is stored as the salt value of the sale order.
|
||||
/// @param beginTime Begin time of the auction (32 bits)
|
||||
/// @param beginPrice Starting price of the auction (224 bits)
|
||||
/// @return Encoded Auction Parameters packed into a uint256
|
||||
function encodeParameters(
|
||||
uint256 beginTime,
|
||||
uint256 beginPrice
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (uint256 encodedParameters)
|
||||
{
|
||||
require(beginTime <= 2**32, "INVALID_BEGIN_TIME");
|
||||
require(beginPrice <= 2**224, "INVALID_BEGIN_PRICE");
|
||||
encodedParameters = beginTime;
|
||||
encodedParameters |= beginPrice<<32;
|
||||
return encodedParameters;
|
||||
}
|
||||
|
||||
/// @dev Performs a match of the two orders at the price point given the current block time and the auction
|
||||
/// start time (encoded in the salt).
|
||||
/// The Sellers order is a signed order at the lowest price at the end of the auction. Excess from the match
|
||||
/// is transferred to the seller.
|
||||
/// @param buyOrder The Buyer's order
|
||||
/// @param sellOrder The Seller's order
|
||||
/// @param buySignature Proof that order was created by the left maker.
|
||||
/// @param sellSignature Proof that order was created by the right maker.
|
||||
/// @return matchedFillResults Amounts filled and fees paid by maker and taker of matched orders.
|
||||
function matchOrders(
|
||||
LibOrder.Order memory buyOrder,
|
||||
LibOrder.Order memory sellOrder,
|
||||
bytes memory buySignature,
|
||||
bytes memory sellSignature
|
||||
)
|
||||
public
|
||||
returns (LibFillResults.MatchedFillResults memory matchedFillResults)
|
||||
{
|
||||
AuctionDetails memory auctionDetails = getAuctionDetails(sellOrder);
|
||||
// Ensure the auction has not yet started
|
||||
// solhint-disable-next-line not-rely-on-time
|
||||
require(block.timestamp >= auctionDetails.beginTime, "AUCTION_NOT_STARTED");
|
||||
// Ensure the auction has not expired. This will fail later in 0x but we can save gas by failing early
|
||||
// solhint-disable-next-line not-rely-on-time
|
||||
require(sellOrder.expirationTimeSeconds > block.timestamp, "AUCTION_EXPIRED");
|
||||
// Ensure the auction goes from high to low
|
||||
require(auctionDetails.beginPrice > auctionDetails.endPrice, "INVALID_PRICE");
|
||||
// Validate the buyer amount is greater than the current auction price
|
||||
require(buyOrder.makerAssetAmount >= auctionDetails.currentPrice, "INVALID_PRICE");
|
||||
// Match orders, maximally filling `buyOrder`
|
||||
matchedFillResults = EXCHANGE.matchOrders(
|
||||
buyOrder,
|
||||
sellOrder,
|
||||
buySignature,
|
||||
sellSignature
|
||||
);
|
||||
// Return any spread to the seller
|
||||
uint256 leftMakerAssetSpreadAmount = matchedFillResults.leftMakerAssetSpreadAmount;
|
||||
if (leftMakerAssetSpreadAmount > 0) {
|
||||
bytes memory assetData = sellOrder.takerAssetData;
|
||||
address token = assetData.readAddress(16);
|
||||
address makerAddress = sellOrder.makerAddress;
|
||||
IERC20Token(token).transfer(makerAddress, leftMakerAssetSpreadAmount);
|
||||
}
|
||||
return matchedFillResults;
|
||||
}
|
||||
|
||||
/// @dev Decodes the packed parameters into beginTime and beginPrice.
|
||||
/// @param encodedParameters the encoded parameters
|
||||
/// @return beginTime and beginPrice decoded
|
||||
function decodeParameters(
|
||||
uint256 encodedParameters
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256 beginTime, uint256 beginPrice)
|
||||
{
|
||||
beginTime = encodedParameters & 0x00000000000000000000000fffffffff;
|
||||
beginPrice = encodedParameters>>32;
|
||||
return (beginTime, beginPrice);
|
||||
}
|
||||
|
||||
/// @dev Calculates the Auction Details for the given order
|
||||
/// @param order The sell order
|
||||
/// @return AuctionDetails
|
||||
function getAuctionDetails(
|
||||
LibOrder.Order memory order
|
||||
)
|
||||
public
|
||||
returns (AuctionDetails memory auctionDetails)
|
||||
{
|
||||
// solhint-disable-next-line indent
|
||||
(uint256 auctionBeginTimeSeconds, uint256 auctionBeginPrice) = decodeParameters(order.salt);
|
||||
require(order.expirationTimeSeconds > auctionBeginTimeSeconds, "INVALID_BEGIN_TIME");
|
||||
uint256 auctionDurationSeconds = order.expirationTimeSeconds-auctionBeginTimeSeconds;
|
||||
// solhint-disable-next-line not-rely-on-time
|
||||
uint256 currentDurationSeconds = order.expirationTimeSeconds-block.timestamp;
|
||||
uint256 minPrice = order.takerAssetAmount;
|
||||
uint256 priceDiff = auctionBeginPrice-minPrice;
|
||||
uint256 currentPrice = minPrice + (currentDurationSeconds*priceDiff/auctionDurationSeconds);
|
||||
|
||||
auctionDetails.beginTime = auctionBeginTimeSeconds;
|
||||
auctionDetails.endTime = order.expirationTimeSeconds;
|
||||
auctionDetails.beginPrice = auctionBeginPrice;
|
||||
auctionDetails.endPrice = minPrice;
|
||||
auctionDetails.currentPrice = currentPrice;
|
||||
// solhint-disable-next-line not-rely-on-time
|
||||
auctionDetails.currentTime = block.timestamp;
|
||||
|
||||
return auctionDetails;
|
||||
}
|
||||
}
|
||||
315
packages/contracts/test/extensions/dutch_auction.ts
Normal file
315
packages/contracts/test/extensions/dutch_auction.ts
Normal file
@@ -0,0 +1,315 @@
|
||||
import { BlockchainLifecycle } from '@0x/dev-utils';
|
||||
import { assetDataUtils } from '@0x/order-utils';
|
||||
import { SignedOrder } from '@0x/types';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import { Web3Wrapper } from '@0x/web3-wrapper';
|
||||
import * as chai from 'chai';
|
||||
|
||||
import { DummyERC20TokenContract } from '../../generated-wrappers/dummy_erc20_token';
|
||||
import { DummyERC721TokenContract } from '../../generated-wrappers/dummy_erc721_token';
|
||||
import { DutchAuctionContract } from '../../generated-wrappers/dutch_auction';
|
||||
import { ExchangeContract } from '../../generated-wrappers/exchange';
|
||||
import { WETH9Contract } from '../../generated-wrappers/weth9';
|
||||
import { artifacts } from '../../src/artifacts';
|
||||
import { expectTransactionFailedAsync } from '../utils/assertions';
|
||||
import { getLatestBlockTimestampAsync } from '../utils/block_timestamp';
|
||||
import { chaiSetup } from '../utils/chai_setup';
|
||||
import { constants } from '../utils/constants';
|
||||
import { ERC20Wrapper } from '../utils/erc20_wrapper';
|
||||
import { ERC721Wrapper } from '../utils/erc721_wrapper';
|
||||
import { ExchangeWrapper } from '../utils/exchange_wrapper';
|
||||
import { OrderFactory } from '../utils/order_factory';
|
||||
import { ContractName, ERC20BalancesByOwner } from '../utils/types';
|
||||
import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
|
||||
const DECIMALS_DEFAULT = 18;
|
||||
|
||||
describe(ContractName.DutchAuction, () => {
|
||||
let makerAddress: string;
|
||||
let owner: string;
|
||||
let takerAddress: string;
|
||||
let feeRecipientAddress: string;
|
||||
let defaultMakerAssetAddress: string;
|
||||
|
||||
let weth: DummyERC20TokenContract;
|
||||
let zrxToken: DummyERC20TokenContract;
|
||||
let erc20TokenA: DummyERC20TokenContract;
|
||||
let erc721Token: DummyERC721TokenContract;
|
||||
let dutchAuctionContract: DutchAuctionContract;
|
||||
let wethContract: WETH9Contract;
|
||||
let exchangeWrapper: ExchangeWrapper;
|
||||
|
||||
let sellerOrderFactory: OrderFactory;
|
||||
let buyerOrderFactory: OrderFactory;
|
||||
let erc20Wrapper: ERC20Wrapper;
|
||||
let erc20Balances: ERC20BalancesByOwner;
|
||||
let tenMinutesInSeconds: number;
|
||||
let currentBlockTimestamp: number;
|
||||
let auctionBeginTime: BigNumber;
|
||||
let auctionBeginPrice: BigNumber;
|
||||
let encodedParams: BigNumber;
|
||||
let sellOrder: SignedOrder;
|
||||
let buyOrder: SignedOrder;
|
||||
let erc721MakerAssetIds: BigNumber[];
|
||||
|
||||
before(async () => {
|
||||
await blockchainLifecycle.startAsync();
|
||||
const accounts = await web3Wrapper.getAvailableAddressesAsync();
|
||||
const usedAddresses = ([owner, makerAddress, takerAddress, feeRecipientAddress] = accounts);
|
||||
|
||||
erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner);
|
||||
|
||||
const numDummyErc20ToDeploy = 2;
|
||||
[erc20TokenA, zrxToken] = await erc20Wrapper.deployDummyTokensAsync(
|
||||
numDummyErc20ToDeploy,
|
||||
constants.DUMMY_TOKEN_DECIMALS,
|
||||
);
|
||||
const erc20Proxy = await erc20Wrapper.deployProxyAsync();
|
||||
await erc20Wrapper.setBalancesAndAllowancesAsync();
|
||||
|
||||
const erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner);
|
||||
[erc721Token] = await erc721Wrapper.deployDummyTokensAsync();
|
||||
const erc721Proxy = await erc721Wrapper.deployProxyAsync();
|
||||
await erc721Wrapper.setBalancesAndAllowancesAsync();
|
||||
const erc721Balances = await erc721Wrapper.getBalancesAsync();
|
||||
erc721MakerAssetIds = erc721Balances[makerAddress][erc721Token.address];
|
||||
|
||||
wethContract = await WETH9Contract.deployFrom0xArtifactAsync(artifacts.WETH9, provider, txDefaults);
|
||||
weth = new DummyERC20TokenContract(wethContract.abi, wethContract.address, provider);
|
||||
erc20Wrapper.addDummyTokenContract(weth);
|
||||
|
||||
const zrxAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address);
|
||||
const exchangeInstance = await ExchangeContract.deployFrom0xArtifactAsync(
|
||||
artifacts.Exchange,
|
||||
provider,
|
||||
txDefaults,
|
||||
zrxAssetData,
|
||||
);
|
||||
exchangeWrapper = new ExchangeWrapper(exchangeInstance, provider);
|
||||
await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, owner);
|
||||
|
||||
await exchangeWrapper.registerAssetProxyAsync(erc721Proxy.address, owner);
|
||||
|
||||
await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeInstance.address, {
|
||||
from: owner,
|
||||
});
|
||||
await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeInstance.address, {
|
||||
from: owner,
|
||||
});
|
||||
|
||||
const dutchAuctionInstance = await DutchAuctionContract.deployFrom0xArtifactAsync(
|
||||
artifacts.DutchAuction,
|
||||
provider,
|
||||
txDefaults,
|
||||
exchangeInstance.address,
|
||||
);
|
||||
dutchAuctionContract = new DutchAuctionContract(
|
||||
dutchAuctionInstance.abi,
|
||||
dutchAuctionInstance.address,
|
||||
provider,
|
||||
);
|
||||
|
||||
defaultMakerAssetAddress = erc20TokenA.address;
|
||||
const defaultTakerAssetAddress = wethContract.address;
|
||||
|
||||
// Set up taker WETH balance and allowance
|
||||
await web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await wethContract.deposit.sendTransactionAsync({
|
||||
from: takerAddress,
|
||||
value: Web3Wrapper.toBaseUnitAmount(new BigNumber(50), DECIMALS_DEFAULT),
|
||||
}),
|
||||
);
|
||||
await web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await wethContract.approve.sendTransactionAsync(
|
||||
erc20Proxy.address,
|
||||
constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS,
|
||||
{ from: takerAddress },
|
||||
),
|
||||
);
|
||||
web3Wrapper.abiDecoder.addABI(exchangeInstance.abi);
|
||||
web3Wrapper.abiDecoder.addABI(zrxToken.abi);
|
||||
erc20Wrapper.addTokenOwnerAddress(dutchAuctionContract.address);
|
||||
tenMinutesInSeconds = 10 * 60;
|
||||
currentBlockTimestamp = await getLatestBlockTimestampAsync();
|
||||
auctionBeginTime = new BigNumber(currentBlockTimestamp).minus(tenMinutesInSeconds);
|
||||
auctionBeginPrice = Web3Wrapper.toBaseUnitAmount(new BigNumber(10), DECIMALS_DEFAULT);
|
||||
encodedParams = await dutchAuctionContract.encodeParameters.callAsync(auctionBeginTime, auctionBeginPrice);
|
||||
|
||||
const sellerDefaultOrderParams = {
|
||||
salt: encodedParams, // Set the encoded params as the salt for the seller order
|
||||
exchangeAddress: exchangeInstance.address,
|
||||
makerAddress,
|
||||
feeRecipientAddress,
|
||||
senderAddress: dutchAuctionContract.address,
|
||||
makerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress),
|
||||
takerAssetData: assetDataUtils.encodeERC20AssetData(defaultTakerAssetAddress),
|
||||
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(200), DECIMALS_DEFAULT),
|
||||
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), DECIMALS_DEFAULT),
|
||||
makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(0), DECIMALS_DEFAULT),
|
||||
takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(0), DECIMALS_DEFAULT),
|
||||
};
|
||||
const buyerDefaultOrderParams = {
|
||||
...sellerDefaultOrderParams,
|
||||
makerAddress: takerAddress,
|
||||
makerAssetData: sellerDefaultOrderParams.takerAssetData,
|
||||
takerAssetData: sellerDefaultOrderParams.makerAssetData,
|
||||
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), DECIMALS_DEFAULT),
|
||||
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(200), DECIMALS_DEFAULT),
|
||||
};
|
||||
const makerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)];
|
||||
const takerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(takerAddress)];
|
||||
sellerOrderFactory = new OrderFactory(makerPrivateKey, sellerDefaultOrderParams);
|
||||
buyerOrderFactory = new OrderFactory(takerPrivateKey, buyerDefaultOrderParams);
|
||||
});
|
||||
after(async () => {
|
||||
await blockchainLifecycle.revertAsync();
|
||||
});
|
||||
beforeEach(async () => {
|
||||
await blockchainLifecycle.startAsync();
|
||||
erc20Balances = await erc20Wrapper.getBalancesAsync();
|
||||
tenMinutesInSeconds = 10 * 60;
|
||||
currentBlockTimestamp = await getLatestBlockTimestampAsync();
|
||||
auctionBeginTime = new BigNumber(currentBlockTimestamp).minus(tenMinutesInSeconds);
|
||||
auctionBeginPrice = Web3Wrapper.toBaseUnitAmount(new BigNumber(10), DECIMALS_DEFAULT);
|
||||
encodedParams = await dutchAuctionContract.encodeParameters.callAsync(auctionBeginTime, auctionBeginPrice);
|
||||
sellOrder = await sellerOrderFactory.newSignedOrderAsync();
|
||||
buyOrder = await buyerOrderFactory.newSignedOrderAsync();
|
||||
});
|
||||
afterEach(async () => {
|
||||
await blockchainLifecycle.revertAsync();
|
||||
});
|
||||
describe('matchOrders', () => {
|
||||
it('should encode and decode parameters', async () => {
|
||||
const [decodedBegin, decodedBeginPrice] = await dutchAuctionContract.decodeParameters.callAsync(
|
||||
encodedParams,
|
||||
);
|
||||
expect(decodedBegin).to.be.bignumber.equal(auctionBeginTime);
|
||||
expect(decodedBeginPrice).to.be.bignumber.equal(auctionBeginPrice);
|
||||
});
|
||||
it('should be worth the begin price at the begining of the auction', async () => {
|
||||
// TODO this is flakey
|
||||
currentBlockTimestamp = await web3Wrapper.getBlockTimestampAsync('latest');
|
||||
await web3Wrapper.increaseTimeAsync(1);
|
||||
auctionBeginTime = new BigNumber(currentBlockTimestamp + 2);
|
||||
encodedParams = await dutchAuctionContract.encodeParameters.callAsync(auctionBeginTime, auctionBeginPrice);
|
||||
sellOrder = await sellerOrderFactory.newSignedOrderAsync({
|
||||
salt: encodedParams,
|
||||
});
|
||||
const auctionDetails = await dutchAuctionContract.getAuctionDetails.callAsync(sellOrder);
|
||||
expect(auctionDetails.currentPrice).to.be.bignumber.equal(auctionBeginPrice);
|
||||
expect(auctionDetails.beginPrice).to.be.bignumber.equal(auctionBeginPrice);
|
||||
});
|
||||
it('should match orders and send excess to seller', async () => {
|
||||
const txHash = await dutchAuctionContract.matchOrders.sendTransactionAsync(
|
||||
buyOrder,
|
||||
sellOrder,
|
||||
buyOrder.signature,
|
||||
sellOrder.signature,
|
||||
{
|
||||
from: takerAddress,
|
||||
},
|
||||
);
|
||||
await web3Wrapper.awaitTransactionSuccessAsync(txHash);
|
||||
const newBalances = await erc20Wrapper.getBalancesAsync();
|
||||
expect(newBalances[dutchAuctionContract.address][weth.address]).to.be.bignumber.equal(
|
||||
constants.ZERO_AMOUNT,
|
||||
);
|
||||
expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
|
||||
erc20Balances[makerAddress][weth.address].plus(buyOrder.makerAssetAmount),
|
||||
);
|
||||
});
|
||||
it('should have valid getAuctionDetails at a block in the future', async () => {
|
||||
let auctionDetails = await dutchAuctionContract.getAuctionDetails.callAsync(sellOrder);
|
||||
const beforePrice = auctionDetails.currentPrice;
|
||||
// Increase block time
|
||||
await web3Wrapper.increaseTimeAsync(60);
|
||||
auctionDetails = await dutchAuctionContract.getAuctionDetails.callAsync(sellOrder);
|
||||
const currentPrice = auctionDetails.currentPrice;
|
||||
expect(beforePrice).to.be.bignumber.greaterThan(currentPrice);
|
||||
buyOrder = await buyerOrderFactory.newSignedOrderAsync({
|
||||
makerAssetAmount: currentPrice,
|
||||
});
|
||||
const txHash = await dutchAuctionContract.matchOrders.sendTransactionAsync(
|
||||
buyOrder,
|
||||
sellOrder,
|
||||
buyOrder.signature,
|
||||
sellOrder.signature,
|
||||
{
|
||||
from: takerAddress,
|
||||
},
|
||||
);
|
||||
await web3Wrapper.awaitTransactionSuccessAsync(txHash);
|
||||
const newBalances = await erc20Wrapper.getBalancesAsync();
|
||||
expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
|
||||
erc20Balances[makerAddress][weth.address].plus(currentPrice),
|
||||
);
|
||||
});
|
||||
it('should revert when auction expires', async () => {
|
||||
// Increase block time
|
||||
await web3Wrapper.increaseTimeAsync(tenMinutesInSeconds);
|
||||
return expectTransactionFailedAsync(
|
||||
dutchAuctionContract.matchOrders.sendTransactionAsync(
|
||||
buyOrder,
|
||||
sellOrder,
|
||||
buyOrder.signature,
|
||||
sellOrder.signature,
|
||||
{
|
||||
from: takerAddress,
|
||||
},
|
||||
),
|
||||
'AUCTION_EXPIRED' as any,
|
||||
);
|
||||
});
|
||||
it('cannot be filled for less than the current price', async () => {
|
||||
// Increase block time
|
||||
await web3Wrapper.increaseTimeAsync(60);
|
||||
buyOrder = await buyerOrderFactory.newSignedOrderAsync({
|
||||
makerAssetAmount: sellOrder.takerAssetAmount,
|
||||
});
|
||||
return expectTransactionFailedAsync(
|
||||
dutchAuctionContract.matchOrders.sendTransactionAsync(
|
||||
buyOrder,
|
||||
sellOrder,
|
||||
buyOrder.signature,
|
||||
sellOrder.signature,
|
||||
{
|
||||
from: takerAddress,
|
||||
},
|
||||
),
|
||||
'INVALID_PRICE' as any,
|
||||
);
|
||||
});
|
||||
describe('ERC721', () => {
|
||||
it('should match orders when ERC721', async () => {
|
||||
const makerAssetId = erc721MakerAssetIds[0];
|
||||
sellOrder = await sellerOrderFactory.newSignedOrderAsync({
|
||||
makerAssetAmount: new BigNumber(1),
|
||||
makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId),
|
||||
});
|
||||
buyOrder = await buyerOrderFactory.newSignedOrderAsync({
|
||||
takerAssetAmount: new BigNumber(1),
|
||||
takerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId),
|
||||
});
|
||||
const txHash = await dutchAuctionContract.matchOrders.sendTransactionAsync(
|
||||
buyOrder,
|
||||
sellOrder,
|
||||
buyOrder.signature,
|
||||
sellOrder.signature,
|
||||
{
|
||||
from: takerAddress,
|
||||
},
|
||||
);
|
||||
await web3Wrapper.awaitTransactionSuccessAsync(txHash);
|
||||
const newBalances = await erc20Wrapper.getBalancesAsync();
|
||||
expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
|
||||
erc20Balances[makerAddress][weth.address].plus(buyOrder.makerAssetAmount),
|
||||
);
|
||||
const newOwner = await erc721Token.ownerOf.callAsync(makerAssetId);
|
||||
expect(newOwner).to.be.bignumber.equal(takerAddress);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user