* generate wrappers in @0x/contract-wrappers and delete abi-gen-wrappers * trim exports from contract-wrappers * separate contract-wrappers tests to get rid of dependency cycle * remove dummy token contracts * temporarily skip coordinator test until we can upgrade coordinator server
338 lines
15 KiB
TypeScript
338 lines
15 KiB
TypeScript
import { ContractAddresses } from '@0x/contract-addresses';
|
|
import { DevUtilsContract, ERC20TokenContract, ExchangeContract } from '@0x/contract-wrappers';
|
|
import { constants as devConstants, OrderFactory } from '@0x/contracts-test-utils';
|
|
import { BlockchainLifecycle, tokenUtils } from '@0x/dev-utils';
|
|
import { migrateOnceAsync } from '@0x/migrations';
|
|
import { BigNumber } from '@0x/utils';
|
|
import * as chai from 'chai';
|
|
import 'mocha';
|
|
|
|
import { SwapQuote } from '../src';
|
|
import { constants } from '../src/constants';
|
|
import { ExchangeSwapQuoteConsumer } from '../src/quote_consumers/exchange_swap_quote_consumer';
|
|
import {
|
|
ExchangeMarketBuySmartContractParams,
|
|
ExchangeMarketSellSmartContractParams,
|
|
MarketBuySwapQuote,
|
|
MarketOperation,
|
|
MarketSellSwapQuote,
|
|
PrunedSignedOrder,
|
|
} from '../src/types';
|
|
import { ProtocolFeeUtils } from '../src/utils/protocol_fee_utils';
|
|
|
|
import { chaiSetup } from './utils/chai_setup';
|
|
import { getFullyFillableSwapQuoteWithNoFeesAsync } from './utils/swap_quote';
|
|
import { provider, web3Wrapper } from './utils/web3_wrapper';
|
|
|
|
chaiSetup.configure();
|
|
const expect = chai.expect;
|
|
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
|
|
|
|
const GAS_PRICE = new BigNumber(devConstants.DEFAULT_GAS_PRICE);
|
|
const ONE_ETH_IN_WEI = new BigNumber(1000000000000000000);
|
|
const TESTRPC_CHAIN_ID = devConstants.TESTRPC_CHAIN_ID;
|
|
const UNLIMITED_ALLOWANCE = new BigNumber(2).pow(256).minus(1); // tslint:disable-line:custom-no-magic-numbers
|
|
|
|
const PARTIAL_PRUNED_SIGNED_ORDERS_FEELESS: Array<Partial<PrunedSignedOrder>> = [
|
|
{
|
|
takerAssetAmount: new BigNumber(5).multipliedBy(ONE_ETH_IN_WEI),
|
|
makerAssetAmount: new BigNumber(2).multipliedBy(ONE_ETH_IN_WEI),
|
|
fillableTakerAssetAmount: new BigNumber(5).multipliedBy(ONE_ETH_IN_WEI),
|
|
fillableMakerAssetAmount: new BigNumber(2).multipliedBy(ONE_ETH_IN_WEI),
|
|
},
|
|
{
|
|
takerAssetAmount: new BigNumber(3).multipliedBy(ONE_ETH_IN_WEI),
|
|
makerAssetAmount: new BigNumber(3).multipliedBy(ONE_ETH_IN_WEI),
|
|
fillableTakerAssetAmount: new BigNumber(3).multipliedBy(ONE_ETH_IN_WEI),
|
|
fillableMakerAssetAmount: new BigNumber(3).multipliedBy(ONE_ETH_IN_WEI),
|
|
},
|
|
{
|
|
takerAssetAmount: new BigNumber(2).multipliedBy(ONE_ETH_IN_WEI),
|
|
makerAssetAmount: new BigNumber(5).multipliedBy(ONE_ETH_IN_WEI),
|
|
fillableTakerAssetAmount: new BigNumber(2).multipliedBy(ONE_ETH_IN_WEI),
|
|
fillableMakerAssetAmount: new BigNumber(5).multipliedBy(ONE_ETH_IN_WEI),
|
|
},
|
|
];
|
|
|
|
const expectMakerAndTakerBalancesAsyncFactory = (
|
|
erc20TokenContract: ERC20TokenContract,
|
|
makerAddress: string,
|
|
takerAddress: string,
|
|
) => async (expectedMakerBalance: BigNumber, expectedTakerBalance: BigNumber) => {
|
|
const makerBalance = await erc20TokenContract.balanceOf(makerAddress).callAsync();
|
|
const takerBalance = await erc20TokenContract.balanceOf(takerAddress).callAsync();
|
|
expect(makerBalance).to.bignumber.equal(expectedMakerBalance);
|
|
expect(takerBalance).to.bignumber.equal(expectedTakerBalance);
|
|
};
|
|
|
|
describe('ExchangeSwapQuoteConsumer', () => {
|
|
let protocolFeeUtils: ProtocolFeeUtils;
|
|
let userAddresses: string[];
|
|
let erc20MakerTokenContract: ERC20TokenContract;
|
|
let erc20TakerTokenContract: ERC20TokenContract;
|
|
let coinbaseAddress: string;
|
|
let makerAddress: string;
|
|
let takerAddress: string;
|
|
let orderFactory: OrderFactory;
|
|
let feeRecipient: string;
|
|
let makerTokenAddress: string;
|
|
let takerTokenAddress: string;
|
|
let makerAssetData: string;
|
|
let takerAssetData: string;
|
|
let wethAssetData: string;
|
|
let contractAddresses: ContractAddresses;
|
|
let exchangeContract: ExchangeContract;
|
|
|
|
const chainId = TESTRPC_CHAIN_ID;
|
|
|
|
let orders: PrunedSignedOrder[];
|
|
let marketSellSwapQuote: SwapQuote;
|
|
let marketBuySwapQuote: SwapQuote;
|
|
let swapQuoteConsumer: ExchangeSwapQuoteConsumer;
|
|
let expectMakerAndTakerBalancesForMakerAssetAsync: (
|
|
expectedMakerBalance: BigNumber,
|
|
expectedTakerBalance: BigNumber,
|
|
) => Promise<void>;
|
|
let expectMakerAndTakerBalancesForTakerAssetAsync: (
|
|
expectedMakerBalance: BigNumber,
|
|
expectedTakerBalance: BigNumber,
|
|
) => Promise<void>;
|
|
|
|
before(async () => {
|
|
contractAddresses = await migrateOnceAsync(provider);
|
|
await blockchainLifecycle.startAsync();
|
|
userAddresses = await web3Wrapper.getAvailableAddressesAsync();
|
|
[coinbaseAddress, takerAddress, makerAddress, feeRecipient] = userAddresses;
|
|
[makerTokenAddress, takerTokenAddress] = tokenUtils.getDummyERC20TokenAddresses();
|
|
const devUtils = new DevUtilsContract(contractAddresses.devUtils, provider);
|
|
[makerAssetData, takerAssetData, wethAssetData] = await Promise.all([
|
|
devUtils.encodeERC20AssetData(makerTokenAddress).callAsync(),
|
|
devUtils.encodeERC20AssetData(takerTokenAddress).callAsync(),
|
|
devUtils.encodeERC20AssetData(contractAddresses.etherToken).callAsync(),
|
|
]);
|
|
erc20MakerTokenContract = new ERC20TokenContract(makerTokenAddress, provider);
|
|
erc20TakerTokenContract = new ERC20TokenContract(takerTokenAddress, provider);
|
|
exchangeContract = new ExchangeContract(contractAddresses.exchange, provider);
|
|
// Configure order defaults
|
|
const defaultOrderParams = {
|
|
...devConstants.STATIC_ORDER_PARAMS,
|
|
makerAddress,
|
|
takerAddress,
|
|
makerAssetData,
|
|
takerAssetData,
|
|
makerFeeAssetData: constants.NULL_ERC20_ASSET_DATA,
|
|
takerFeeAssetData: constants.NULL_ERC20_ASSET_DATA,
|
|
makerFee: constants.ZERO_AMOUNT,
|
|
takerFee: constants.ZERO_AMOUNT,
|
|
feeRecipientAddress: feeRecipient,
|
|
exchangeAddress: contractAddresses.exchange,
|
|
chainId,
|
|
};
|
|
const privateKey = devConstants.TESTRPC_PRIVATE_KEYS[userAddresses.indexOf(makerAddress)];
|
|
orderFactory = new OrderFactory(privateKey, defaultOrderParams);
|
|
protocolFeeUtils = new ProtocolFeeUtils();
|
|
expectMakerAndTakerBalancesForTakerAssetAsync = expectMakerAndTakerBalancesAsyncFactory(
|
|
erc20TakerTokenContract,
|
|
makerAddress,
|
|
takerAddress,
|
|
);
|
|
expectMakerAndTakerBalancesForMakerAssetAsync = expectMakerAndTakerBalancesAsyncFactory(
|
|
erc20MakerTokenContract,
|
|
makerAddress,
|
|
takerAddress,
|
|
);
|
|
});
|
|
after(async () => {
|
|
await blockchainLifecycle.revertAsync();
|
|
});
|
|
beforeEach(async () => {
|
|
await blockchainLifecycle.startAsync();
|
|
orders = [];
|
|
for (const partialOrder of PARTIAL_PRUNED_SIGNED_ORDERS_FEELESS) {
|
|
const order = await orderFactory.newSignedOrderAsync(partialOrder);
|
|
const prunedOrder = {
|
|
...order,
|
|
...partialOrder,
|
|
};
|
|
orders.push(prunedOrder as PrunedSignedOrder);
|
|
}
|
|
|
|
marketSellSwapQuote = await getFullyFillableSwapQuoteWithNoFeesAsync(
|
|
makerAssetData,
|
|
takerAssetData,
|
|
orders,
|
|
MarketOperation.Sell,
|
|
GAS_PRICE,
|
|
protocolFeeUtils,
|
|
);
|
|
|
|
marketBuySwapQuote = await getFullyFillableSwapQuoteWithNoFeesAsync(
|
|
makerAssetData,
|
|
takerAssetData,
|
|
orders,
|
|
MarketOperation.Buy,
|
|
GAS_PRICE,
|
|
protocolFeeUtils,
|
|
);
|
|
|
|
swapQuoteConsumer = new ExchangeSwapQuoteConsumer(provider, contractAddresses, {
|
|
chainId,
|
|
});
|
|
|
|
await erc20MakerTokenContract
|
|
.transfer(makerAddress, marketBuySwapQuote.worstCaseQuoteInfo.makerAssetAmount)
|
|
.sendTransactionAsync({
|
|
from: coinbaseAddress,
|
|
});
|
|
await erc20TakerTokenContract
|
|
.transfer(takerAddress, marketBuySwapQuote.worstCaseQuoteInfo.totalTakerAssetAmount)
|
|
.sendTransactionAsync({
|
|
from: coinbaseAddress,
|
|
});
|
|
await erc20MakerTokenContract
|
|
.approve(contractAddresses.erc20Proxy, UNLIMITED_ALLOWANCE)
|
|
.sendTransactionAsync({ from: makerAddress });
|
|
await erc20TakerTokenContract
|
|
.approve(contractAddresses.erc20Proxy, UNLIMITED_ALLOWANCE)
|
|
.sendTransactionAsync({ from: takerAddress });
|
|
});
|
|
afterEach(async () => {
|
|
await blockchainLifecycle.revertAsync();
|
|
});
|
|
describe('#executeSwapQuoteOrThrowAsync', () => {
|
|
/*
|
|
* Testing that SwapQuoteConsumer logic correctly performs a execution (doesn't throw or revert)
|
|
* Does not test the validity of the state change performed by the forwarder smart contract
|
|
*/
|
|
it('should perform a marketSell execution when provided a MarketSell type swapQuote', async () => {
|
|
await expectMakerAndTakerBalancesForMakerAssetAsync(
|
|
new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
|
|
constants.ZERO_AMOUNT,
|
|
);
|
|
await expectMakerAndTakerBalancesForTakerAssetAsync(
|
|
constants.ZERO_AMOUNT,
|
|
new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
|
|
);
|
|
await swapQuoteConsumer.executeSwapQuoteOrThrowAsync(marketSellSwapQuote, {
|
|
takerAddress,
|
|
gasLimit: 4000000,
|
|
});
|
|
await expectMakerAndTakerBalancesForMakerAssetAsync(
|
|
constants.ZERO_AMOUNT,
|
|
new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
|
|
);
|
|
await expectMakerAndTakerBalancesForTakerAssetAsync(
|
|
new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
|
|
constants.ZERO_AMOUNT,
|
|
);
|
|
});
|
|
it('should perform a marketBuy execution when provided a MarketBuy type swapQuote', async () => {
|
|
await expectMakerAndTakerBalancesForMakerAssetAsync(
|
|
new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
|
|
constants.ZERO_AMOUNT,
|
|
);
|
|
await expectMakerAndTakerBalancesForTakerAssetAsync(
|
|
constants.ZERO_AMOUNT,
|
|
new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
|
|
);
|
|
await swapQuoteConsumer.executeSwapQuoteOrThrowAsync(marketBuySwapQuote, {
|
|
takerAddress,
|
|
gasLimit: 4000000,
|
|
});
|
|
await expectMakerAndTakerBalancesForMakerAssetAsync(
|
|
constants.ZERO_AMOUNT,
|
|
new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
|
|
);
|
|
await expectMakerAndTakerBalancesForTakerAssetAsync(
|
|
new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
|
|
constants.ZERO_AMOUNT,
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('#getSmartContractParamsOrThrow', () => {
|
|
describe('valid swap quote', async () => {
|
|
// TODO(david) Check for valid MethodAbi
|
|
it('provide correct and optimized smart contract params for a marketSell SwapQuote', async () => {
|
|
const { toAddress, params } = await swapQuoteConsumer.getSmartContractParamsOrThrowAsync(
|
|
marketSellSwapQuote,
|
|
{},
|
|
);
|
|
expect(toAddress).to.deep.equal(exchangeContract.address);
|
|
const { takerAssetFillAmount, signatures, type } = params as ExchangeMarketSellSmartContractParams;
|
|
expect(type).to.deep.equal(MarketOperation.Sell);
|
|
expect(takerAssetFillAmount).to.bignumber.equal(
|
|
(marketSellSwapQuote as MarketSellSwapQuote).takerAssetFillAmount,
|
|
);
|
|
const orderSignatures = marketSellSwapQuote.orders.map(order => order.signature);
|
|
expect(signatures).to.deep.equal(orderSignatures);
|
|
});
|
|
it('provide correct smart contract params for a marketBuy SwapQuote', async () => {
|
|
const { toAddress, params } = await swapQuoteConsumer.getSmartContractParamsOrThrowAsync(
|
|
marketBuySwapQuote,
|
|
{},
|
|
);
|
|
expect(toAddress).to.deep.equal(exchangeContract.address);
|
|
const { makerAssetFillAmount, signatures, type } = params as ExchangeMarketBuySmartContractParams;
|
|
expect(type).to.deep.equal(MarketOperation.Buy);
|
|
expect(makerAssetFillAmount).to.bignumber.equal(
|
|
(marketBuySwapQuote as MarketBuySwapQuote).makerAssetFillAmount,
|
|
);
|
|
const orderSignatures = marketSellSwapQuote.orders.map(order => order.signature);
|
|
expect(signatures).to.deep.equal(orderSignatures);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('#getCalldataOrThrow', () => {
|
|
describe('valid swap quote', async () => {
|
|
it('provide correct and optimized calldata options with default options for a marketSell SwapQuote (no affiliate fees)', async () => {
|
|
await expectMakerAndTakerBalancesForMakerAssetAsync(
|
|
new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
|
|
constants.ZERO_AMOUNT,
|
|
);
|
|
const { calldataHexString, toAddress, ethAmount } = await swapQuoteConsumer.getCalldataOrThrowAsync(
|
|
marketSellSwapQuote,
|
|
{},
|
|
);
|
|
expect(toAddress).to.deep.equal(exchangeContract.address);
|
|
await web3Wrapper.sendTransactionAsync({
|
|
from: takerAddress,
|
|
to: toAddress,
|
|
data: calldataHexString,
|
|
gas: 4000000,
|
|
gasPrice: GAS_PRICE,
|
|
value: ethAmount,
|
|
});
|
|
await expectMakerAndTakerBalancesForMakerAssetAsync(
|
|
constants.ZERO_AMOUNT,
|
|
new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
|
|
);
|
|
});
|
|
it('provide correct and optimized calldata options with default options for a marketBuy SwapQuote (no affiliate fees)', async () => {
|
|
await expectMakerAndTakerBalancesForMakerAssetAsync(
|
|
new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
|
|
constants.ZERO_AMOUNT,
|
|
);
|
|
const { calldataHexString, toAddress, ethAmount } = await swapQuoteConsumer.getCalldataOrThrowAsync(
|
|
marketBuySwapQuote,
|
|
{},
|
|
);
|
|
expect(toAddress).to.deep.equal(exchangeContract.address);
|
|
await web3Wrapper.sendTransactionAsync({
|
|
from: takerAddress,
|
|
to: toAddress,
|
|
data: calldataHexString,
|
|
gas: 4000000,
|
|
gasPrice: GAS_PRICE,
|
|
value: ethAmount,
|
|
});
|
|
await expectMakerAndTakerBalancesForMakerAssetAsync(
|
|
constants.ZERO_AMOUNT,
|
|
new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
|
|
);
|
|
});
|
|
});
|
|
});
|
|
});
|