355 lines
21 KiB
TypeScript
355 lines
21 KiB
TypeScript
import { ContractAddresses, ContractWrappers } from '@0x/contract-wrappers';
|
|
import { tokenUtils } from '@0x/contract-wrappers/lib/test/utils/token_utils';
|
|
import { BlockchainLifecycle } from '@0x/dev-utils';
|
|
import { FillScenarios } from '@0x/fill-scenarios';
|
|
import { assetDataUtils } from '@0x/order-utils';
|
|
import { MarketOperation, SignedOrder } from '@0x/types';
|
|
import { BigNumber, AbiDecoder } from '@0x/utils';
|
|
import * as chai from 'chai';
|
|
import 'mocha';
|
|
|
|
import { SwapQuote } from '../src';
|
|
import { ForwarderSwapQuoteConsumer } from '../src/quote_consumers/forwarder_swap_quote_consumer';
|
|
|
|
import { chaiSetup } from './utils/chai_setup';
|
|
import { migrateOnceAsync } from './utils/migrate';
|
|
import { getFullyFillableSwapQuoteWithNoFees, getSignedOrdersWithNoFeesAsync } from './utils/swap_quote';
|
|
import { provider, web3Wrapper } from './utils/web3_wrapper';
|
|
import { constants } from '../src/constants';
|
|
import { ForwarderMarketSellSmartContractParams, MarketSellSwapQuote, ForwarderMarketBuySmartContractParams, MarketBuySwapQuote } from '../src/types';
|
|
|
|
chaiSetup.configure();
|
|
const expect = chai.expect;
|
|
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
|
|
|
|
const ONE_ETH_IN_WEI = new BigNumber(1000000000000000000);
|
|
const TESTRPC_NETWORK_ID = 50;
|
|
const MARKET_OPERATION = MarketOperation.Sell;
|
|
const FILLABLE_AMOUNTS = [new BigNumber(2), new BigNumber(3), new BigNumber(5)].map(value => value.multipliedBy(ONE_ETH_IN_WEI));
|
|
const FILLABLE_FEE_AMOUNTS = [new BigNumber(1), new BigNumber(1), new BigNumber(1)].map(value => value.multipliedBy(ONE_ETH_IN_WEI));
|
|
|
|
describe('ForwarderSwapQuoteConsumer', () => {
|
|
let contractWrappers: ContractWrappers;
|
|
let userAddresses: string[];
|
|
let coinbaseAddress: string;
|
|
let makerAddress: string;
|
|
let takerAddress: string;
|
|
let fillScenarios: FillScenarios;
|
|
let feeRecipient: string;
|
|
let makerTokenAddress: string;
|
|
let takerTokenAddress: string;
|
|
let makerAssetData: string;
|
|
let takerAssetData: string;
|
|
let wethAssetData: string;
|
|
let contractAddresses: ContractAddresses;
|
|
|
|
let orders: SignedOrder[];
|
|
let marketSellSwapQuote: SwapQuote;
|
|
let marketBuySwapQuote: SwapQuote;
|
|
let swapQuoteConsumer: ForwarderSwapQuoteConsumer;
|
|
let erc20ProxyAddress: string;
|
|
|
|
const networkId = TESTRPC_NETWORK_ID;
|
|
before(async () => {
|
|
contractAddresses = await migrateOnceAsync();
|
|
await blockchainLifecycle.startAsync();
|
|
userAddresses = await web3Wrapper.getAvailableAddressesAsync();
|
|
fillScenarios = new FillScenarios(
|
|
provider,
|
|
userAddresses,
|
|
contractAddresses.zrxToken,
|
|
contractAddresses.exchange,
|
|
contractAddresses.erc20Proxy,
|
|
contractAddresses.erc721Proxy,
|
|
);
|
|
const config = {
|
|
networkId,
|
|
contractAddresses,
|
|
};
|
|
contractWrappers = new ContractWrappers(provider, config);
|
|
[coinbaseAddress, takerAddress, makerAddress, feeRecipient] = userAddresses;
|
|
[makerTokenAddress, takerTokenAddress] = tokenUtils.getDummyERC20TokenAddresses();
|
|
[makerAssetData, takerAssetData, wethAssetData] = [
|
|
assetDataUtils.encodeERC20AssetData(makerTokenAddress),
|
|
assetDataUtils.encodeERC20AssetData(takerTokenAddress),
|
|
assetDataUtils.encodeERC20AssetData(contractAddresses.etherToken),
|
|
];
|
|
});
|
|
after(async () => {
|
|
await blockchainLifecycle.revertAsync();
|
|
});
|
|
beforeEach(async () => {
|
|
await blockchainLifecycle.startAsync();
|
|
const UNLIMITED_ALLOWANCE = contractWrappers.erc20Token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS;
|
|
erc20ProxyAddress = contractWrappers.erc20Proxy.address;
|
|
|
|
const totalFillableAmount = FILLABLE_AMOUNTS.reduce((a: BigNumber, c: BigNumber) => a.plus(c), new BigNumber(0));
|
|
|
|
await contractWrappers.erc20Token.transferAsync(makerTokenAddress, coinbaseAddress, makerAddress, totalFillableAmount);
|
|
|
|
await contractWrappers.erc20Token.setAllowanceAsync(makerTokenAddress, makerAddress, erc20ProxyAddress, UNLIMITED_ALLOWANCE);
|
|
orders = await getSignedOrdersWithNoFeesAsync(
|
|
provider,
|
|
makerAssetData,
|
|
wethAssetData,
|
|
makerAddress,
|
|
takerAddress,
|
|
FILLABLE_AMOUNTS,
|
|
contractAddresses.exchange,
|
|
);
|
|
|
|
marketSellSwapQuote = getFullyFillableSwapQuoteWithNoFees(
|
|
makerAssetData,
|
|
wethAssetData,
|
|
orders,
|
|
MarketOperation.Sell,
|
|
);
|
|
|
|
marketBuySwapQuote = getFullyFillableSwapQuoteWithNoFees(
|
|
makerAssetData,
|
|
wethAssetData,
|
|
orders,
|
|
MarketOperation.Buy,
|
|
);
|
|
|
|
swapQuoteConsumer = new ForwarderSwapQuoteConsumer(provider, {
|
|
networkId,
|
|
});
|
|
});
|
|
afterEach(async () => {
|
|
await blockchainLifecycle.revertAsync();
|
|
});
|
|
describe('executeSwapQuoteOrThrowAsync', () => {
|
|
|
|
describe('validation', () => {
|
|
it('should throw if swapQuote provided is not a valid forwarder SwapQuote (taker asset is wEth', async () => {
|
|
const invalidSignedOrders = await getSignedOrdersWithNoFeesAsync(
|
|
provider,
|
|
makerAssetData,
|
|
takerAssetData,
|
|
makerAddress,
|
|
takerAddress,
|
|
FILLABLE_AMOUNTS,
|
|
);
|
|
const invalidSwapQuote = getFullyFillableSwapQuoteWithNoFees(
|
|
makerAssetData,
|
|
takerAssetData,
|
|
invalidSignedOrders,
|
|
MARKET_OPERATION,
|
|
);
|
|
expect(
|
|
swapQuoteConsumer.executeSwapQuoteOrThrowAsync(invalidSwapQuote, { takerAddress }),
|
|
).to.be.rejectedWith(`Expected quote.orders[0] to have takerAssetData set as ${wethAssetData}, but is ${takerAssetData}`);
|
|
});
|
|
});
|
|
|
|
// TODO(david) test execution of swap quotes with fee orders
|
|
describe('valid swap quote', () => {
|
|
/*
|
|
* 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 () => {
|
|
let makerBalance = await contractWrappers.erc20Token.getBalanceAsync(makerTokenAddress, makerAddress);
|
|
let takerBalance = await contractWrappers.erc20Token.getBalanceAsync(makerTokenAddress, takerAddress);
|
|
expect(makerBalance).to.bignumber.equal((new BigNumber(10)).multipliedBy(ONE_ETH_IN_WEI));
|
|
expect(takerBalance).to.bignumber.equal(constants.ZERO_AMOUNT);
|
|
await swapQuoteConsumer.executeSwapQuoteOrThrowAsync(marketSellSwapQuote, { takerAddress });
|
|
makerBalance = await contractWrappers.erc20Token.getBalanceAsync(makerTokenAddress, makerAddress);
|
|
takerBalance = await contractWrappers.erc20Token.getBalanceAsync(makerTokenAddress, takerAddress);
|
|
expect(makerBalance).to.bignumber.equal((new BigNumber(0.5)).multipliedBy(ONE_ETH_IN_WEI));
|
|
expect(takerBalance).to.bignumber.equal((new BigNumber(9.5)).multipliedBy(ONE_ETH_IN_WEI));
|
|
});
|
|
|
|
it('should perform a marketBuy execution when provided a MarketBuy type swapQuote', async () => {
|
|
let makerBalance = await contractWrappers.erc20Token.getBalanceAsync(makerTokenAddress, makerAddress);
|
|
let takerBalance = await contractWrappers.erc20Token.getBalanceAsync(makerTokenAddress, takerAddress);
|
|
expect(makerBalance).to.bignumber.equal((new BigNumber(10)).multipliedBy(ONE_ETH_IN_WEI));
|
|
expect(takerBalance).to.bignumber.equal(constants.ZERO_AMOUNT);
|
|
await swapQuoteConsumer.executeSwapQuoteOrThrowAsync(marketBuySwapQuote, { takerAddress });
|
|
makerBalance = await contractWrappers.erc20Token.getBalanceAsync(makerTokenAddress, makerAddress);
|
|
takerBalance = await contractWrappers.erc20Token.getBalanceAsync(makerTokenAddress, takerAddress);
|
|
expect(takerBalance).to.bignumber.equal((new BigNumber(10)).multipliedBy(ONE_ETH_IN_WEI));
|
|
expect(makerBalance).to.bignumber.equal(constants.ZERO_AMOUNT);
|
|
});
|
|
|
|
it('should perform a marketBuy execution with affiliate fees', async () => {
|
|
let makerBalance = await contractWrappers.erc20Token.getBalanceAsync(makerTokenAddress, makerAddress);
|
|
let takerBalance = await contractWrappers.erc20Token.getBalanceAsync(makerTokenAddress, takerAddress);
|
|
const feeRecipientEthBalanceBefore = await web3Wrapper.getBalanceInWeiAsync(feeRecipient);
|
|
expect(makerBalance).to.bignumber.equal((new BigNumber(10)).multipliedBy(ONE_ETH_IN_WEI));
|
|
expect(takerBalance).to.bignumber.equal(constants.ZERO_AMOUNT);
|
|
await swapQuoteConsumer.executeSwapQuoteOrThrowAsync(marketBuySwapQuote, { takerAddress, feePercentage: 0.05, feeRecipient });
|
|
makerBalance = await contractWrappers.erc20Token.getBalanceAsync(makerTokenAddress, makerAddress);
|
|
takerBalance = await contractWrappers.erc20Token.getBalanceAsync(makerTokenAddress, takerAddress);
|
|
const feeRecipientEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(feeRecipient);
|
|
expect(makerBalance).to.bignumber.equal(constants.ZERO_AMOUNT);
|
|
expect(takerBalance).to.bignumber.equal((new BigNumber(10)).multipliedBy(ONE_ETH_IN_WEI));
|
|
expect(feeRecipientEthBalanceAfter.minus(feeRecipientEthBalanceBefore)).to.bignumber.equal((new BigNumber(0.5)).multipliedBy(ONE_ETH_IN_WEI));
|
|
});
|
|
|
|
// TODO(david) Finish marketSell affiliate fee excution testing
|
|
// it('should perform a marketSell execution with affiliate fees', async () => {
|
|
// let makerBalance = await contractWrappers.erc20Token.getBalanceAsync(makerTokenAddress, makerAddress);
|
|
// let takerBalance = await contractWrappers.erc20Token.getBalanceAsync(makerTokenAddress, takerAddress);
|
|
// const feeRecipientEthBalanceBefore = await web3Wrapper.getBalanceInWeiAsync(feeRecipient);
|
|
// expect(makerBalance).to.bignumber.equal((new BigNumber(10)).multipliedBy(ONE_ETH_IN_WEI));
|
|
// expect(takerBalance).to.bignumber.equal(constants.ZERO_AMOUNT);
|
|
// console.log(makerBalance, takerBalance, feeRecipientEthBalanceBefore);
|
|
// await swapQuoteConsumer.executeSwapQuoteOrThrowAsync(marketSellSwapQuote, { takerAddress, feePercentage: 0.05, feeRecipient });
|
|
// makerBalance = await contractWrappers.erc20Token.getBalanceAsync(makerTokenAddress, makerAddress);
|
|
// takerBalance = await contractWrappers.erc20Token.getBalanceAsync(makerTokenAddress, takerAddress);
|
|
// const feeRecipientEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(feeRecipient);
|
|
// console.log(makerBalance, takerBalance, feeRecipientEthBalanceAfter);
|
|
// expect(makerBalance).to.bignumber.equal((new BigNumber(0.5)).multipliedBy(ONE_ETH_IN_WEI));
|
|
// expect(takerBalance).to.bignumber.equal((new BigNumber(9.5)).multipliedBy(ONE_ETH_IN_WEI));
|
|
// expect(feeRecipientEthBalanceAfter.minus(feeRecipientEthBalanceBefore)).to.bignumber.equal((new BigNumber(0.5)).multipliedBy(ONE_ETH_IN_WEI));
|
|
// });
|
|
});
|
|
});
|
|
|
|
describe('getSmartContractParamsOrThrow', () => {
|
|
|
|
describe('validation', () => {
|
|
it('should throw if swap quote provided is not a valid forwarder SwapQuote (taker asset is WETH)', async () => {
|
|
const invalidSignedOrders = await getSignedOrdersWithNoFeesAsync(
|
|
provider,
|
|
makerAssetData,
|
|
takerAssetData,
|
|
makerAddress,
|
|
takerAddress,
|
|
FILLABLE_AMOUNTS,
|
|
);
|
|
const invalidSwapQuote = getFullyFillableSwapQuoteWithNoFees(
|
|
makerAssetData,
|
|
takerAssetData,
|
|
invalidSignedOrders,
|
|
MARKET_OPERATION,
|
|
);
|
|
expect(
|
|
swapQuoteConsumer.getSmartContractParamsOrThrowAsync(invalidSwapQuote, { }),
|
|
).to.be.rejectedWith(`Expected quote.orders[0] to have takerAssetData set as ${wethAssetData}, but is ${takerAssetData}`);
|
|
});
|
|
});
|
|
|
|
describe('valid swap quote', async () => {
|
|
it('provide correct and optimized smart contract params with default options for a marketSell SwapQuote (no affiliate fees)', async () => {
|
|
const { to, params } = await swapQuoteConsumer.getSmartContractParamsOrThrowAsync(marketSellSwapQuote, {});
|
|
expect(to).to.deep.equal(contractWrappers.forwarder.address);
|
|
const { feeSignatures, feePercentage, feeRecipient: feeRecipientFromParams, signatures, type } = params as ForwarderMarketSellSmartContractParams;
|
|
expect(type).to.deep.equal(MarketOperation.Sell);
|
|
expect(feeRecipientFromParams).to.deep.equal(constants.NULL_ADDRESS);
|
|
const orderSignatures = marketSellSwapQuote.orders.map(order => order.signature);
|
|
expect(signatures).to.deep.equal(orderSignatures);
|
|
expect(feePercentage).to.bignumber.equal(0);
|
|
expect(feeSignatures).to.deep.equal([]);
|
|
});
|
|
it('provide correct and optimized smart contract params with default options for a marketBuy SwapQuote (no affiliate fees)', async () => {
|
|
const { to, params } = await swapQuoteConsumer.getSmartContractParamsOrThrowAsync(marketBuySwapQuote, {});
|
|
expect(to).to.deep.equal(contractWrappers.forwarder.address);
|
|
const { makerAssetFillAmount, feeSignatures, feePercentage, feeRecipient: feeRecipientFromParams, signatures, type } = params as ForwarderMarketBuySmartContractParams;
|
|
expect(type).to.deep.equal(MarketOperation.Buy);
|
|
expect(feeRecipientFromParams).to.deep.equal(constants.NULL_ADDRESS);
|
|
expect(makerAssetFillAmount).to.bignumber.equal((marketBuySwapQuote as MarketBuySwapQuote).makerAssetFillAmount);
|
|
const orderSignatures = marketBuySwapQuote.orders.map(order => order.signature);
|
|
expect(signatures).to.deep.equal(orderSignatures);
|
|
expect(feePercentage).to.bignumber.equal(0);
|
|
expect(feeSignatures).to.deep.equal([]);
|
|
});
|
|
it('provide correct and optimized smart contract params with affiliate fees for a marketSell SwapQuote', async () => {
|
|
const { to, params } = await swapQuoteConsumer.getSmartContractParamsOrThrowAsync(marketSellSwapQuote, { feePercentage: 0.05, feeRecipient });
|
|
expect(to).to.deep.equal(contractWrappers.forwarder.address);
|
|
const { feeSignatures, feePercentage, feeRecipient: feeRecipientFromParams, signatures, type } = params as ForwarderMarketSellSmartContractParams;
|
|
expect(type).to.deep.equal(MarketOperation.Sell);
|
|
expect(feeRecipientFromParams).to.deep.equal(feeRecipient);
|
|
const orderSignatures = marketSellSwapQuote.orders.map(order => order.signature);
|
|
expect(signatures).to.deep.equal(orderSignatures);
|
|
expect(feePercentage).to.bignumber.equal((new BigNumber(0.05)).multipliedBy(ONE_ETH_IN_WEI));
|
|
expect(feeSignatures).to.deep.equal([]);
|
|
});
|
|
it('provide correct and optimized smart contract params with affiliate fees for a marketBuy SwapQuote', async () => {
|
|
const { to, params } = await swapQuoteConsumer.getSmartContractParamsOrThrowAsync(marketBuySwapQuote, { feePercentage: 0.05, feeRecipient });
|
|
expect(to).to.deep.equal(contractWrappers.forwarder.address);
|
|
const { makerAssetFillAmount, feeSignatures, feePercentage, feeRecipient: feeRecipientFromParams, signatures, type } = params as ForwarderMarketBuySmartContractParams;
|
|
expect(type).to.deep.equal(MarketOperation.Buy);
|
|
expect(feeRecipientFromParams).to.deep.equal(feeRecipient);
|
|
expect(makerAssetFillAmount).to.bignumber.equal((marketBuySwapQuote as MarketBuySwapQuote).makerAssetFillAmount);
|
|
const orderSignatures = marketBuySwapQuote.orders.map(order => order.signature);
|
|
expect(signatures).to.deep.equal(orderSignatures);
|
|
expect(feePercentage).to.bignumber.equal((new BigNumber(0.05)).multipliedBy(ONE_ETH_IN_WEI));
|
|
expect(feeSignatures).to.deep.equal([]);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('getCalldataOrThrow', () => {
|
|
describe('validation', () => {
|
|
it('should throw if swap quote provided is not a valid forwarder SwapQuote (taker asset is WETH)', async () => {
|
|
const invalidSignedOrders = await getSignedOrdersWithNoFeesAsync(
|
|
provider,
|
|
makerAssetData,
|
|
takerAssetData,
|
|
makerAddress,
|
|
takerAddress,
|
|
FILLABLE_AMOUNTS,
|
|
);
|
|
const invalidSwapQuote = getFullyFillableSwapQuoteWithNoFees(
|
|
makerAssetData,
|
|
takerAssetData,
|
|
invalidSignedOrders,
|
|
MARKET_OPERATION,
|
|
);
|
|
expect(
|
|
swapQuoteConsumer.getCalldataOrThrowAsync(invalidSwapQuote, { }),
|
|
).to.be.rejectedWith(`Expected quote.orders[0] to have takerAssetData set as ${wethAssetData}, but is ${takerAssetData}`);
|
|
});
|
|
});
|
|
|
|
describe('valid swap quote', async () => {
|
|
it('provide correct and optimized calldata options with default options for a marketSell SwapQuote (no affiliate fees)', async () => {
|
|
let makerBalance = await contractWrappers.erc20Token.getBalanceAsync(makerTokenAddress, makerAddress);
|
|
let takerBalance = await contractWrappers.erc20Token.getBalanceAsync(makerTokenAddress, takerAddress);
|
|
expect(makerBalance).to.bignumber.equal((new BigNumber(10)).multipliedBy(ONE_ETH_IN_WEI));
|
|
expect(takerBalance).to.bignumber.equal(constants.ZERO_AMOUNT);
|
|
const { calldataHexString, to } = await swapQuoteConsumer.getCalldataOrThrowAsync(marketSellSwapQuote, {});
|
|
expect(to).to.deep.equal(contractWrappers.forwarder.address);
|
|
await web3Wrapper.sendTransactionAsync({
|
|
from: takerAddress,
|
|
to,
|
|
data: calldataHexString,
|
|
value: marketSellSwapQuote.worstCaseQuoteInfo.totalTakerTokenAmount,
|
|
gas: 4000000,
|
|
});
|
|
makerBalance = await contractWrappers.erc20Token.getBalanceAsync(makerTokenAddress, makerAddress);
|
|
takerBalance = await contractWrappers.erc20Token.getBalanceAsync(makerTokenAddress, takerAddress);
|
|
expect(makerBalance).to.bignumber.equal((new BigNumber(0.5).multipliedBy(ONE_ETH_IN_WEI)));
|
|
expect(takerBalance).to.bignumber.equal((new BigNumber(9.5)).multipliedBy(ONE_ETH_IN_WEI));
|
|
});
|
|
it('provide correct and optimized calldata options with default options for a marketBuy SwapQuote (no affiliate fees)', async () => {
|
|
let makerBalance = await contractWrappers.erc20Token.getBalanceAsync(makerTokenAddress, makerAddress);
|
|
let takerBalance = await contractWrappers.erc20Token.getBalanceAsync(makerTokenAddress, takerAddress);
|
|
expect(makerBalance).to.bignumber.equal((new BigNumber(10)).multipliedBy(ONE_ETH_IN_WEI));
|
|
expect(takerBalance).to.bignumber.equal(constants.ZERO_AMOUNT);
|
|
const { calldataHexString, to } = await swapQuoteConsumer.getCalldataOrThrowAsync(marketBuySwapQuote, {});
|
|
expect(to).to.deep.equal(contractAddresses.forwarder);
|
|
await web3Wrapper.sendTransactionAsync({
|
|
from: takerAddress,
|
|
to,
|
|
data: calldataHexString,
|
|
value: marketBuySwapQuote.worstCaseQuoteInfo.totalTakerTokenAmount,
|
|
gas: 4000000,
|
|
});
|
|
makerBalance = await contractWrappers.erc20Token.getBalanceAsync(makerTokenAddress, makerAddress);
|
|
takerBalance = await contractWrappers.erc20Token.getBalanceAsync(makerTokenAddress, takerAddress);
|
|
expect(takerBalance).to.bignumber.equal((new BigNumber(10)).multipliedBy(ONE_ETH_IN_WEI));
|
|
expect(makerBalance).to.bignumber.equal(constants.ZERO_AMOUNT);
|
|
});
|
|
// TODO(david) finish testing for affiliate fees calldata output
|
|
// it('provide correct and optimized calldata options with affiliate fees for a marketSell SwapQuote', async () => {
|
|
// });
|
|
// it('provide correct and optimized calldata options with affiliate fees for a marketBuy SwapQuote', async () => {
|
|
// });
|
|
});
|
|
});
|
|
});
|