@0x/contracts-zero-ex: Introduce transformer contracts.
This commit is contained in:
committed by
Lawrence Forman
parent
0e1a5a375a
commit
2ba3818b65
@@ -0,0 +1,849 @@
|
||||
import {
|
||||
assertIntegerRoughlyEquals,
|
||||
blockchainTests,
|
||||
constants,
|
||||
expect,
|
||||
getRandomInteger,
|
||||
Numberish,
|
||||
randomAddress,
|
||||
} from '@0x/contracts-test-utils';
|
||||
import { assetDataUtils } from '@0x/order-utils';
|
||||
import { Order } from '@0x/types';
|
||||
import { BigNumber, hexUtils, ZeroExRevertErrors } from '@0x/utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { encodeFillQuoteTransformerData, FillQuoteTransformerData } from '../../src/transformer_data_encoders';
|
||||
import { artifacts } from '../artifacts';
|
||||
import {
|
||||
FillQuoteTransformerContract,
|
||||
TestFillQuoteTransformerExchangeContract,
|
||||
TestFillQuoteTransformerHostContract,
|
||||
TestMintableERC20TokenContract,
|
||||
} from '../wrappers';
|
||||
|
||||
const { NULL_ADDRESS, NULL_BYTES, MAX_UINT256, ZERO_AMOUNT } = constants;
|
||||
|
||||
blockchainTests.resets('FillQuoteTransformer', env => {
|
||||
let maker: string;
|
||||
let feeRecipient: string;
|
||||
let exchange: TestFillQuoteTransformerExchangeContract;
|
||||
let transformer: FillQuoteTransformerContract;
|
||||
let host: TestFillQuoteTransformerHostContract;
|
||||
let makerToken: TestMintableERC20TokenContract;
|
||||
let takerToken: TestMintableERC20TokenContract;
|
||||
let takerFeeToken: TestMintableERC20TokenContract;
|
||||
let singleProtocolFee: BigNumber;
|
||||
|
||||
const GAS_PRICE = 1337;
|
||||
|
||||
before(async () => {
|
||||
[maker, feeRecipient] = await env.getAccountAddressesAsync();
|
||||
exchange = await TestFillQuoteTransformerExchangeContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestFillQuoteTransformerExchange,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
artifacts,
|
||||
);
|
||||
transformer = await FillQuoteTransformerContract.deployFrom0xArtifactAsync(
|
||||
artifacts.FillQuoteTransformer,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
artifacts,
|
||||
exchange.address,
|
||||
);
|
||||
host = await TestFillQuoteTransformerHostContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestFillQuoteTransformerHost,
|
||||
env.provider,
|
||||
{
|
||||
...env.txDefaults,
|
||||
gasPrice: GAS_PRICE,
|
||||
},
|
||||
artifacts,
|
||||
);
|
||||
[makerToken, takerToken, takerFeeToken] = await Promise.all(
|
||||
_.times(3, async () =>
|
||||
TestMintableERC20TokenContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestMintableERC20Token,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
artifacts,
|
||||
),
|
||||
),
|
||||
);
|
||||
singleProtocolFee = (await exchange.protocolFeeMultiplier().callAsync()).times(GAS_PRICE);
|
||||
});
|
||||
|
||||
type FilledOrder = Order & { filledTakerAssetAmount: BigNumber };
|
||||
|
||||
function createOrder(fields: Partial<Order> = {}): FilledOrder {
|
||||
return {
|
||||
chainId: 1,
|
||||
exchangeAddress: exchange.address,
|
||||
expirationTimeSeconds: ZERO_AMOUNT,
|
||||
salt: ZERO_AMOUNT,
|
||||
senderAddress: NULL_ADDRESS,
|
||||
takerAddress: NULL_ADDRESS,
|
||||
makerAddress: maker,
|
||||
feeRecipientAddress: feeRecipient,
|
||||
makerAssetAmount: getRandomInteger('0.1e18', '1e18'),
|
||||
takerAssetAmount: getRandomInteger('0.1e18', '1e18'),
|
||||
makerFee: ZERO_AMOUNT,
|
||||
takerFee: getRandomInteger('0.001e18', '0.1e18'),
|
||||
makerAssetData: assetDataUtils.encodeERC20AssetData(makerToken.address),
|
||||
takerAssetData: assetDataUtils.encodeERC20AssetData(takerToken.address),
|
||||
makerFeeAssetData: NULL_BYTES,
|
||||
takerFeeAssetData: assetDataUtils.encodeERC20AssetData(takerToken.address),
|
||||
filledTakerAssetAmount: ZERO_AMOUNT,
|
||||
...fields,
|
||||
};
|
||||
}
|
||||
|
||||
interface QuoteFillResults {
|
||||
makerAssetBought: BigNumber;
|
||||
takerAssetSpent: BigNumber;
|
||||
protocolFeePaid: BigNumber;
|
||||
}
|
||||
|
||||
const ZERO_QUOTE_FILL_RESULTS = {
|
||||
makerAssetBought: ZERO_AMOUNT,
|
||||
takerAssetSpent: ZERO_AMOUNT,
|
||||
protocolFeePaid: ZERO_AMOUNT,
|
||||
};
|
||||
|
||||
function getExpectedSellQuoteFillResults(
|
||||
orders: FilledOrder[],
|
||||
takerAssetFillAmount: BigNumber = constants.MAX_UINT256,
|
||||
): QuoteFillResults {
|
||||
const qfr = { ...ZERO_QUOTE_FILL_RESULTS };
|
||||
for (const order of orders) {
|
||||
if (qfr.takerAssetSpent.gte(takerAssetFillAmount)) {
|
||||
break;
|
||||
}
|
||||
const singleFillAmount = BigNumber.min(
|
||||
takerAssetFillAmount.minus(qfr.takerAssetSpent),
|
||||
order.takerAssetAmount.minus(order.filledTakerAssetAmount),
|
||||
);
|
||||
const fillRatio = singleFillAmount.div(order.takerAssetAmount);
|
||||
qfr.takerAssetSpent = qfr.takerAssetSpent.plus(singleFillAmount);
|
||||
qfr.protocolFeePaid = qfr.protocolFeePaid.plus(singleProtocolFee);
|
||||
qfr.makerAssetBought = qfr.makerAssetBought.plus(
|
||||
fillRatio.times(order.makerAssetAmount).integerValue(BigNumber.ROUND_DOWN),
|
||||
);
|
||||
const takerFee = fillRatio.times(order.takerFee).integerValue(BigNumber.ROUND_DOWN);
|
||||
if (order.takerAssetData === order.takerFeeAssetData) {
|
||||
// Taker fee is in taker asset.
|
||||
qfr.takerAssetSpent = qfr.takerAssetSpent.plus(takerFee);
|
||||
} else if (order.makerAssetData === order.takerFeeAssetData) {
|
||||
// Taker fee is in maker asset.
|
||||
qfr.makerAssetBought = qfr.makerAssetBought.minus(takerFee);
|
||||
}
|
||||
}
|
||||
return qfr;
|
||||
}
|
||||
|
||||
function getExpectedBuyQuoteFillResults(
|
||||
orders: FilledOrder[],
|
||||
makerAssetFillAmount: BigNumber = constants.MAX_UINT256,
|
||||
): QuoteFillResults {
|
||||
const qfr = { ...ZERO_QUOTE_FILL_RESULTS };
|
||||
for (const order of orders) {
|
||||
if (qfr.makerAssetBought.gte(makerAssetFillAmount)) {
|
||||
break;
|
||||
}
|
||||
const filledMakerAssetAmount = order.filledTakerAssetAmount
|
||||
.times(order.makerAssetAmount.div(order.takerAssetAmount))
|
||||
.integerValue(BigNumber.ROUND_DOWN);
|
||||
const singleFillAmount = BigNumber.min(
|
||||
makerAssetFillAmount.minus(qfr.makerAssetBought),
|
||||
order.makerAssetAmount.minus(filledMakerAssetAmount),
|
||||
);
|
||||
const fillRatio = singleFillAmount.div(order.makerAssetAmount);
|
||||
qfr.takerAssetSpent = qfr.takerAssetSpent.plus(
|
||||
fillRatio.times(order.takerAssetAmount).integerValue(BigNumber.ROUND_UP),
|
||||
);
|
||||
qfr.protocolFeePaid = qfr.protocolFeePaid.plus(singleProtocolFee);
|
||||
qfr.makerAssetBought = qfr.makerAssetBought.plus(singleFillAmount);
|
||||
const takerFee = fillRatio.times(order.takerFee).integerValue(BigNumber.ROUND_UP);
|
||||
if (order.takerAssetData === order.takerFeeAssetData) {
|
||||
// Taker fee is in taker asset.
|
||||
qfr.takerAssetSpent = qfr.takerAssetSpent.plus(takerFee);
|
||||
} else if (order.makerAssetData === order.takerFeeAssetData) {
|
||||
// Taker fee is in maker asset.
|
||||
qfr.makerAssetBought = qfr.makerAssetBought.minus(takerFee);
|
||||
}
|
||||
}
|
||||
return qfr;
|
||||
}
|
||||
|
||||
interface Balances {
|
||||
makerAssetBalance: BigNumber;
|
||||
takerAssetBalance: BigNumber;
|
||||
takerFeeBalance: BigNumber;
|
||||
protocolFeeBalance: BigNumber;
|
||||
}
|
||||
|
||||
const ZERO_BALANCES = {
|
||||
makerAssetBalance: ZERO_AMOUNT,
|
||||
takerAssetBalance: ZERO_AMOUNT,
|
||||
takerFeeBalance: ZERO_AMOUNT,
|
||||
protocolFeeBalance: ZERO_AMOUNT,
|
||||
};
|
||||
|
||||
async function getBalancesAsync(owner: string): Promise<Balances> {
|
||||
const balances = { ...ZERO_BALANCES };
|
||||
[
|
||||
balances.makerAssetBalance,
|
||||
balances.takerAssetBalance,
|
||||
balances.takerFeeBalance,
|
||||
balances.protocolFeeBalance,
|
||||
] = await Promise.all([
|
||||
makerToken.balanceOf(owner).callAsync(),
|
||||
takerToken.balanceOf(owner).callAsync(),
|
||||
takerFeeToken.balanceOf(owner).callAsync(),
|
||||
env.web3Wrapper.getBalanceInWeiAsync(owner),
|
||||
]);
|
||||
return balances;
|
||||
}
|
||||
|
||||
function assertBalances(actual: Balances, expected: Balances): void {
|
||||
assertIntegerRoughlyEquals(actual.makerAssetBalance, expected.makerAssetBalance, 10, 'makerAssetBalance');
|
||||
assertIntegerRoughlyEquals(actual.takerAssetBalance, expected.takerAssetBalance, 10, 'takerAssetBalance');
|
||||
assertIntegerRoughlyEquals(actual.takerFeeBalance, expected.takerFeeBalance, 10, 'takerFeeBalance');
|
||||
assertIntegerRoughlyEquals(actual.protocolFeeBalance, expected.protocolFeeBalance, 10, 'protocolFeeBalance');
|
||||
}
|
||||
|
||||
function encodeTransformData(fields: Partial<FillQuoteTransformerData> = {}): string {
|
||||
return encodeFillQuoteTransformerData({
|
||||
sellToken: takerToken.address,
|
||||
buyToken: makerToken.address,
|
||||
orders: [],
|
||||
signatures: [],
|
||||
maxOrderFillAmounts: [],
|
||||
sellAmount: MAX_UINT256,
|
||||
buyAmount: ZERO_AMOUNT,
|
||||
...fields,
|
||||
});
|
||||
}
|
||||
|
||||
function encodeExchangeBehavior(
|
||||
filledTakerAssetAmount: Numberish = 0,
|
||||
makerAssetMintRatio: Numberish = 1.0,
|
||||
): string {
|
||||
return hexUtils.slice(
|
||||
exchange
|
||||
.encodeBehaviorData({
|
||||
filledTakerAssetAmount: new BigNumber(filledTakerAssetAmount),
|
||||
makerAssetMintRatio: new BigNumber(makerAssetMintRatio).times('1e18').integerValue(),
|
||||
})
|
||||
.getABIEncodedTransactionData(),
|
||||
4,
|
||||
);
|
||||
}
|
||||
|
||||
const ERC20_ASSET_PROXY_ID = '0xf47261b0';
|
||||
|
||||
describe('sell quotes', () => {
|
||||
it('can fully sell to a single order quote', async () => {
|
||||
const orders = _.times(1, () => createOrder());
|
||||
const signatures = orders.map(() => encodeExchangeBehavior());
|
||||
const qfr = getExpectedSellQuoteFillResults(orders);
|
||||
await host
|
||||
.executeTransform(
|
||||
transformer.address,
|
||||
takerToken.address,
|
||||
qfr.takerAssetSpent,
|
||||
encodeTransformData({
|
||||
orders,
|
||||
signatures,
|
||||
}),
|
||||
)
|
||||
.awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid });
|
||||
assertBalances(await getBalancesAsync(host.address), {
|
||||
...ZERO_BALANCES,
|
||||
makerAssetBalance: qfr.makerAssetBought,
|
||||
});
|
||||
});
|
||||
|
||||
it('can fully sell to multi order quote', async () => {
|
||||
const orders = _.times(3, () => createOrder());
|
||||
const signatures = orders.map(() => encodeExchangeBehavior());
|
||||
const qfr = getExpectedSellQuoteFillResults(orders);
|
||||
await host
|
||||
.executeTransform(
|
||||
transformer.address,
|
||||
takerToken.address,
|
||||
qfr.takerAssetSpent,
|
||||
encodeTransformData({
|
||||
orders,
|
||||
signatures,
|
||||
}),
|
||||
)
|
||||
.awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid });
|
||||
assertBalances(await getBalancesAsync(host.address), {
|
||||
...ZERO_BALANCES,
|
||||
makerAssetBalance: qfr.makerAssetBought,
|
||||
});
|
||||
});
|
||||
|
||||
it('can partially sell to single order quote', async () => {
|
||||
const orders = _.times(1, () => createOrder());
|
||||
const signatures = orders.map(() => encodeExchangeBehavior());
|
||||
const qfr = getExpectedSellQuoteFillResults(
|
||||
orders,
|
||||
getExpectedSellQuoteFillResults(orders).takerAssetSpent.dividedToIntegerBy(2),
|
||||
);
|
||||
await host
|
||||
.executeTransform(
|
||||
transformer.address,
|
||||
takerToken.address,
|
||||
qfr.takerAssetSpent,
|
||||
encodeTransformData({
|
||||
orders,
|
||||
signatures,
|
||||
}),
|
||||
)
|
||||
.awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid });
|
||||
assertBalances(await getBalancesAsync(host.address), {
|
||||
...ZERO_BALANCES,
|
||||
makerAssetBalance: qfr.makerAssetBought,
|
||||
});
|
||||
});
|
||||
|
||||
it('can partially sell to multi order quote and refund unused protocol fees', async () => {
|
||||
const orders = _.times(3, () => createOrder());
|
||||
const signatures = orders.map(() => encodeExchangeBehavior());
|
||||
const qfr = getExpectedSellQuoteFillResults(orders.slice(0, 2));
|
||||
const maxProtocolFees = singleProtocolFee.times(orders.length);
|
||||
await host
|
||||
.executeTransform(
|
||||
transformer.address,
|
||||
takerToken.address,
|
||||
qfr.takerAssetSpent,
|
||||
encodeTransformData({
|
||||
orders,
|
||||
signatures,
|
||||
}),
|
||||
)
|
||||
.awaitTransactionSuccessAsync({ value: maxProtocolFees });
|
||||
assertBalances(await getBalancesAsync(host.address), {
|
||||
...ZERO_BALANCES,
|
||||
makerAssetBalance: qfr.makerAssetBought,
|
||||
protocolFeeBalance: singleProtocolFee,
|
||||
});
|
||||
});
|
||||
|
||||
it('can sell to multi order quote with a failing order', async () => {
|
||||
const orders = _.times(3, () => createOrder());
|
||||
// First order will fail.
|
||||
const validOrders = orders.slice(1);
|
||||
const signatures = [NULL_BYTES, ...validOrders.map(() => encodeExchangeBehavior())];
|
||||
const qfr = getExpectedSellQuoteFillResults(validOrders);
|
||||
await host
|
||||
.executeTransform(
|
||||
transformer.address,
|
||||
takerToken.address,
|
||||
qfr.takerAssetSpent,
|
||||
encodeTransformData({
|
||||
orders,
|
||||
signatures,
|
||||
}),
|
||||
)
|
||||
.awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid });
|
||||
assertBalances(await getBalancesAsync(host.address), {
|
||||
...ZERO_BALANCES,
|
||||
makerAssetBalance: qfr.makerAssetBought,
|
||||
});
|
||||
});
|
||||
|
||||
it('succeeds if an order transfers too few maker tokens', async () => {
|
||||
const mintScale = 0.5;
|
||||
const orders = _.times(3, () => createOrder());
|
||||
// First order mints less than expected.
|
||||
const signatures = [
|
||||
encodeExchangeBehavior(0, mintScale),
|
||||
...orders.slice(1).map(() => encodeExchangeBehavior()),
|
||||
];
|
||||
const qfr = getExpectedSellQuoteFillResults(orders);
|
||||
await host
|
||||
.executeTransform(
|
||||
transformer.address,
|
||||
takerToken.address,
|
||||
qfr.takerAssetSpent,
|
||||
encodeTransformData({
|
||||
orders,
|
||||
signatures,
|
||||
}),
|
||||
)
|
||||
.awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid });
|
||||
assertBalances(await getBalancesAsync(host.address), {
|
||||
...ZERO_BALANCES,
|
||||
makerAssetBalance: qfr.makerAssetBought
|
||||
.minus(orders[0].makerAssetAmount.times(1 - mintScale))
|
||||
.integerValue(BigNumber.ROUND_DOWN),
|
||||
});
|
||||
});
|
||||
|
||||
it('can fail if an order is partially filled', async () => {
|
||||
const orders = _.times(3, () => createOrder());
|
||||
// First order is partially filled.
|
||||
const filledOrder = {
|
||||
...orders[0],
|
||||
filledTakerAssetAmount: orders[0].takerAssetAmount.dividedToIntegerBy(2),
|
||||
};
|
||||
// First order is partially filled.
|
||||
const signatures = [
|
||||
encodeExchangeBehavior(filledOrder.filledTakerAssetAmount),
|
||||
...orders.slice(1).map(() => encodeExchangeBehavior()),
|
||||
];
|
||||
const qfr = getExpectedSellQuoteFillResults(orders);
|
||||
const tx = host
|
||||
.executeTransform(
|
||||
transformer.address,
|
||||
takerToken.address,
|
||||
qfr.takerAssetSpent,
|
||||
encodeTransformData({
|
||||
orders,
|
||||
signatures,
|
||||
}),
|
||||
)
|
||||
.awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid });
|
||||
return expect(tx).to.revertWith(
|
||||
new ZeroExRevertErrors.TransformERC20.IncompleteFillSellQuoteError(
|
||||
takerToken.address,
|
||||
getExpectedSellQuoteFillResults([filledOrder, ...orders.slice(1)]).takerAssetSpent,
|
||||
qfr.takerAssetSpent,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('fails if not enough protocol fee provided', async () => {
|
||||
const orders = _.times(3, () => createOrder());
|
||||
const signatures = orders.map(() => encodeExchangeBehavior());
|
||||
const qfr = getExpectedSellQuoteFillResults(orders);
|
||||
const tx = host
|
||||
.executeTransform(
|
||||
transformer.address,
|
||||
takerToken.address,
|
||||
qfr.takerAssetSpent,
|
||||
encodeTransformData({
|
||||
orders,
|
||||
signatures,
|
||||
}),
|
||||
)
|
||||
.awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid.minus(1) });
|
||||
return expect(tx).to.revertWith(
|
||||
new ZeroExRevertErrors.TransformERC20.InsufficientProtocolFeeError(
|
||||
singleProtocolFee.minus(1),
|
||||
singleProtocolFee,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('can sell less than the taker token balance', async () => {
|
||||
const orders = _.times(3, () => createOrder());
|
||||
const signatures = orders.map(() => encodeExchangeBehavior());
|
||||
const qfr = getExpectedSellQuoteFillResults(orders);
|
||||
const takerTokenBalance = qfr.takerAssetSpent.times(1.01).integerValue();
|
||||
await host
|
||||
.executeTransform(
|
||||
transformer.address,
|
||||
takerToken.address,
|
||||
takerTokenBalance,
|
||||
encodeTransformData({
|
||||
orders,
|
||||
signatures,
|
||||
sellAmount: qfr.takerAssetSpent,
|
||||
}),
|
||||
)
|
||||
.awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid });
|
||||
assertBalances(await getBalancesAsync(host.address), {
|
||||
...ZERO_BALANCES,
|
||||
makerAssetBalance: qfr.makerAssetBought,
|
||||
takerAssetBalance: qfr.takerAssetSpent.times(0.01).integerValue(),
|
||||
});
|
||||
});
|
||||
|
||||
it('fails to sell more than the taker token balance', async () => {
|
||||
const orders = _.times(3, () => createOrder());
|
||||
const signatures = orders.map(() => encodeExchangeBehavior());
|
||||
const qfr = getExpectedSellQuoteFillResults(orders);
|
||||
const takerTokenBalance = qfr.takerAssetSpent.times(0.99).integerValue();
|
||||
const tx = host
|
||||
.executeTransform(
|
||||
transformer.address,
|
||||
takerToken.address,
|
||||
takerTokenBalance,
|
||||
encodeTransformData({
|
||||
orders,
|
||||
signatures,
|
||||
sellAmount: qfr.takerAssetSpent,
|
||||
}),
|
||||
)
|
||||
.awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid });
|
||||
return expect(tx).to.revertWith(
|
||||
new ZeroExRevertErrors.TransformERC20.IncompleteFillSellQuoteError(
|
||||
takerToken.address,
|
||||
getExpectedSellQuoteFillResults(orders.slice(0, 2)).takerAssetSpent,
|
||||
qfr.takerAssetSpent,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('can fully sell to a single order with maker asset taker fees', async () => {
|
||||
const orders = _.times(1, () =>
|
||||
createOrder({
|
||||
takerFeeAssetData: assetDataUtils.encodeERC20AssetData(makerToken.address),
|
||||
}),
|
||||
);
|
||||
const signatures = orders.map(() => encodeExchangeBehavior());
|
||||
const qfr = getExpectedSellQuoteFillResults(orders);
|
||||
await host
|
||||
.executeTransform(
|
||||
transformer.address,
|
||||
takerToken.address,
|
||||
qfr.takerAssetSpent,
|
||||
encodeTransformData({
|
||||
orders,
|
||||
signatures,
|
||||
}),
|
||||
)
|
||||
.awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid });
|
||||
assertBalances(await getBalancesAsync(host.address), {
|
||||
...ZERO_BALANCES,
|
||||
makerAssetBalance: qfr.makerAssetBought,
|
||||
});
|
||||
});
|
||||
|
||||
it('fails if an order has a non-standard taker fee asset', async () => {
|
||||
const BAD_ASSET_DATA = hexUtils.random(36);
|
||||
const orders = _.times(1, () => createOrder({ takerFeeAssetData: BAD_ASSET_DATA }));
|
||||
const signatures = orders.map(() => encodeExchangeBehavior());
|
||||
const qfr = getExpectedSellQuoteFillResults(orders);
|
||||
const tx = host
|
||||
.executeTransform(
|
||||
transformer.address,
|
||||
takerToken.address,
|
||||
qfr.takerAssetSpent,
|
||||
encodeTransformData({
|
||||
orders,
|
||||
signatures,
|
||||
}),
|
||||
)
|
||||
.awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid });
|
||||
return expect(tx).to.revertWith(
|
||||
new ZeroExRevertErrors.TransformERC20.InvalidERC20AssetDataError(BAD_ASSET_DATA),
|
||||
);
|
||||
});
|
||||
|
||||
it('fails if an order has a fee asset that is neither maker or taker asset', async () => {
|
||||
const badToken = randomAddress();
|
||||
const BAD_ASSET_DATA = hexUtils.concat(ERC20_ASSET_PROXY_ID, hexUtils.leftPad(badToken));
|
||||
const orders = _.times(1, () => createOrder({ takerFeeAssetData: BAD_ASSET_DATA }));
|
||||
const signatures = orders.map(() => encodeExchangeBehavior());
|
||||
const qfr = getExpectedSellQuoteFillResults(orders);
|
||||
const tx = host
|
||||
.executeTransform(
|
||||
transformer.address,
|
||||
takerToken.address,
|
||||
qfr.takerAssetSpent,
|
||||
encodeTransformData({
|
||||
orders,
|
||||
signatures,
|
||||
}),
|
||||
)
|
||||
.awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid });
|
||||
return expect(tx).to.revertWith(new ZeroExRevertErrors.TransformERC20.InvalidTakerFeeTokenError(badToken));
|
||||
});
|
||||
|
||||
it('respects `maxOrderFillAmounts`', async () => {
|
||||
const orders = _.times(2, () => createOrder());
|
||||
const signatures = orders.map(() => encodeExchangeBehavior());
|
||||
const qfr = getExpectedSellQuoteFillResults(orders.slice(1));
|
||||
const protocolFee = singleProtocolFee.times(2);
|
||||
await host
|
||||
.executeTransform(
|
||||
transformer.address,
|
||||
takerToken.address,
|
||||
qfr.takerAssetSpent,
|
||||
encodeTransformData({
|
||||
orders,
|
||||
signatures,
|
||||
// Skip the first order.
|
||||
maxOrderFillAmounts: [ZERO_AMOUNT],
|
||||
}),
|
||||
)
|
||||
.awaitTransactionSuccessAsync({ value: protocolFee });
|
||||
assertBalances(await getBalancesAsync(host.address), {
|
||||
...ZERO_BALANCES,
|
||||
makerAssetBalance: qfr.makerAssetBought,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('buy quotes', () => {
|
||||
it('can fully buy from a single order quote', async () => {
|
||||
const orders = _.times(1, () => createOrder());
|
||||
const signatures = orders.map(() => encodeExchangeBehavior());
|
||||
const qfr = getExpectedBuyQuoteFillResults(orders);
|
||||
await host
|
||||
.executeTransform(
|
||||
transformer.address,
|
||||
takerToken.address,
|
||||
qfr.takerAssetSpent,
|
||||
encodeTransformData({
|
||||
orders,
|
||||
signatures,
|
||||
buyAmount: qfr.makerAssetBought,
|
||||
}),
|
||||
)
|
||||
.awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid });
|
||||
assertBalances(await getBalancesAsync(host.address), {
|
||||
...ZERO_BALANCES,
|
||||
makerAssetBalance: qfr.makerAssetBought,
|
||||
});
|
||||
});
|
||||
|
||||
it('can fully buy from a multi order quote', async () => {
|
||||
const orders = _.times(3, () => createOrder());
|
||||
const signatures = orders.map(() => encodeExchangeBehavior());
|
||||
const qfr = getExpectedBuyQuoteFillResults(orders);
|
||||
await host
|
||||
.executeTransform(
|
||||
transformer.address,
|
||||
takerToken.address,
|
||||
qfr.takerAssetSpent,
|
||||
encodeTransformData({
|
||||
orders,
|
||||
signatures,
|
||||
buyAmount: qfr.makerAssetBought,
|
||||
}),
|
||||
)
|
||||
.awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid });
|
||||
assertBalances(await getBalancesAsync(host.address), {
|
||||
...ZERO_BALANCES,
|
||||
makerAssetBalance: qfr.makerAssetBought,
|
||||
});
|
||||
});
|
||||
|
||||
it('can partially buy from a single order quote', async () => {
|
||||
const orders = _.times(1, () => createOrder());
|
||||
const signatures = orders.map(() => encodeExchangeBehavior());
|
||||
const qfr = getExpectedBuyQuoteFillResults(
|
||||
orders,
|
||||
getExpectedBuyQuoteFillResults(orders).makerAssetBought.dividedToIntegerBy(2),
|
||||
);
|
||||
await host
|
||||
.executeTransform(
|
||||
transformer.address,
|
||||
takerToken.address,
|
||||
qfr.takerAssetSpent,
|
||||
encodeTransformData({
|
||||
orders,
|
||||
signatures,
|
||||
buyAmount: qfr.makerAssetBought,
|
||||
}),
|
||||
)
|
||||
.awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid });
|
||||
assertBalances(await getBalancesAsync(host.address), {
|
||||
...ZERO_BALANCES,
|
||||
makerAssetBalance: qfr.makerAssetBought,
|
||||
});
|
||||
});
|
||||
|
||||
it('can partially buy from multi order quote and refund unused protocol fees', async () => {
|
||||
const orders = _.times(3, () => createOrder());
|
||||
const signatures = orders.map(() => encodeExchangeBehavior());
|
||||
const qfr = getExpectedBuyQuoteFillResults(orders.slice(0, 2));
|
||||
const maxProtocolFees = singleProtocolFee.times(orders.length);
|
||||
await host
|
||||
.executeTransform(
|
||||
transformer.address,
|
||||
takerToken.address,
|
||||
qfr.takerAssetSpent,
|
||||
encodeTransformData({
|
||||
orders,
|
||||
signatures,
|
||||
buyAmount: qfr.makerAssetBought,
|
||||
}),
|
||||
)
|
||||
.awaitTransactionSuccessAsync({ value: maxProtocolFees });
|
||||
assertBalances(await getBalancesAsync(host.address), {
|
||||
...ZERO_BALANCES,
|
||||
makerAssetBalance: qfr.makerAssetBought,
|
||||
protocolFeeBalance: singleProtocolFee,
|
||||
});
|
||||
});
|
||||
|
||||
it('can buy from multi order quote with a failing order', async () => {
|
||||
const orders = _.times(3, () => createOrder());
|
||||
// First order will fail.
|
||||
const validOrders = orders.slice(1);
|
||||
const signatures = [NULL_BYTES, ...validOrders.map(() => encodeExchangeBehavior())];
|
||||
const qfr = getExpectedBuyQuoteFillResults(validOrders);
|
||||
await host
|
||||
.executeTransform(
|
||||
transformer.address,
|
||||
takerToken.address,
|
||||
qfr.takerAssetSpent,
|
||||
encodeTransformData({
|
||||
orders,
|
||||
signatures,
|
||||
buyAmount: qfr.makerAssetBought,
|
||||
}),
|
||||
)
|
||||
.awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid });
|
||||
assertBalances(await getBalancesAsync(host.address), {
|
||||
...ZERO_BALANCES,
|
||||
makerAssetBalance: qfr.makerAssetBought,
|
||||
});
|
||||
});
|
||||
|
||||
it('succeeds if an order transfers too many maker tokens', async () => {
|
||||
const orders = _.times(2, () => createOrder());
|
||||
// First order will mint its tokens + the maker tokens of the second.
|
||||
const mintScale = orders[1].makerAssetAmount.div(orders[0].makerAssetAmount.minus(1)).plus(1);
|
||||
const signatures = [
|
||||
encodeExchangeBehavior(0, mintScale),
|
||||
...orders.slice(1).map(() => encodeExchangeBehavior()),
|
||||
];
|
||||
const qfr = getExpectedBuyQuoteFillResults(orders);
|
||||
await host
|
||||
.executeTransform(
|
||||
transformer.address,
|
||||
takerToken.address,
|
||||
qfr.takerAssetSpent,
|
||||
encodeTransformData({
|
||||
orders,
|
||||
signatures,
|
||||
buyAmount: qfr.makerAssetBought,
|
||||
}),
|
||||
)
|
||||
.awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid });
|
||||
assertBalances(await getBalancesAsync(host.address), {
|
||||
...ZERO_BALANCES,
|
||||
makerAssetBalance: orders[0].makerAssetAmount.times(mintScale).integerValue(BigNumber.ROUND_DOWN),
|
||||
takerAssetBalance: orders[1].takerAssetAmount.plus(orders[1].takerFee),
|
||||
protocolFeeBalance: singleProtocolFee,
|
||||
});
|
||||
});
|
||||
|
||||
it('fails to buy more than available in orders', async () => {
|
||||
const orders = _.times(3, () => createOrder());
|
||||
const signatures = orders.map(() => encodeExchangeBehavior());
|
||||
const qfr = getExpectedBuyQuoteFillResults(orders);
|
||||
const tx = host
|
||||
.executeTransform(
|
||||
transformer.address,
|
||||
takerToken.address,
|
||||
qfr.takerAssetSpent,
|
||||
encodeTransformData({
|
||||
orders,
|
||||
signatures,
|
||||
buyAmount: qfr.makerAssetBought.plus(1),
|
||||
}),
|
||||
)
|
||||
.awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid });
|
||||
return expect(tx).to.revertWith(
|
||||
new ZeroExRevertErrors.TransformERC20.IncompleteFillBuyQuoteError(
|
||||
makerToken.address,
|
||||
qfr.makerAssetBought,
|
||||
qfr.makerAssetBought.plus(1),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('can fully buy from a single order with maker asset taker fees', async () => {
|
||||
const orders = _.times(1, () =>
|
||||
createOrder({
|
||||
takerFeeAssetData: assetDataUtils.encodeERC20AssetData(makerToken.address),
|
||||
}),
|
||||
);
|
||||
const signatures = orders.map(() => encodeExchangeBehavior());
|
||||
const qfr = getExpectedBuyQuoteFillResults(orders);
|
||||
await host
|
||||
.executeTransform(
|
||||
transformer.address,
|
||||
takerToken.address,
|
||||
qfr.takerAssetSpent,
|
||||
encodeTransformData({
|
||||
orders,
|
||||
signatures,
|
||||
buyAmount: qfr.makerAssetBought,
|
||||
}),
|
||||
)
|
||||
.awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid });
|
||||
assertBalances(await getBalancesAsync(host.address), {
|
||||
...ZERO_BALANCES,
|
||||
makerAssetBalance: qfr.makerAssetBought,
|
||||
});
|
||||
});
|
||||
|
||||
it('fails if an order has a non-standard taker fee asset', async () => {
|
||||
const BAD_ASSET_DATA = hexUtils.random(36);
|
||||
const orders = _.times(1, () => createOrder({ takerFeeAssetData: BAD_ASSET_DATA }));
|
||||
const signatures = orders.map(() => encodeExchangeBehavior());
|
||||
const qfr = getExpectedSellQuoteFillResults(orders);
|
||||
const tx = host
|
||||
.executeTransform(
|
||||
transformer.address,
|
||||
takerToken.address,
|
||||
qfr.takerAssetSpent,
|
||||
encodeTransformData({
|
||||
orders,
|
||||
signatures,
|
||||
buyAmount: qfr.makerAssetBought,
|
||||
}),
|
||||
)
|
||||
.awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid });
|
||||
return expect(tx).to.revertWith(
|
||||
new ZeroExRevertErrors.TransformERC20.InvalidERC20AssetDataError(BAD_ASSET_DATA),
|
||||
);
|
||||
});
|
||||
|
||||
it('fails if an order has a fee asset that is neither maker or taker asset', async () => {
|
||||
const badToken = randomAddress();
|
||||
const BAD_ASSET_DATA = hexUtils.concat(ERC20_ASSET_PROXY_ID, hexUtils.leftPad(badToken));
|
||||
const orders = _.times(1, () => createOrder({ takerFeeAssetData: BAD_ASSET_DATA }));
|
||||
const signatures = orders.map(() => encodeExchangeBehavior());
|
||||
const qfr = getExpectedSellQuoteFillResults(orders);
|
||||
const tx = host
|
||||
.executeTransform(
|
||||
transformer.address,
|
||||
takerToken.address,
|
||||
qfr.takerAssetSpent,
|
||||
encodeTransformData({
|
||||
orders,
|
||||
signatures,
|
||||
buyAmount: qfr.makerAssetBought,
|
||||
}),
|
||||
)
|
||||
.awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid });
|
||||
return expect(tx).to.revertWith(new ZeroExRevertErrors.TransformERC20.InvalidTakerFeeTokenError(badToken));
|
||||
});
|
||||
|
||||
it('respects `maxOrderFillAmounts`', async () => {
|
||||
const orders = _.times(2, () => createOrder());
|
||||
const signatures = orders.map(() => encodeExchangeBehavior());
|
||||
const qfr = getExpectedSellQuoteFillResults(orders.slice(1));
|
||||
const protocolFee = singleProtocolFee.times(2);
|
||||
await host
|
||||
.executeTransform(
|
||||
transformer.address,
|
||||
takerToken.address,
|
||||
qfr.takerAssetSpent,
|
||||
encodeTransformData({
|
||||
orders,
|
||||
signatures,
|
||||
buyAmount: qfr.makerAssetBought,
|
||||
// Skip the first order.
|
||||
maxOrderFillAmounts: [ZERO_AMOUNT],
|
||||
}),
|
||||
)
|
||||
.awaitTransactionSuccessAsync({ value: protocolFee });
|
||||
assertBalances(await getBalancesAsync(host.address), {
|
||||
...ZERO_BALANCES,
|
||||
makerAssetBalance: qfr.makerAssetBought,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
147
contracts/zero-ex/test/transformers/pay_taker_transformer.ts
Normal file
147
contracts/zero-ex/test/transformers/pay_taker_transformer.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
import { blockchainTests, constants, expect, getRandomInteger, randomAddress } from '@0x/contracts-test-utils';
|
||||
import { BigNumber, hexUtils } from '@0x/utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { ETH_TOKEN_ADDRESS } from '../../src/constants';
|
||||
import { encodePayTakerTransformerData } from '../../src/transformer_data_encoders';
|
||||
import { artifacts } from '../artifacts';
|
||||
import { PayTakerTransformerContract, TestMintableERC20TokenContract, TestTransformerHostContract } from '../wrappers';
|
||||
|
||||
const { MAX_UINT256, ZERO_AMOUNT } = constants;
|
||||
|
||||
blockchainTests.resets('PayTakerTransformer', env => {
|
||||
let caller: string;
|
||||
const taker = randomAddress();
|
||||
let token: TestMintableERC20TokenContract;
|
||||
let transformer: PayTakerTransformerContract;
|
||||
let host: TestTransformerHostContract;
|
||||
|
||||
before(async () => {
|
||||
[caller] = await env.getAccountAddressesAsync();
|
||||
token = await TestMintableERC20TokenContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestMintableERC20Token,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
artifacts,
|
||||
);
|
||||
transformer = await PayTakerTransformerContract.deployFrom0xArtifactAsync(
|
||||
artifacts.PayTakerTransformer,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
artifacts,
|
||||
);
|
||||
host = await TestTransformerHostContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestTransformerHost,
|
||||
env.provider,
|
||||
{ ...env.txDefaults, from: caller },
|
||||
artifacts,
|
||||
);
|
||||
});
|
||||
|
||||
interface Balances {
|
||||
ethBalance: BigNumber;
|
||||
tokenBalance: BigNumber;
|
||||
}
|
||||
|
||||
const ZERO_BALANCES = {
|
||||
ethBalance: ZERO_AMOUNT,
|
||||
tokenBalance: ZERO_AMOUNT,
|
||||
};
|
||||
|
||||
async function getBalancesAsync(owner: string): Promise<Balances> {
|
||||
return {
|
||||
ethBalance: await env.web3Wrapper.getBalanceInWeiAsync(owner),
|
||||
tokenBalance: await token.balanceOf(owner).callAsync(),
|
||||
};
|
||||
}
|
||||
|
||||
async function mintHostTokensAsync(amount: BigNumber): Promise<void> {
|
||||
await token.mint(host.address, amount).awaitTransactionSuccessAsync();
|
||||
}
|
||||
|
||||
async function sendEtherAsync(to: string, amount: BigNumber): Promise<void> {
|
||||
await env.web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await env.web3Wrapper.sendTransactionAsync({
|
||||
...env.txDefaults,
|
||||
to,
|
||||
from: caller,
|
||||
value: amount,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
it('can transfer a token and ETH', async () => {
|
||||
const amounts = _.times(2, () => getRandomInteger(1, '1e18'));
|
||||
const data = encodePayTakerTransformerData({
|
||||
amounts,
|
||||
tokens: [token.address, ETH_TOKEN_ADDRESS],
|
||||
});
|
||||
await mintHostTokensAsync(amounts[0]);
|
||||
await sendEtherAsync(host.address, amounts[1]);
|
||||
await host
|
||||
.rawExecuteTransform(transformer.address, hexUtils.random(), taker, data)
|
||||
.awaitTransactionSuccessAsync();
|
||||
expect(await getBalancesAsync(host.address)).to.deep.eq(ZERO_BALANCES);
|
||||
expect(await getBalancesAsync(taker)).to.deep.eq({
|
||||
tokenBalance: amounts[0],
|
||||
ethBalance: amounts[1],
|
||||
});
|
||||
});
|
||||
|
||||
it('can transfer all of a token and ETH', async () => {
|
||||
const amounts = _.times(2, () => getRandomInteger(1, '1e18'));
|
||||
const data = encodePayTakerTransformerData({
|
||||
amounts: [MAX_UINT256, MAX_UINT256],
|
||||
tokens: [token.address, ETH_TOKEN_ADDRESS],
|
||||
});
|
||||
await mintHostTokensAsync(amounts[0]);
|
||||
await sendEtherAsync(host.address, amounts[1]);
|
||||
await host
|
||||
.rawExecuteTransform(transformer.address, hexUtils.random(), taker, data)
|
||||
.awaitTransactionSuccessAsync();
|
||||
expect(await getBalancesAsync(host.address)).to.deep.eq(ZERO_BALANCES);
|
||||
expect(await getBalancesAsync(taker)).to.deep.eq({
|
||||
tokenBalance: amounts[0],
|
||||
ethBalance: amounts[1],
|
||||
});
|
||||
});
|
||||
|
||||
it('can transfer all of a token and ETH (empty amounts)', async () => {
|
||||
const amounts = _.times(2, () => getRandomInteger(1, '1e18'));
|
||||
const data = encodePayTakerTransformerData({
|
||||
amounts: [],
|
||||
tokens: [token.address, ETH_TOKEN_ADDRESS],
|
||||
});
|
||||
await mintHostTokensAsync(amounts[0]);
|
||||
await sendEtherAsync(host.address, amounts[1]);
|
||||
await host
|
||||
.rawExecuteTransform(transformer.address, hexUtils.random(), taker, data)
|
||||
.awaitTransactionSuccessAsync();
|
||||
expect(await getBalancesAsync(host.address)).to.deep.eq(ZERO_BALANCES);
|
||||
expect(await getBalancesAsync(taker)).to.deep.eq({
|
||||
tokenBalance: amounts[0],
|
||||
ethBalance: amounts[1],
|
||||
});
|
||||
});
|
||||
|
||||
it('can transfer less than the balance of a token and ETH', async () => {
|
||||
const amounts = _.times(2, () => getRandomInteger(1, '1e18'));
|
||||
const data = encodePayTakerTransformerData({
|
||||
amounts: amounts.map(a => a.dividedToIntegerBy(2)),
|
||||
tokens: [token.address, ETH_TOKEN_ADDRESS],
|
||||
});
|
||||
await mintHostTokensAsync(amounts[0]);
|
||||
await sendEtherAsync(host.address, amounts[1]);
|
||||
await host
|
||||
.rawExecuteTransform(transformer.address, hexUtils.random(), taker, data)
|
||||
.awaitTransactionSuccessAsync();
|
||||
expect(await getBalancesAsync(host.address)).to.deep.eq({
|
||||
tokenBalance: amounts[0].minus(amounts[0].dividedToIntegerBy(2)),
|
||||
ethBalance: amounts[1].minus(amounts[1].dividedToIntegerBy(2)),
|
||||
});
|
||||
expect(await getBalancesAsync(taker)).to.deep.eq({
|
||||
tokenBalance: amounts[0].dividedToIntegerBy(2),
|
||||
ethBalance: amounts[1].dividedToIntegerBy(2),
|
||||
});
|
||||
});
|
||||
});
|
||||
147
contracts/zero-ex/test/transformers/weth_transformer_test.ts
Normal file
147
contracts/zero-ex/test/transformers/weth_transformer_test.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
import { blockchainTests, constants, expect, getRandomInteger, randomAddress } from '@0x/contracts-test-utils';
|
||||
import { BigNumber, ZeroExRevertErrors } from '@0x/utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { ETH_TOKEN_ADDRESS } from '../../src/constants';
|
||||
import { encodeWethTransformerData } from '../../src/transformer_data_encoders';
|
||||
import { artifacts } from '../artifacts';
|
||||
import { TestWethContract, TestWethTransformerHostContract, WethTransformerContract } from '../wrappers';
|
||||
|
||||
const { MAX_UINT256, ZERO_AMOUNT } = constants;
|
||||
|
||||
blockchainTests.resets('WethTransformer', env => {
|
||||
let weth: TestWethContract;
|
||||
let transformer: WethTransformerContract;
|
||||
let host: TestWethTransformerHostContract;
|
||||
|
||||
before(async () => {
|
||||
weth = await TestWethContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestWeth,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
artifacts,
|
||||
);
|
||||
transformer = await WethTransformerContract.deployFrom0xArtifactAsync(
|
||||
artifacts.WethTransformer,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
artifacts,
|
||||
weth.address,
|
||||
);
|
||||
host = await TestWethTransformerHostContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestWethTransformerHost,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
artifacts,
|
||||
weth.address,
|
||||
);
|
||||
});
|
||||
|
||||
interface Balances {
|
||||
ethBalance: BigNumber;
|
||||
wethBalance: BigNumber;
|
||||
}
|
||||
|
||||
async function getHostBalancesAsync(): Promise<Balances> {
|
||||
return {
|
||||
ethBalance: await env.web3Wrapper.getBalanceInWeiAsync(host.address),
|
||||
wethBalance: await weth.balanceOf(host.address).callAsync(),
|
||||
};
|
||||
}
|
||||
|
||||
it('fails if the token is neither ETH or WETH', async () => {
|
||||
const amount = getRandomInteger(1, '1e18');
|
||||
const data = encodeWethTransformerData({
|
||||
amount,
|
||||
token: randomAddress(),
|
||||
});
|
||||
const tx = host
|
||||
.executeTransform(amount, transformer.address, data)
|
||||
.awaitTransactionSuccessAsync({ value: amount });
|
||||
return expect(tx).to.revertWith(new ZeroExRevertErrors.TransformERC20.InvalidTransformDataError(data));
|
||||
});
|
||||
|
||||
it('can unwrap WETH', async () => {
|
||||
const amount = getRandomInteger(1, '1e18');
|
||||
const data = encodeWethTransformerData({
|
||||
amount,
|
||||
token: weth.address,
|
||||
});
|
||||
await host.executeTransform(amount, transformer.address, data).awaitTransactionSuccessAsync({ value: amount });
|
||||
expect(await getHostBalancesAsync()).to.deep.eq({
|
||||
ethBalance: amount,
|
||||
wethBalance: ZERO_AMOUNT,
|
||||
});
|
||||
});
|
||||
|
||||
it('can unwrap all WETH', async () => {
|
||||
const amount = getRandomInteger(1, '1e18');
|
||||
const data = encodeWethTransformerData({
|
||||
amount: MAX_UINT256,
|
||||
token: weth.address,
|
||||
});
|
||||
await host.executeTransform(amount, transformer.address, data).awaitTransactionSuccessAsync({ value: amount });
|
||||
expect(await getHostBalancesAsync()).to.deep.eq({
|
||||
ethBalance: amount,
|
||||
wethBalance: ZERO_AMOUNT,
|
||||
});
|
||||
});
|
||||
|
||||
it('can unwrap some WETH', async () => {
|
||||
const amount = getRandomInteger(1, '1e18');
|
||||
const data = encodeWethTransformerData({
|
||||
amount: amount.dividedToIntegerBy(2),
|
||||
token: weth.address,
|
||||
});
|
||||
await host.executeTransform(amount, transformer.address, data).awaitTransactionSuccessAsync({ value: amount });
|
||||
expect(await getHostBalancesAsync()).to.deep.eq({
|
||||
ethBalance: amount.dividedToIntegerBy(2),
|
||||
wethBalance: amount.minus(amount.dividedToIntegerBy(2)),
|
||||
});
|
||||
});
|
||||
|
||||
it('can wrap ETH', async () => {
|
||||
const amount = getRandomInteger(1, '1e18');
|
||||
const data = encodeWethTransformerData({
|
||||
amount,
|
||||
token: ETH_TOKEN_ADDRESS,
|
||||
});
|
||||
await host
|
||||
.executeTransform(ZERO_AMOUNT, transformer.address, data)
|
||||
.awaitTransactionSuccessAsync({ value: amount });
|
||||
expect(await getHostBalancesAsync()).to.deep.eq({
|
||||
ethBalance: ZERO_AMOUNT,
|
||||
wethBalance: amount,
|
||||
});
|
||||
});
|
||||
|
||||
it('can wrap all ETH', async () => {
|
||||
const amount = getRandomInteger(1, '1e18');
|
||||
const data = encodeWethTransformerData({
|
||||
amount: MAX_UINT256,
|
||||
token: ETH_TOKEN_ADDRESS,
|
||||
});
|
||||
await host
|
||||
.executeTransform(ZERO_AMOUNT, transformer.address, data)
|
||||
.awaitTransactionSuccessAsync({ value: amount });
|
||||
expect(await getHostBalancesAsync()).to.deep.eq({
|
||||
ethBalance: ZERO_AMOUNT,
|
||||
wethBalance: amount,
|
||||
});
|
||||
});
|
||||
|
||||
it('can wrap some ETH', async () => {
|
||||
const amount = getRandomInteger(1, '1e18');
|
||||
const data = encodeWethTransformerData({
|
||||
amount: amount.dividedToIntegerBy(2),
|
||||
token: ETH_TOKEN_ADDRESS,
|
||||
});
|
||||
await host
|
||||
.executeTransform(ZERO_AMOUNT, transformer.address, data)
|
||||
.awaitTransactionSuccessAsync({ value: amount });
|
||||
expect(await getHostBalancesAsync()).to.deep.eq({
|
||||
ethBalance: amount.minus(amount.dividedToIntegerBy(2)),
|
||||
wethBalance: amount.dividedToIntegerBy(2),
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user