feat: v4 final (#136)

* v4 FillQuoteTransformer (#104)

* Update FQT to support v4 orders

* `@0x/contracts-zero-ex`: Tweak FQT
`@0x/contracts-zero-ex`: Drop `ERC20BridgeTransfer` event and add `PartialQuoteFill` event.

* `@0x/contracts-utils`: Add `LibSafeMathV06.downcastToUint128()`

* `@0x/protocol-utils`: Update transformer utils for V4 FQT

* `@0x/contracts-zero-ex`: Fixing FQT tests...

* `@0x/contracts-zero-ex`: rename FQT bridge event

* `@0x/contracts-zero-ex`: Un-`only` tests

* `@0x/migrations`: Update `BridgeAdapter` deployment

* `@0x/contracts-integrations`: Delete `mtx_tests`

* `@0x/protocol-utils`: Address review comments

* `@0x/contracts-zero-ex`: Address review comments

* `@0x/migrations`: Update migrations

Co-authored-by: Michael Zhu <mchl.zhu.96@gmail.com>
Co-authored-by: Lawrence Forman <me@merklejerk.com>

* v4: Asset-swapper (main branch) (#113)

* refactor quote_requestor

* WIP v4/asset-swapper: Clean up SwapQuoter and remove @0x/orderbook

* Start replacing SignedOrder everywhere

* wip: new order type

* wip

* remove order-utils from most places

* hack: Play around with VerboseX types (#119)

* hack: Play around with VerboseX types

* More hacks

* Fix up the bridgeData encodings

* Rework Orderbook return type

* feat: Don't charge a protocol fee for RFQ orders WIP (#121)

* fix simple build errors

* simplify types a little

* remove SwapQuoteCalculator: unnecessary abstraction

* Fix all ./src build errors; make types consistent

* export more types for use in 0x API; modify Orderbook interface

* stop overriding APIOrder

* feat: RFQ v4 + consolidated bridge encoders (#125)

* feat: check if taker address is contract

* Rework bridge data

* Worst case adjustments

* RFQT v4

* Future/v4 validate orders (#126)

* RFQT v4

* v4 validate native orders

* use default invalid signature

* refactor rfqt validations in swap quoter

* fix types

* fix RFQT unlisted api key

* remove priceAwareRFQFlag

* adjust maker/taker amounts

* update JSON schemas

* filter zero fillable orders

Co-authored-by: xianny <xianny@gmail.com>

* fix type export

Co-authored-by: xianny <xianny@gmail.com>

* remove order-utils as much as possible

* work on tests compile

* Comment out quote reporter test

* updated tests

* restore order-utils accidental changes

* some lints

* Remove old fill_test

* ts lint disable for now

* update quote report

* Re-enable quote report tests

* make fill data required field

* fix lint

* type guards

* force fillData as required

* fix lint

* fix naming

* exports

* adjust MultiBridge by slippage

* cleanups (checkpoint 1)

* cleanup types (checkpoint #2)

* remove unused deps

* `@0x/contract-addresses`: Deploy new FQT (#129)

Co-authored-by: Lawrence Forman <me@merklejerk.com>

* commit bump to republish

* DRY up the rfqt mocker

* fix: Balancer load top pools (#131)

* fix: Balancer load top 250 pools

* refetch top pools on an interval

Co-authored-by: Jacob Evans <jacob@dekz.net>
Co-authored-by: Kim Persson <kimpers@users.noreply.github.com>
Co-authored-by: Lawrence Forman <lawrence@0xproject.com>
Co-authored-by: Lawrence Forman <me@merklejerk.com>

* Update post rebase

* prettier

* Remove test helpers exported in asset-swapper

* Clean up from review comments

* prettier

* lint

* recreate rfqt mocker

* change merge and INVALID_SIGNATURE

Co-authored-by: Lawrence Forman <lawrence@0xproject.com>
Co-authored-by: Michael Zhu <mchl.zhu.96@gmail.com>
Co-authored-by: Lawrence Forman <me@merklejerk.com>
Co-authored-by: Xianny <8582774+xianny@users.noreply.github.com>
Co-authored-by: Kim Persson <kimpers@users.noreply.github.com>
This commit is contained in:
Jacob Evans
2021-02-10 19:20:15 +10:00
committed by GitHub
parent 501b7b9b65
commit 3f4bb933d1
115 changed files with 5654 additions and 9332 deletions

View File

@@ -1,239 +0,0 @@
import { MarketBuySwapQuote, MarketSellSwapQuote, Orderbook, SwapQuoter } from '@0x/asset-swapper';
import { blockchainTests, expect, Numberish } from '@0x/contracts-test-utils';
import { assetDataUtils } from '@0x/order-utils';
import { FillResults, SignedOrder } from '@0x/types';
import { BigNumber, logUtils } from '@0x/utils';
import * as _ from 'lodash';
import { TestMainnetAggregatorFillsContract } from '../wrappers';
import { tokens } from './tokens';
blockchainTests.live('Aggregator Mainnet Tests', env => {
// Mainnet address of the `TestMainnetAggregatorFills` contract.
const TEST_CONTRACT_ADDRESS = '0x37Ca306F42748b7fe105F89FCBb2CD03D27c8146';
const TAKER_ADDRESS = '0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B'; // Vitalik
const ORDERBOOK_POLLING_MS = 1000;
const GAS_PRICE = new BigNumber(1);
const TAKER_ASSET_ETH_VALUE = 500e18;
const MIN_BALANCE = 500.1e18;
const SYMBOLS = ['ETH', 'DAI', 'USDC', 'FOAM'];
const TEST_PAIRS = _.flatten(SYMBOLS.map(m => SYMBOLS.filter(t => t !== m).map(t => [m, t])));
const FILL_VALUES = [1, 10, 1e2, 1e3, 1e4, 2.5e4, 5e4];
let testContract: TestMainnetAggregatorFillsContract;
let swapQuoter: SwapQuoter;
let takerEthBalance: BigNumber;
const orderbooks: { [name: string]: Orderbook } = {};
async function getTakerOrdersAsync(takerAssetSymbol: string): Promise<SignedOrder[]> {
if (takerAssetSymbol === 'ETH') {
return [];
}
return getOrdersAsync(takerAssetSymbol, 'ETH');
}
// Fetches ETH -> taker asset orders for the forwarder contract.
async function getOrdersAsync(makerAssetSymbol: string, takerAssetSymbol: string): Promise<SignedOrder[]> {
const takerTokenAddress = tokens[takerAssetSymbol].address;
const makerTokenAddress = tokens[makerAssetSymbol].address;
const makerAssetData = assetDataUtils.encodeERC20AssetData(makerTokenAddress);
const takerAssetData = assetDataUtils.encodeERC20AssetData(takerTokenAddress);
const orders = _.flatten(
await Promise.all(
Object.keys(orderbooks).map(async name =>
getOrdersFromOrderBookAsync(name, makerAssetData, takerAssetData),
),
),
);
const uniqueOrders: SignedOrder[] = [];
for (const order of orders) {
if (!order.makerFee.eq(0) || !order.takerFee.eq(0)) {
continue;
}
if (uniqueOrders.findIndex(o => isSameOrder(order, o)) === -1) {
uniqueOrders.push(order);
}
}
return uniqueOrders;
}
async function getOrdersFromOrderBookAsync(
name: string,
makerAssetData: string,
takerAssetData: string,
): Promise<SignedOrder[]> {
try {
return (await orderbooks[name].getOrdersAsync(makerAssetData, takerAssetData)).map(r => r.order);
} catch (err) {
logUtils.warn(`Failed to retrieve orders from orderbook "${name}".`);
}
return [];
}
function isSameOrder(a: SignedOrder, b: SignedOrder): boolean {
for (const [k, v] of Object.entries(a)) {
if (k in (b as any)) {
if (BigNumber.isBigNumber(v) && !v.eq((b as any)[k])) {
return false;
}
if (v !== (b as any)[k]) {
return false;
}
}
}
return true;
}
function toTokenUnits(symbol: string, weis: Numberish): BigNumber {
return new BigNumber(weis).div(new BigNumber(10).pow(tokens[symbol].decimals));
}
function fromTokenUnits(symbol: string, units: Numberish): BigNumber {
return new BigNumber(units)
.times(new BigNumber(10).pow(tokens[symbol].decimals))
.integerValue(BigNumber.ROUND_DOWN);
}
interface MarketOperationResult {
makerAssetBalanceBefore: BigNumber;
takerAssetBalanceBefore: BigNumber;
makerAssetBalanceAfter: BigNumber;
takerAssetBalanceAfter: BigNumber;
fillResults: FillResults;
}
// Liquidity is low right now so it's possible we didn't have
// enough taker assets to cover the orders, so occasionally we'll get incomplete
// fills. This function will catch those cases.
// TODO(dorothy-zbornak): Remove this special case when liquidity is up.
function checkHadEnoughTakerAsset(
quote: MarketBuySwapQuote | MarketSellSwapQuote,
result: MarketOperationResult,
): boolean {
if (result.takerAssetBalanceBefore.gte(quote.worstCaseQuoteInfo.takerAssetAmount)) {
return true;
}
const takerAssetPct = result.takerAssetBalanceBefore
.div(quote.worstCaseQuoteInfo.takerAssetAmount)
.times(100)
.toNumber()
.toFixed(1);
logUtils.warn(`Could not acquire enough taker asset to complete the fill: ${takerAssetPct}%`);
expect(result.fillResults.makerAssetFilledAmount).to.bignumber.lt(quote.worstCaseQuoteInfo.makerAssetAmount);
return false;
}
before(async () => {
testContract = new TestMainnetAggregatorFillsContract(TEST_CONTRACT_ADDRESS, env.provider, {
...env.txDefaults,
gasPrice: GAS_PRICE,
gas: 10e6,
});
swapQuoter = SwapQuoter.getSwapQuoterForStandardRelayerAPIUrl(env.provider, 'https://api.0x.org/sra');
// Pool orderbooks because we're desperate for liquidity.
orderbooks.swapQuoter = swapQuoter.orderbook;
orderbooks.bamboo = Orderbook.getOrderbookForPollingProvider({
httpEndpoint: 'https://sra.bamboorelay.com/0x/v3',
pollingIntervalMs: ORDERBOOK_POLLING_MS,
});
// TODO(dorothy-zbornak): Uncomment when radar's SRA is up.
// orderbooks.radar = Orderbook.getOrderbookForPollingProvider({
// httpEndpoint: 'https://api-v3.radarrelay.com/v3',
// pollingIntervalMs: ORDERBOOK_POLLING_MS,
// });
takerEthBalance = await env.web3Wrapper.getBalanceInWeiAsync(TAKER_ADDRESS);
});
it('taker has minimum ETH', async () => {
expect(takerEthBalance).to.bignumber.gte(MIN_BALANCE);
});
describe('market sells', () => {
for (const [makerSymbol, takerSymbol] of TEST_PAIRS) {
for (const fillValue of FILL_VALUES) {
const fillAmount = fromTokenUnits(takerSymbol, new BigNumber(fillValue).div(tokens[takerSymbol].price));
it(`sell ${toTokenUnits(takerSymbol, fillAmount)} ${takerSymbol} for ${makerSymbol}`, async () => {
const [quote, takerOrders] = await Promise.all([
swapQuoter.getMarketSellSwapQuoteAsync(
tokens[makerSymbol].address,
tokens[takerSymbol].address,
fillAmount,
{ gasPrice: GAS_PRICE },
),
getTakerOrdersAsync(takerSymbol),
]);
// Buy taker assets from `takerOrders` and and perform a
// market sell on the bridge orders.
const fill = await testContract
.marketSell(
tokens[makerSymbol].address,
tokens[takerSymbol].address,
quote.orders,
takerOrders,
quote.orders.map(o => o.signature),
takerOrders.map(o => o.signature),
quote.takerAssetFillAmount,
)
.callAsync({
value: quote.worstCaseQuoteInfo.protocolFeeInWeiAmount.plus(TAKER_ASSET_ETH_VALUE),
from: TAKER_ADDRESS,
gasPrice: quote.gasPrice,
});
if (checkHadEnoughTakerAsset(quote, fill)) {
expect(fill.fillResults.makerAssetFilledAmount, 'makerAssetFilledAmount').to.bignumber.gte(
quote.worstCaseQuoteInfo.makerAssetAmount,
);
expect(fill.fillResults.takerAssetFilledAmount, 'takerAssetFilledAmount').to.bignumber.lte(
quote.takerAssetFillAmount,
);
}
});
}
}
});
describe('market buys', () => {
for (const [makerSymbol, takerSymbol] of TEST_PAIRS) {
for (const fillValue of FILL_VALUES) {
const fillAmount = fromTokenUnits(makerSymbol, new BigNumber(fillValue).div(tokens[makerSymbol].price));
it(`buy ${toTokenUnits(makerSymbol, fillAmount)} ${makerSymbol} with ${takerSymbol}`, async () => {
const [quote, takerOrders] = await Promise.all([
swapQuoter.getMarketBuySwapQuoteAsync(
tokens[makerSymbol].address,
tokens[takerSymbol].address,
fillAmount,
{ gasPrice: GAS_PRICE },
),
getTakerOrdersAsync(takerSymbol),
]);
// Buy taker assets from `takerOrders` and and perform a
// market buy on the bridge orders.
const fill = await testContract
.marketBuy(
tokens[makerSymbol].address,
tokens[takerSymbol].address,
quote.orders,
takerOrders,
quote.orders.map(o => o.signature),
takerOrders.map(o => o.signature),
quote.makerAssetFillAmount,
)
.callAsync({
value: quote.worstCaseQuoteInfo.protocolFeeInWeiAmount.plus(TAKER_ASSET_ETH_VALUE),
from: TAKER_ADDRESS,
gasPrice: quote.gasPrice,
});
if (checkHadEnoughTakerAsset(quote, fill)) {
expect(fill.fillResults.takerAssetFilledAmount, 'takerAssetFilledAmount').to.bignumber.lte(
quote.worstCaseQuoteInfo.takerAssetAmount,
);
expect(fill.fillResults.makerAssetFilledAmount, 'makerAssetFilledAmount').to.bignumber.gte(
quote.makerAssetFillAmount,
);
}
});
}
}
});
});

View File

@@ -1,77 +0,0 @@
export const tokens: { [symbol: string]: { address: string; decimals: number; price: number } } = {
ETH: {
address: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
decimals: 18,
price: 133,
},
SAI: {
address: '0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359',
decimals: 18,
price: 1,
},
DAI: {
address: '0x6B175474E89094C44Da98b954EedeAC495271d0F',
decimals: 18,
price: 1,
},
USDC: {
address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
decimals: 6,
price: 1,
},
WBTC: {
address: '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599',
decimals: 8,
price: 6900,
},
MKR: {
address: '0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2',
decimals: 18,
price: 454,
},
BAT: {
address: '0x0D8775F648430679A709E98d2b0Cb6250d2887EF',
decimals: 18,
price: 0.17,
},
OMG: {
address: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07',
decimals: 18,
price: 0.65,
},
ZRX: {
address: '0xE41d2489571d322189246DaFA5ebDe1F4699F498',
decimals: 18,
price: 0.19,
},
ZIL: {
address: '0x05f4a42e251f2d52b8ed15E9FEdAacFcEF1FAD27',
decimals: 12,
price: 0.004,
},
FOAM: {
address: '0x4946Fcea7C692606e8908002e55A582af44AC121',
decimals: 18,
price: 0.004,
},
USDT: {
address: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
decimals: 6,
price: 0.019,
},
REP: {
address: '0x1985365e9f78359a9B6AD760e32412f4a445E862',
decimals: 18,
price: 8.9,
},
MANA: {
address: '0x0F5D2fB29fb7d3CFeE444a200298f468908cC942',
decimals: 18,
price: 0.025,
},
LINK: {
address: '0x514910771AF9Ca656af840dff83E8264EcF986CA',
decimals: 18,
price: 1.8,
},
};

View File

@@ -1,360 +0,0 @@
import { ContractAddresses } from '@0x/contract-addresses';
import { artifacts as erc20Artifacts, DummyERC20TokenContract } from '@0x/contracts-erc20';
import { IExchangeContract } from '@0x/contracts-exchange';
import { blockchainTests, constants, expect, getRandomPortion, verifyEventsFromLogs } from '@0x/contracts-test-utils';
import {
artifacts as exchangeProxyArtifacts,
IZeroExContract,
LogMetadataTransformerContract,
} from '@0x/contracts-zero-ex';
import { migrateOnceAsync } from '@0x/migrations';
import { assetDataUtils, signatureUtils, SignedExchangeProxyMetaTransaction } from '@0x/order-utils';
import {
encodeFillQuoteTransformerData,
encodePayTakerTransformerData,
ETH_TOKEN_ADDRESS,
FillQuoteTransformerSide,
findTransformerNonce,
Signature,
} from '@0x/protocol-utils';
import { AssetProxyId, Order, SignedOrder } from '@0x/types';
import { BigNumber, hexUtils } from '@0x/utils';
import * as ethjs from 'ethereumjs-util';
const { MAX_UINT256, NULL_ADDRESS, NULL_BYTES, ZERO_AMOUNT } = constants;
function sigstruct(signature: string): Signature {
return {
v: parseInt(hexUtils.slice(signature, 0, 1), 16),
signatureType: parseInt(hexUtils.slice(signature, 65, 66), 16),
r: hexUtils.slice(signature, 1, 33),
s: hexUtils.slice(signature, 33, 65),
};
}
blockchainTests.resets('exchange proxy - meta-transactions', env => {
const quoteSignerKey = hexUtils.random();
const quoteSigner = hexUtils.toHex(ethjs.privateToAddress(ethjs.toBuffer(quoteSignerKey)));
let owner: string;
let relayer: string;
let maker: string;
let taker: string;
let flashWalletAddress: string;
let zeroEx: IZeroExContract;
let exchange: IExchangeContract;
let inputToken: DummyERC20TokenContract;
let outputToken: DummyERC20TokenContract;
let feeToken: DummyERC20TokenContract;
let addresses: ContractAddresses;
let protocolFee: BigNumber;
let metadataTransformer: LogMetadataTransformerContract;
const GAS_PRICE = new BigNumber('1e9');
const MAKER_BALANCE = new BigNumber('100e18');
const TAKER_BALANCE = new BigNumber('100e18');
const TAKER_FEE_BALANCE = new BigNumber('100e18');
before(async () => {
[, relayer, maker, taker] = await env.getAccountAddressesAsync();
addresses = await migrateOnceAsync(env.provider);
zeroEx = new IZeroExContract(addresses.exchangeProxy, env.provider, env.txDefaults, {
LogMetadataTransformer: LogMetadataTransformerContract.ABI(),
DummyERC20Token: DummyERC20TokenContract.ABI(),
});
exchange = new IExchangeContract(addresses.exchange, env.provider, env.txDefaults);
[inputToken, outputToken, feeToken] = await Promise.all(
[...new Array(3)].map(i =>
DummyERC20TokenContract.deployFrom0xArtifactAsync(
erc20Artifacts.DummyERC20Token,
env.provider,
env.txDefaults,
{},
`DummyToken-${i}`,
`TOK${i}`,
new BigNumber(18),
BigNumber.max(MAKER_BALANCE, TAKER_BALANCE),
),
),
);
// LogMetadataTransformer is not deployed in migrations.
metadataTransformer = await LogMetadataTransformerContract.deployFrom0xArtifactAsync(
exchangeProxyArtifacts.LogMetadataTransformer,
env.provider,
{
...env.txDefaults,
from: addresses.exchangeProxyTransformerDeployer,
},
{},
);
owner = await zeroEx.owner().callAsync();
protocolFee = await exchange.protocolFeeMultiplier().callAsync();
flashWalletAddress = await zeroEx.getTransformWallet().callAsync();
const erc20Proxy = await exchange.getAssetProxy(AssetProxyId.ERC20).callAsync();
const allowanceTarget = await zeroEx.getAllowanceTarget().callAsync();
await outputToken.mint(MAKER_BALANCE).awaitTransactionSuccessAsync({ from: maker });
await inputToken.mint(TAKER_BALANCE).awaitTransactionSuccessAsync({ from: taker });
await feeToken.mint(TAKER_FEE_BALANCE).awaitTransactionSuccessAsync({ from: taker });
await outputToken.approve(erc20Proxy, MAX_UINT256).awaitTransactionSuccessAsync({ from: maker });
await inputToken.approve(allowanceTarget, MAX_UINT256).awaitTransactionSuccessAsync({ from: taker });
await feeToken.approve(allowanceTarget, MAX_UINT256).awaitTransactionSuccessAsync({ from: taker });
await zeroEx.setQuoteSigner(quoteSigner).awaitTransactionSuccessAsync({ from: owner });
});
interface Transformation {
deploymentNonce: number;
data: string;
}
interface SwapInfo {
inputTokenAddress: string;
outputTokenAddress: string;
inputTokenAmount: BigNumber;
minOutputTokenAmount: BigNumber;
transformations: Transformation[];
orders: SignedOrder[];
}
async function generateSwapAsync(orderFields: Partial<Order> = {}, isRfqt: boolean = false): Promise<SwapInfo> {
const order = await signatureUtils.ecSignTypedDataOrderAsync(
env.provider,
{
chainId: 1337,
exchangeAddress: exchange.address,
expirationTimeSeconds: new BigNumber(Date.now()),
salt: new BigNumber(hexUtils.random()),
feeRecipientAddress: NULL_ADDRESS,
senderAddress: NULL_ADDRESS,
takerAddress: isRfqt ? flashWalletAddress : NULL_ADDRESS,
makerAddress: maker,
makerAssetData: assetDataUtils.encodeERC20AssetData(outputToken.address),
takerAssetData: assetDataUtils.encodeERC20AssetData(inputToken.address),
makerFeeAssetData: NULL_BYTES,
takerFeeAssetData: NULL_BYTES,
takerAssetAmount: getRandomPortion(TAKER_BALANCE),
makerAssetAmount: getRandomPortion(MAKER_BALANCE),
makerFee: ZERO_AMOUNT,
takerFee: ZERO_AMOUNT,
...orderFields,
},
maker,
);
const transformations = [
{
deploymentNonce: findTransformerNonce(
addresses.transformers.fillQuoteTransformer,
addresses.exchangeProxyTransformerDeployer,
),
data: encodeFillQuoteTransformerData({
orders: [order],
signatures: [order.signature],
buyToken: outputToken.address,
sellToken: inputToken.address,
fillAmount: order.takerAssetAmount,
maxOrderFillAmounts: [],
refundReceiver: hexUtils.leftPad(2, 20), // Send refund to sender.
rfqtTakerAddress: isRfqt ? taker : NULL_ADDRESS,
side: FillQuoteTransformerSide.Sell,
}),
},
{
deploymentNonce: findTransformerNonce(
addresses.transformers.payTakerTransformer,
addresses.exchangeProxyTransformerDeployer,
),
data: encodePayTakerTransformerData({
tokens: [inputToken.address, outputToken.address, ETH_TOKEN_ADDRESS],
amounts: [MAX_UINT256, MAX_UINT256, MAX_UINT256],
}),
},
{
deploymentNonce: findTransformerNonce(
metadataTransformer.address,
addresses.exchangeProxyTransformerDeployer,
),
data: NULL_BYTES,
},
];
return {
transformations,
orders: [order],
inputTokenAddress: inputToken.address,
outputTokenAddress: outputToken.address,
inputTokenAmount: order.takerAssetAmount,
minOutputTokenAmount: order.makerAssetAmount,
};
}
function getSwapData(swap: SwapInfo): string {
return zeroEx
.transformERC20(
swap.inputTokenAddress,
swap.outputTokenAddress,
swap.inputTokenAmount,
swap.minOutputTokenAmount,
swap.transformations,
)
.getABIEncodedTransactionData();
}
async function createMetaTransactionAsync(
data: string,
value: BigNumber,
fee?: BigNumber | number,
): Promise<SignedExchangeProxyMetaTransaction> {
return signatureUtils.ecSignTypedDataExchangeProxyMetaTransactionAsync(
env.provider,
{
value,
signer: taker,
sender: relayer,
minGasPrice: GAS_PRICE,
maxGasPrice: GAS_PRICE,
expirationTimeSeconds: new BigNumber(Math.floor(Date.now() / 1000) + 60),
salt: new BigNumber(hexUtils.random()),
callData: data,
feeToken: feeToken.address,
feeAmount: fee !== undefined ? new BigNumber(fee) : getRandomPortion(TAKER_FEE_BALANCE),
domain: {
chainId: 1,
name: 'ZeroEx',
version: '1.0.0',
verifyingContract: zeroEx.address,
},
},
taker,
);
}
it('can call `transformERC20()` with calldata and no relayer fee', async () => {
const swap = await generateSwapAsync();
const _protocolFee = protocolFee.times(GAS_PRICE).times(swap.orders.length + 1); // Pay a little more fee than needed.
const mtx = await createMetaTransactionAsync(getSwapData(swap), _protocolFee, 0);
const relayerEthBalanceBefore = await env.web3Wrapper.getBalanceInWeiAsync(relayer);
const receipt = await zeroEx
.executeMetaTransaction(mtx, sigstruct(mtx.signature))
.awaitTransactionSuccessAsync({ from: relayer, value: mtx.value, gasPrice: GAS_PRICE });
const relayerEthRefund = relayerEthBalanceBefore
.minus(await env.web3Wrapper.getBalanceInWeiAsync(relayer))
.minus(GAS_PRICE.times(receipt.gasUsed));
// Ensure the relayer got back the unused protocol fees.
expect(relayerEthRefund).to.bignumber.eq(protocolFee.times(GAS_PRICE));
// Ensure the relayer got paid no mtx fees.
expect(await feeToken.balanceOf(relayer).callAsync()).to.bignumber.eq(0);
// Ensure the taker got output tokens.
expect(await outputToken.balanceOf(taker).callAsync()).to.bignumber.eq(swap.minOutputTokenAmount);
// Ensure the maker got input tokens.
expect(await inputToken.balanceOf(maker).callAsync()).to.bignumber.eq(swap.inputTokenAmount);
// Check events.
verifyEventsFromLogs(
receipt.logs,
[
{
taker,
sender: zeroEx.address,
data: NULL_BYTES,
},
],
'TransformerMetadata',
);
});
it('can call `transformERC20()` with calldata and a relayer fee', async () => {
const swap = await generateSwapAsync();
const _protocolFee = protocolFee.times(GAS_PRICE).times(swap.orders.length + 1); // Pay a little more fee than needed.
const mtx = await createMetaTransactionAsync(getSwapData(swap), _protocolFee);
const relayerEthBalanceBefore = await env.web3Wrapper.getBalanceInWeiAsync(relayer);
const receipt = await zeroEx
.executeMetaTransaction(mtx, sigstruct(mtx.signature))
.awaitTransactionSuccessAsync({ from: relayer, value: mtx.value, gasPrice: GAS_PRICE });
const relayerEthRefund = relayerEthBalanceBefore
.minus(await env.web3Wrapper.getBalanceInWeiAsync(relayer))
.minus(GAS_PRICE.times(receipt.gasUsed));
// Ensure the relayer got back the unused protocol fees.
expect(relayerEthRefund).to.bignumber.eq(protocolFee.times(GAS_PRICE));
// Ensure the relayer got paid mtx fees.
expect(await feeToken.balanceOf(relayer).callAsync()).to.bignumber.eq(mtx.feeAmount);
// Ensure the taker got output tokens.
expect(await outputToken.balanceOf(taker).callAsync()).to.bignumber.eq(swap.minOutputTokenAmount);
// Ensure the maker got input tokens.
expect(await inputToken.balanceOf(maker).callAsync()).to.bignumber.eq(swap.inputTokenAmount);
// Check events.
verifyEventsFromLogs(
receipt.logs,
[
{
taker,
sender: zeroEx.address,
data: NULL_BYTES,
},
],
'TransformerMetadata',
);
});
it('`transformERC20()` can fill RFQT order', async () => {
const swap = await generateSwapAsync({}, true);
const _protocolFee = protocolFee.times(GAS_PRICE).times(swap.orders.length + 1); // Pay a little more fee than needed.
const mtx = await createMetaTransactionAsync(getSwapData(swap), _protocolFee, 0);
const relayerEthBalanceBefore = await env.web3Wrapper.getBalanceInWeiAsync(relayer);
const receipt = await zeroEx
.executeMetaTransaction(mtx, sigstruct(mtx.signature))
.awaitTransactionSuccessAsync({ from: relayer, value: mtx.value, gasPrice: GAS_PRICE });
const relayerEthRefund = relayerEthBalanceBefore
.minus(await env.web3Wrapper.getBalanceInWeiAsync(relayer))
.minus(GAS_PRICE.times(receipt.gasUsed));
// Ensure the relayer got back the unused protocol fees.
expect(relayerEthRefund).to.bignumber.eq(protocolFee.times(GAS_PRICE));
// Ensure the relayer got paid no mtx fees.
expect(await feeToken.balanceOf(relayer).callAsync()).to.bignumber.eq(0);
// Ensure the taker got output tokens.
expect(await outputToken.balanceOf(taker).callAsync()).to.bignumber.eq(swap.minOutputTokenAmount);
// Ensure the maker got input tokens.
expect(await inputToken.balanceOf(maker).callAsync()).to.bignumber.eq(swap.inputTokenAmount);
// Check events.
verifyEventsFromLogs(
receipt.logs,
[
{
taker,
sender: zeroEx.address,
data: NULL_BYTES,
},
],
'TransformerMetadata',
);
});
it('`transformERC20()` can fill RFQT order if quote signer configured', async () => {
const swap = await generateSwapAsync({}, true);
const callData = getSwapData(swap);
const _protocolFee = protocolFee.times(GAS_PRICE).times(swap.orders.length + 1); // Pay a little more fee than needed.
const mtx = await createMetaTransactionAsync(callData, _protocolFee, 0);
const relayerEthBalanceBefore = await env.web3Wrapper.getBalanceInWeiAsync(relayer);
await zeroEx.setQuoteSigner(NULL_ADDRESS).awaitTransactionSuccessAsync({ from: owner });
const receipt = await zeroEx
.executeMetaTransaction(mtx, sigstruct(mtx.signature))
.awaitTransactionSuccessAsync({ from: relayer, value: mtx.value, gasPrice: GAS_PRICE });
const relayerEthRefund = relayerEthBalanceBefore
.minus(await env.web3Wrapper.getBalanceInWeiAsync(relayer))
.minus(GAS_PRICE.times(receipt.gasUsed));
// Ensure the relayer got back the unused protocol fees.
expect(relayerEthRefund).to.bignumber.eq(protocolFee.times(GAS_PRICE));
// Ensure the relayer got paid no mtx fees.
expect(await feeToken.balanceOf(relayer).callAsync()).to.bignumber.eq(0);
// Ensure the taker got output tokens.
expect(await outputToken.balanceOf(taker).callAsync()).to.bignumber.eq(swap.minOutputTokenAmount);
// Ensure the maker got input tokens.
expect(await inputToken.balanceOf(maker).callAsync()).to.bignumber.eq(swap.inputTokenAmount);
// Check events.
verifyEventsFromLogs(
receipt.logs,
[
{
taker,
sender: zeroEx.address,
data: NULL_BYTES,
},
],
'TransformerMetadata',
);
});
});