* Refactor Multiplex into multiple files * Pull UniswapV3 into separate file * Add support for multihop nested within batch sell * Add useSelfBalance and recipient to _fillRfqOrder * Expose onlySelf variant in UniswapV3Feature for Multiplex * Add useSelfBalance and recipient to _transformERC20 * Add support for proportional fill amounts in batchSell * Comments and renaming * Unit tests * Use caps for immutables * Rename taker -> recipient in TransformContext and SettleOrderInfo * lint * Address nits * Swallow reverts for LiquidityProvider and UniswapV2 batch sells * Address spot-check findings (#279) * Check didSucceed in _callWithOptionalBooleanResult * Add takerToken=ETH support to OtcOrdersFeature (#287) * Add takerToken=ETH support to OtcOrdersFeature * Add batchFillTakerSignedOtcOrders * Add support for OTC to Multiplex * Address PR feedback * Update TransformERC20Feature (#303) * remove multiplex_utils * Update changelog * unbreak tests
2141 lines
92 KiB
TypeScript
2141 lines
92 KiB
TypeScript
import {
|
|
blockchainTests,
|
|
constants,
|
|
expect,
|
|
getRandomInteger,
|
|
toBaseUnitAmount,
|
|
verifyEventsFromLogs,
|
|
} from '@0x/contracts-test-utils';
|
|
import { OtcOrder, RfqOrder, SIGNATURE_ABI } from '@0x/protocol-utils';
|
|
import { AbiEncoder, BigNumber, hexUtils } from '@0x/utils';
|
|
import { LogWithDecodedArgs } from 'ethereum-types';
|
|
|
|
import { IZeroExContract, IZeroExEvents } from '../../src/wrappers';
|
|
import { artifacts } from '../artifacts';
|
|
import { abis } from '../utils/abis';
|
|
import { fullMigrateAsync } from '../utils/migration';
|
|
import { getRandomOtcOrder, getRandomRfqOrder } from '../utils/orders';
|
|
import {
|
|
IOwnableFeatureContract,
|
|
LiquidityProviderSandboxContract,
|
|
MultiplexFeatureContract,
|
|
MultiplexFeatureEvents,
|
|
OtcOrdersFeatureContract,
|
|
TestLiquidityProviderContract,
|
|
TestMintableERC20TokenContract,
|
|
TestMintableERC20TokenEvents,
|
|
TestMintTokenERC20TransformerContract,
|
|
TestMintTokenERC20TransformerEvents,
|
|
TestUniswapV2FactoryContract,
|
|
TestUniswapV2FactoryPoolCreatedEventArgs,
|
|
TestUniswapV2PoolContract,
|
|
TestUniswapV3FactoryContract,
|
|
TestUniswapV3FactoryPoolCreatedEventArgs,
|
|
TestUniswapV3PoolContract,
|
|
TestWethContract,
|
|
TestWethEvents,
|
|
UniswapV3FeatureContract,
|
|
} from '../wrappers';
|
|
|
|
interface TransferEvent {
|
|
token: string;
|
|
from: string;
|
|
to: string;
|
|
value?: BigNumber;
|
|
}
|
|
|
|
enum MultiplexSubcall {
|
|
Invalid,
|
|
Rfq,
|
|
Otc,
|
|
UniswapV2,
|
|
UniswapV3,
|
|
LiquidityProvider,
|
|
TransformERC20,
|
|
BatchSell,
|
|
MultiHopSell,
|
|
}
|
|
|
|
interface MultiHopSellSubcall {
|
|
id: MultiplexSubcall;
|
|
data: string;
|
|
}
|
|
|
|
interface BatchSellSubcall extends MultiHopSellSubcall {
|
|
sellAmount: BigNumber;
|
|
}
|
|
|
|
const HIGH_BIT = new BigNumber(2).pow(255);
|
|
function encodeFractionalFillAmount(frac: number): BigNumber {
|
|
return HIGH_BIT.plus(new BigNumber(frac).times('1e18').integerValue());
|
|
}
|
|
|
|
blockchainTests.resets('MultiplexFeature', env => {
|
|
const POOL_FEE = 1234;
|
|
|
|
let zeroEx: IZeroExContract;
|
|
let multiplex: MultiplexFeatureContract;
|
|
let flashWalletAddress: string;
|
|
let sandbox: LiquidityProviderSandboxContract;
|
|
let liquidityProvider: TestLiquidityProviderContract;
|
|
let sushiFactory: TestUniswapV2FactoryContract;
|
|
let uniV2Factory: TestUniswapV2FactoryContract;
|
|
let uniV3Factory: TestUniswapV3FactoryContract;
|
|
let dai: TestMintableERC20TokenContract;
|
|
let shib: TestMintableERC20TokenContract;
|
|
let zrx: TestMintableERC20TokenContract;
|
|
let weth: TestWethContract;
|
|
let owner: string;
|
|
let maker: string;
|
|
let taker: string;
|
|
let transformerNonce: number;
|
|
|
|
//////////////// Deployment utility functions ////////////////
|
|
async function migrateOtcOrdersFeatureAsync(): Promise<void> {
|
|
const featureImpl = await OtcOrdersFeatureContract.deployFrom0xArtifactAsync(
|
|
artifacts.OtcOrdersFeature,
|
|
env.provider,
|
|
env.txDefaults,
|
|
artifacts,
|
|
zeroEx.address,
|
|
weth.address,
|
|
);
|
|
await new IOwnableFeatureContract(zeroEx.address, env.provider, env.txDefaults)
|
|
.migrate(featureImpl.address, featureImpl.migrate().getABIEncodedTransactionData(), owner)
|
|
.awaitTransactionSuccessAsync();
|
|
}
|
|
|
|
async function migrateLiquidityProviderContractsAsync(): Promise<void> {
|
|
sandbox = await LiquidityProviderSandboxContract.deployFrom0xArtifactAsync(
|
|
artifacts.LiquidityProviderSandbox,
|
|
env.provider,
|
|
env.txDefaults,
|
|
artifacts,
|
|
zeroEx.address,
|
|
);
|
|
liquidityProvider = await TestLiquidityProviderContract.deployFrom0xArtifactAsync(
|
|
artifacts.TestLiquidityProvider,
|
|
env.provider,
|
|
env.txDefaults,
|
|
artifacts,
|
|
);
|
|
}
|
|
|
|
async function migrateUniswapV2ContractsAsync(): Promise<void> {
|
|
sushiFactory = await TestUniswapV2FactoryContract.deployFrom0xArtifactAsync(
|
|
artifacts.TestUniswapV2Factory,
|
|
env.provider,
|
|
env.txDefaults,
|
|
artifacts,
|
|
);
|
|
uniV2Factory = await TestUniswapV2FactoryContract.deployFrom0xArtifactAsync(
|
|
artifacts.TestUniswapV2Factory,
|
|
env.provider,
|
|
env.txDefaults,
|
|
artifacts,
|
|
);
|
|
}
|
|
|
|
async function migrateUniswapV3ContractsAsync(): Promise<void> {
|
|
uniV3Factory = await TestUniswapV3FactoryContract.deployFrom0xArtifactAsync(
|
|
artifacts.TestUniswapV3Factory,
|
|
env.provider,
|
|
env.txDefaults,
|
|
artifacts,
|
|
);
|
|
const featureImpl = await UniswapV3FeatureContract.deployFrom0xArtifactAsync(
|
|
artifacts.TestUniswapV3Feature,
|
|
env.provider,
|
|
env.txDefaults,
|
|
artifacts,
|
|
weth.address,
|
|
uniV3Factory.address,
|
|
await uniV3Factory.POOL_INIT_CODE_HASH().callAsync(),
|
|
);
|
|
await new IOwnableFeatureContract(zeroEx.address, env.provider, env.txDefaults)
|
|
.migrate(featureImpl.address, featureImpl.migrate().getABIEncodedTransactionData(), owner)
|
|
.awaitTransactionSuccessAsync();
|
|
}
|
|
|
|
//////////////// Miscellaneous utils ////////////////
|
|
|
|
function isWethContract(t: TestMintableERC20TokenContract | TestWethContract): t is TestWethContract {
|
|
return !!(t as any).deposit;
|
|
}
|
|
|
|
async function mintToAsync(
|
|
token: TestMintableERC20TokenContract | TestWethContract,
|
|
recipient: string,
|
|
amount: BigNumber,
|
|
): Promise<void> {
|
|
if (isWethContract(token)) {
|
|
await token.depositTo(recipient).awaitTransactionSuccessAsync({ value: amount });
|
|
} else {
|
|
await token.mint(recipient, amount).awaitTransactionSuccessAsync();
|
|
}
|
|
}
|
|
|
|
//////////////// Deploy Uniswap pools ////////////////
|
|
|
|
async function createUniswapV2PoolAsync(
|
|
factory: TestUniswapV2FactoryContract,
|
|
token0: TestMintableERC20TokenContract | TestWethContract,
|
|
token1: TestMintableERC20TokenContract | TestWethContract,
|
|
balance0: BigNumber = toBaseUnitAmount(10),
|
|
balance1: BigNumber = toBaseUnitAmount(10),
|
|
): Promise<TestUniswapV2PoolContract> {
|
|
const r = await factory.createPool(token0.address, token1.address).awaitTransactionSuccessAsync();
|
|
const pool = new TestUniswapV2PoolContract(
|
|
(r.logs[0] as LogWithDecodedArgs<TestUniswapV2FactoryPoolCreatedEventArgs>).args.pool,
|
|
env.provider,
|
|
env.txDefaults,
|
|
);
|
|
await mintToAsync(token0, pool.address, balance0);
|
|
await mintToAsync(token1, pool.address, balance1);
|
|
if (token0.address < token1.address) {
|
|
await pool.setReserves(balance0, balance1, constants.ZERO_AMOUNT).awaitTransactionSuccessAsync();
|
|
} else {
|
|
await pool.setReserves(balance1, balance0, constants.ZERO_AMOUNT).awaitTransactionSuccessAsync();
|
|
}
|
|
return pool;
|
|
}
|
|
|
|
async function createUniswapV3PoolAsync(
|
|
token0: TestMintableERC20TokenContract | TestWethContract,
|
|
token1: TestMintableERC20TokenContract | TestWethContract,
|
|
balance0: BigNumber = toBaseUnitAmount(10),
|
|
balance1: BigNumber = toBaseUnitAmount(10),
|
|
): Promise<TestUniswapV3PoolContract> {
|
|
const r = await uniV3Factory
|
|
.createPool(token0.address, token1.address, new BigNumber(POOL_FEE))
|
|
.awaitTransactionSuccessAsync();
|
|
const pool = new TestUniswapV3PoolContract(
|
|
(r.logs[0] as LogWithDecodedArgs<TestUniswapV3FactoryPoolCreatedEventArgs>).args.pool,
|
|
env.provider,
|
|
env.txDefaults,
|
|
);
|
|
await mintToAsync(token0, pool.address, balance0);
|
|
await mintToAsync(token1, pool.address, balance1);
|
|
return pool;
|
|
}
|
|
|
|
//////////////// Generate subcalls ////////////////
|
|
|
|
function getTestRfqOrder(overrides: Partial<RfqOrder> = {}): RfqOrder {
|
|
return getRandomRfqOrder({
|
|
maker,
|
|
verifyingContract: zeroEx.address,
|
|
chainId: 1337,
|
|
takerToken: dai.address,
|
|
makerToken: zrx.address,
|
|
makerAmount: toBaseUnitAmount(1),
|
|
takerAmount: toBaseUnitAmount(1),
|
|
txOrigin: taker,
|
|
...overrides,
|
|
});
|
|
}
|
|
async function getRfqSubcallAsync(
|
|
rfqOrder: RfqOrder,
|
|
sellAmount: BigNumber = rfqOrder.takerAmount,
|
|
): Promise<BatchSellSubcall> {
|
|
const rfqDataEncoder = AbiEncoder.create([
|
|
{ name: 'order', type: 'tuple', components: RfqOrder.STRUCT_ABI },
|
|
{ name: 'signature', type: 'tuple', components: SIGNATURE_ABI },
|
|
]);
|
|
const makerToken =
|
|
rfqOrder.makerToken === weth.address
|
|
? weth
|
|
: new TestMintableERC20TokenContract(rfqOrder.makerToken, env.provider, env.txDefaults);
|
|
await mintToAsync(makerToken, rfqOrder.maker, rfqOrder.makerAmount);
|
|
return {
|
|
id: MultiplexSubcall.Rfq,
|
|
sellAmount,
|
|
data: rfqDataEncoder.encode({
|
|
order: rfqOrder,
|
|
signature: await rfqOrder.getSignatureWithProviderAsync(env.provider),
|
|
}),
|
|
};
|
|
}
|
|
|
|
function getTestOtcOrder(fields: Partial<OtcOrder> = {}): OtcOrder {
|
|
return getRandomOtcOrder({
|
|
maker,
|
|
verifyingContract: zeroEx.address,
|
|
chainId: 1337,
|
|
takerToken: dai.address,
|
|
makerToken: zrx.address,
|
|
makerAmount: toBaseUnitAmount(1),
|
|
takerAmount: toBaseUnitAmount(1),
|
|
taker,
|
|
txOrigin: taker,
|
|
...fields,
|
|
});
|
|
}
|
|
async function getOtcSubcallAsync(
|
|
otcOrder: OtcOrder,
|
|
sellAmount: BigNumber = otcOrder.takerAmount,
|
|
): Promise<BatchSellSubcall> {
|
|
const otcDataEncoder = AbiEncoder.create([
|
|
{ name: 'order', type: 'tuple', components: OtcOrder.STRUCT_ABI },
|
|
{ name: 'signature', type: 'tuple', components: SIGNATURE_ABI },
|
|
]);
|
|
const makerToken =
|
|
otcOrder.makerToken === weth.address
|
|
? weth
|
|
: new TestMintableERC20TokenContract(otcOrder.makerToken, env.provider, env.txDefaults);
|
|
await mintToAsync(makerToken, otcOrder.maker, otcOrder.makerAmount);
|
|
return {
|
|
id: MultiplexSubcall.Otc,
|
|
sellAmount,
|
|
data: otcDataEncoder.encode({
|
|
order: otcOrder,
|
|
signature: await otcOrder.getSignatureWithProviderAsync(env.provider),
|
|
}),
|
|
};
|
|
}
|
|
|
|
function getUniswapV2MultiHopSubcall(tokens: string[], isSushi: boolean = false): MultiHopSellSubcall {
|
|
const uniswapDataEncoder = AbiEncoder.create([
|
|
{ name: 'tokens', type: 'address[]' },
|
|
{ name: 'isSushi', type: 'bool' },
|
|
]);
|
|
return {
|
|
id: MultiplexSubcall.UniswapV2,
|
|
data: uniswapDataEncoder.encode({ tokens, isSushi }),
|
|
};
|
|
}
|
|
function getUniswapV2BatchSubcall(
|
|
tokens: string[],
|
|
sellAmount: BigNumber = getRandomInteger(1, toBaseUnitAmount(1)),
|
|
isSushi: boolean = false,
|
|
): BatchSellSubcall {
|
|
return {
|
|
...getUniswapV2MultiHopSubcall(tokens, isSushi),
|
|
sellAmount,
|
|
};
|
|
}
|
|
|
|
function getUniswapV3MultiHopSubcall(
|
|
tokens_: Array<TestMintableERC20TokenContract | TestWethContract>,
|
|
): MultiHopSellSubcall {
|
|
const elems: string[] = [];
|
|
tokens_.forEach((t, i) => {
|
|
if (i) {
|
|
elems.push(hexUtils.leftPad(POOL_FEE, 3));
|
|
}
|
|
elems.push(hexUtils.leftPad(t.address, 20));
|
|
});
|
|
const data = hexUtils.concat(...elems);
|
|
|
|
return {
|
|
id: MultiplexSubcall.UniswapV3,
|
|
data,
|
|
};
|
|
}
|
|
function getUniswapV3BatchSubcall(
|
|
tokens: Array<TestMintableERC20TokenContract | TestWethContract>,
|
|
sellAmount: BigNumber = getRandomInteger(1, toBaseUnitAmount(1)),
|
|
): BatchSellSubcall {
|
|
return {
|
|
...getUniswapV3MultiHopSubcall(tokens),
|
|
sellAmount,
|
|
};
|
|
}
|
|
|
|
function getLiquidityProviderMultiHopSubcall(): MultiHopSellSubcall {
|
|
const plpDataEncoder = AbiEncoder.create([
|
|
{ name: 'provider', type: 'address' },
|
|
{ name: 'auxiliaryData', type: 'bytes' },
|
|
]);
|
|
return {
|
|
id: MultiplexSubcall.LiquidityProvider,
|
|
data: plpDataEncoder.encode({
|
|
provider: liquidityProvider.address,
|
|
auxiliaryData: constants.NULL_BYTES,
|
|
}),
|
|
};
|
|
}
|
|
function getLiquidityProviderBatchSubcall(
|
|
sellAmount: BigNumber = getRandomInteger(1, toBaseUnitAmount(1)),
|
|
): BatchSellSubcall {
|
|
return {
|
|
...getLiquidityProviderMultiHopSubcall(),
|
|
sellAmount,
|
|
};
|
|
}
|
|
|
|
function getTransformERC20Subcall(
|
|
inputToken: string,
|
|
outputToken: string,
|
|
sellAmount: BigNumber = getRandomInteger(1, toBaseUnitAmount(1)),
|
|
mintAmount: BigNumber = getRandomInteger(1, toBaseUnitAmount(1)),
|
|
): BatchSellSubcall {
|
|
const transformERC20Encoder = AbiEncoder.create([
|
|
{
|
|
name: 'transformations',
|
|
type: 'tuple[]',
|
|
components: [
|
|
{ name: 'deploymentNonce', type: 'uint32' },
|
|
{ name: 'data', type: 'bytes' },
|
|
],
|
|
},
|
|
]);
|
|
const transformDataEncoder = AbiEncoder.create([
|
|
{
|
|
name: 'data',
|
|
type: 'tuple',
|
|
components: [
|
|
{ name: 'inputToken', type: 'address' },
|
|
{ name: 'outputToken', type: 'address' },
|
|
{ name: 'burnAmount', type: 'uint256' },
|
|
{ name: 'mintAmount', type: 'uint256' },
|
|
{ name: 'feeAmount', type: 'uint256' },
|
|
],
|
|
},
|
|
]);
|
|
return {
|
|
id: MultiplexSubcall.TransformERC20,
|
|
sellAmount,
|
|
data: transformERC20Encoder.encode({
|
|
transformations: [
|
|
{
|
|
deploymentNonce: transformerNonce,
|
|
data: transformDataEncoder.encode([
|
|
{
|
|
inputToken,
|
|
outputToken,
|
|
burnAmount: constants.ZERO_AMOUNT,
|
|
mintAmount,
|
|
feeAmount: constants.ZERO_AMOUNT,
|
|
},
|
|
]),
|
|
},
|
|
],
|
|
}),
|
|
};
|
|
}
|
|
|
|
function getNestedBatchSellSubcall(calls: BatchSellSubcall[]): MultiHopSellSubcall {
|
|
const batchSellDataEncoder = AbiEncoder.create([
|
|
{
|
|
name: 'calls',
|
|
type: 'tuple[]',
|
|
components: [
|
|
{ name: 'id', type: 'uint8' },
|
|
{ name: 'sellAmount', type: 'uint256' },
|
|
{ name: 'data', type: 'bytes' },
|
|
],
|
|
},
|
|
]);
|
|
return {
|
|
id: MultiplexSubcall.BatchSell,
|
|
data: batchSellDataEncoder.encode({ calls }),
|
|
};
|
|
}
|
|
|
|
function getNestedMultiHopSellSubcall(
|
|
tokens: string[],
|
|
calls: MultiHopSellSubcall[],
|
|
sellAmount: BigNumber = getRandomInteger(1, toBaseUnitAmount(1)),
|
|
): BatchSellSubcall {
|
|
const multiHopSellDataEncoder = AbiEncoder.create([
|
|
{
|
|
name: 'tokens',
|
|
type: 'address[]',
|
|
},
|
|
{
|
|
name: 'calls',
|
|
type: 'tuple[]',
|
|
components: [
|
|
{ name: 'id', type: 'uint8' },
|
|
{ name: 'data', type: 'bytes' },
|
|
],
|
|
},
|
|
]);
|
|
return {
|
|
id: MultiplexSubcall.MultiHopSell,
|
|
sellAmount,
|
|
data: multiHopSellDataEncoder.encode({ tokens, calls }),
|
|
};
|
|
}
|
|
|
|
before(async () => {
|
|
[owner, maker, taker] = await env.getAccountAddressesAsync();
|
|
zeroEx = await fullMigrateAsync(owner, env.provider, env.txDefaults, {});
|
|
flashWalletAddress = await zeroEx.getTransformWallet().callAsync();
|
|
|
|
[dai, shib, zrx] = await Promise.all(
|
|
[...new Array(3)].map(async () =>
|
|
TestMintableERC20TokenContract.deployFrom0xArtifactAsync(
|
|
artifacts.TestMintableERC20Token,
|
|
env.provider,
|
|
env.txDefaults,
|
|
artifacts,
|
|
),
|
|
),
|
|
);
|
|
weth = await TestWethContract.deployFrom0xArtifactAsync(
|
|
artifacts.TestWeth,
|
|
env.provider,
|
|
env.txDefaults,
|
|
artifacts,
|
|
);
|
|
|
|
await Promise.all([
|
|
...[dai, shib, zrx, weth].map(t =>
|
|
t.approve(zeroEx.address, constants.MAX_UINT256).awaitTransactionSuccessAsync({ from: taker }),
|
|
),
|
|
...[dai, shib, zrx, weth].map(t =>
|
|
t.approve(zeroEx.address, constants.MAX_UINT256).awaitTransactionSuccessAsync({ from: maker }),
|
|
),
|
|
]);
|
|
await migrateOtcOrdersFeatureAsync();
|
|
await migrateLiquidityProviderContractsAsync();
|
|
await migrateUniswapV2ContractsAsync();
|
|
await migrateUniswapV3ContractsAsync();
|
|
transformerNonce = await env.web3Wrapper.getAccountNonceAsync(owner);
|
|
await TestMintTokenERC20TransformerContract.deployFrom0xArtifactAsync(
|
|
artifacts.TestMintTokenERC20Transformer,
|
|
env.provider,
|
|
env.txDefaults,
|
|
artifacts,
|
|
);
|
|
|
|
const featureImpl = await MultiplexFeatureContract.deployFrom0xArtifactAsync(
|
|
artifacts.MultiplexFeature,
|
|
env.provider,
|
|
env.txDefaults,
|
|
artifacts,
|
|
zeroEx.address,
|
|
weth.address,
|
|
sandbox.address,
|
|
uniV2Factory.address,
|
|
sushiFactory.address,
|
|
await uniV2Factory.POOL_INIT_CODE_HASH().callAsync(),
|
|
await sushiFactory.POOL_INIT_CODE_HASH().callAsync(),
|
|
);
|
|
await new IOwnableFeatureContract(zeroEx.address, env.provider, env.txDefaults)
|
|
.migrate(featureImpl.address, featureImpl.migrate().getABIEncodedTransactionData(), owner)
|
|
.awaitTransactionSuccessAsync();
|
|
multiplex = new MultiplexFeatureContract(zeroEx.address, env.provider, env.txDefaults, abis);
|
|
});
|
|
|
|
describe('batch sells', () => {
|
|
describe('multiplexBatchSellTokenForToken', () => {
|
|
it('reverts if minBuyAmount is not satisfied', async () => {
|
|
const order = getTestRfqOrder();
|
|
const rfqSubcall = await getRfqSubcallAsync(order);
|
|
await mintToAsync(dai, taker, rfqSubcall.sellAmount);
|
|
|
|
const tx = multiplex
|
|
.multiplexBatchSellTokenForToken(
|
|
dai.address,
|
|
zrx.address,
|
|
[rfqSubcall],
|
|
order.takerAmount,
|
|
order.makerAmount.plus(1),
|
|
)
|
|
.awaitTransactionSuccessAsync({ from: taker });
|
|
return expect(tx).to.revertWith('MultiplexFeature::_multiplexBatchSell/UNDERBOUGHT');
|
|
});
|
|
it('reverts if given an invalid subcall type', async () => {
|
|
const invalidSubcall: BatchSellSubcall = {
|
|
id: MultiplexSubcall.Invalid,
|
|
sellAmount: toBaseUnitAmount(1),
|
|
data: constants.NULL_BYTES,
|
|
};
|
|
const tx = multiplex
|
|
.multiplexBatchSellTokenForToken(
|
|
dai.address,
|
|
zrx.address,
|
|
[invalidSubcall],
|
|
invalidSubcall.sellAmount,
|
|
constants.ZERO_AMOUNT,
|
|
)
|
|
.awaitTransactionSuccessAsync({ from: taker });
|
|
return expect(tx).to.revertWith('MultiplexFeature::_executeBatchSell/INVALID_SUBCALL');
|
|
});
|
|
it('reverts if the full sell amount is not sold', async () => {
|
|
const order = getTestRfqOrder();
|
|
const rfqSubcall = await getRfqSubcallAsync(order);
|
|
await mintToAsync(dai, taker, rfqSubcall.sellAmount);
|
|
|
|
const tx = multiplex
|
|
.multiplexBatchSellTokenForToken(
|
|
dai.address,
|
|
zrx.address,
|
|
[rfqSubcall],
|
|
order.takerAmount.plus(1),
|
|
order.makerAmount,
|
|
)
|
|
.awaitTransactionSuccessAsync({ from: taker });
|
|
return expect(tx).to.revertWith('MultiplexFeature::_executeBatchSell/INCORRECT_AMOUNT_SOLD');
|
|
});
|
|
it('RFQ, fallback(UniswapV2)', async () => {
|
|
const order = getTestRfqOrder();
|
|
const rfqSubcall = await getRfqSubcallAsync(order);
|
|
await createUniswapV2PoolAsync(uniV2Factory, dai, zrx);
|
|
await mintToAsync(dai, taker, rfqSubcall.sellAmount);
|
|
|
|
const tx = await multiplex
|
|
.multiplexBatchSellTokenForToken(
|
|
dai.address,
|
|
zrx.address,
|
|
[rfqSubcall, getUniswapV2BatchSubcall([dai.address, zrx.address], order.takerAmount)],
|
|
order.takerAmount,
|
|
constants.ZERO_AMOUNT,
|
|
)
|
|
.awaitTransactionSuccessAsync({ from: taker });
|
|
verifyEventsFromLogs(
|
|
tx.logs,
|
|
[
|
|
{
|
|
orderHash: order.getHash(),
|
|
maker,
|
|
taker,
|
|
makerToken: order.makerToken,
|
|
takerToken: order.takerToken,
|
|
takerTokenFilledAmount: order.takerAmount,
|
|
makerTokenFilledAmount: order.makerAmount,
|
|
pool: order.pool,
|
|
},
|
|
],
|
|
IZeroExEvents.RfqOrderFilled,
|
|
);
|
|
});
|
|
it('OTC, fallback(UniswapV2)', async () => {
|
|
const order = getTestOtcOrder();
|
|
const otcSubcall = await getOtcSubcallAsync(order);
|
|
await createUniswapV2PoolAsync(uniV2Factory, dai, zrx);
|
|
await mintToAsync(dai, taker, otcSubcall.sellAmount);
|
|
|
|
const tx = await multiplex
|
|
.multiplexBatchSellTokenForToken(
|
|
dai.address,
|
|
zrx.address,
|
|
[otcSubcall, getUniswapV2BatchSubcall([dai.address, zrx.address], order.takerAmount)],
|
|
order.takerAmount,
|
|
constants.ZERO_AMOUNT,
|
|
)
|
|
.awaitTransactionSuccessAsync({ from: taker });
|
|
verifyEventsFromLogs(
|
|
tx.logs,
|
|
[
|
|
{
|
|
orderHash: order.getHash(),
|
|
maker,
|
|
taker,
|
|
makerToken: order.makerToken,
|
|
takerToken: order.takerToken,
|
|
takerTokenFilledAmount: order.takerAmount,
|
|
makerTokenFilledAmount: order.makerAmount,
|
|
},
|
|
],
|
|
IZeroExEvents.OtcOrderFilled,
|
|
);
|
|
});
|
|
it('expired RFQ, fallback(UniswapV2)', async () => {
|
|
const order = getTestRfqOrder({ expiry: constants.ZERO_AMOUNT });
|
|
const rfqSubcall = await getRfqSubcallAsync(order);
|
|
const uniswap = await createUniswapV2PoolAsync(uniV2Factory, dai, zrx);
|
|
await mintToAsync(dai, taker, rfqSubcall.sellAmount);
|
|
|
|
const tx = await multiplex
|
|
.multiplexBatchSellTokenForToken(
|
|
dai.address,
|
|
zrx.address,
|
|
[rfqSubcall, getUniswapV2BatchSubcall([dai.address, zrx.address], order.takerAmount)],
|
|
order.takerAmount,
|
|
constants.ZERO_AMOUNT,
|
|
)
|
|
.awaitTransactionSuccessAsync({ from: taker });
|
|
verifyEventsFromLogs(
|
|
tx.logs,
|
|
[
|
|
{
|
|
orderHash: order.getHash(),
|
|
maker,
|
|
expiry: order.expiry,
|
|
},
|
|
],
|
|
MultiplexFeatureEvents.ExpiredRfqOrder,
|
|
);
|
|
verifyEventsFromLogs<TransferEvent>(
|
|
tx.logs,
|
|
[
|
|
{
|
|
token: dai.address,
|
|
from: taker,
|
|
to: uniswap.address,
|
|
value: order.takerAmount,
|
|
},
|
|
{
|
|
token: zrx.address,
|
|
from: uniswap.address,
|
|
to: taker,
|
|
},
|
|
],
|
|
TestMintableERC20TokenEvents.Transfer,
|
|
);
|
|
});
|
|
it('expired OTC, fallback(UniswapV2)', async () => {
|
|
const order = getTestOtcOrder({ expiry: constants.ZERO_AMOUNT });
|
|
const otcSubcall = await getOtcSubcallAsync(order);
|
|
const uniswap = await createUniswapV2PoolAsync(uniV2Factory, dai, zrx);
|
|
await mintToAsync(dai, taker, otcSubcall.sellAmount);
|
|
|
|
const tx = await multiplex
|
|
.multiplexBatchSellTokenForToken(
|
|
dai.address,
|
|
zrx.address,
|
|
[otcSubcall, getUniswapV2BatchSubcall([dai.address, zrx.address], order.takerAmount)],
|
|
order.takerAmount,
|
|
constants.ZERO_AMOUNT,
|
|
)
|
|
.awaitTransactionSuccessAsync({ from: taker });
|
|
verifyEventsFromLogs(
|
|
tx.logs,
|
|
[
|
|
{
|
|
orderHash: order.getHash(),
|
|
maker,
|
|
expiry: order.expiry,
|
|
},
|
|
],
|
|
MultiplexFeatureEvents.ExpiredOtcOrder,
|
|
);
|
|
verifyEventsFromLogs<TransferEvent>(
|
|
tx.logs,
|
|
[
|
|
{
|
|
token: dai.address,
|
|
from: taker,
|
|
to: uniswap.address,
|
|
value: order.takerAmount,
|
|
},
|
|
{
|
|
token: zrx.address,
|
|
from: uniswap.address,
|
|
to: taker,
|
|
},
|
|
],
|
|
TestMintableERC20TokenEvents.Transfer,
|
|
);
|
|
});
|
|
it('expired RFQ, fallback(TransformERC20)', async () => {
|
|
const order = getTestRfqOrder({ expiry: constants.ZERO_AMOUNT });
|
|
const rfqSubcall = await getRfqSubcallAsync(order);
|
|
const transformERC20Subcall = getTransformERC20Subcall(dai.address, zrx.address, order.takerAmount);
|
|
await mintToAsync(dai, taker, order.takerAmount);
|
|
|
|
const tx = await multiplex
|
|
.multiplexBatchSellTokenForToken(
|
|
dai.address,
|
|
zrx.address,
|
|
[rfqSubcall, transformERC20Subcall],
|
|
order.takerAmount,
|
|
constants.ZERO_AMOUNT,
|
|
)
|
|
.awaitTransactionSuccessAsync({ from: taker });
|
|
verifyEventsFromLogs(
|
|
tx.logs,
|
|
[
|
|
{
|
|
orderHash: order.getHash(),
|
|
maker,
|
|
expiry: order.expiry,
|
|
},
|
|
],
|
|
MultiplexFeatureEvents.ExpiredRfqOrder,
|
|
);
|
|
verifyEventsFromLogs<TransferEvent>(
|
|
tx.logs,
|
|
[
|
|
{
|
|
token: dai.address,
|
|
from: taker,
|
|
to: flashWalletAddress,
|
|
value: order.takerAmount,
|
|
},
|
|
{
|
|
token: dai.address,
|
|
from: flashWalletAddress,
|
|
to: constants.NULL_ADDRESS,
|
|
},
|
|
{
|
|
token: zrx.address,
|
|
from: flashWalletAddress,
|
|
to: taker,
|
|
},
|
|
],
|
|
TestMintableERC20TokenEvents.Transfer,
|
|
);
|
|
verifyEventsFromLogs(
|
|
tx.logs,
|
|
[
|
|
{
|
|
caller: zeroEx.address,
|
|
sender: zeroEx.address,
|
|
taker,
|
|
inputTokenBalance: order.takerAmount,
|
|
},
|
|
],
|
|
TestMintTokenERC20TransformerEvents.MintTransform,
|
|
);
|
|
});
|
|
it('LiquidityProvider, UniV3, Sushiswap', async () => {
|
|
const sushiswap = await createUniswapV2PoolAsync(sushiFactory, dai, zrx);
|
|
const uniV3 = await createUniswapV3PoolAsync(dai, zrx);
|
|
const liquidityProviderSubcall = getLiquidityProviderBatchSubcall();
|
|
const uniV3Subcall = getUniswapV3BatchSubcall([dai, zrx]);
|
|
const sushiswapSubcall = getUniswapV2BatchSubcall([dai.address, zrx.address], undefined, true);
|
|
const sellAmount = BigNumber.sum(
|
|
...[liquidityProviderSubcall, uniV3Subcall, sushiswapSubcall].map(c => c.sellAmount),
|
|
).minus(1);
|
|
await mintToAsync(dai, taker, sellAmount);
|
|
const tx = await multiplex
|
|
.multiplexBatchSellTokenForToken(
|
|
dai.address,
|
|
zrx.address,
|
|
[liquidityProviderSubcall, uniV3Subcall, sushiswapSubcall],
|
|
sellAmount,
|
|
constants.ZERO_AMOUNT,
|
|
)
|
|
.awaitTransactionSuccessAsync({ from: taker });
|
|
verifyEventsFromLogs<TransferEvent>(
|
|
tx.logs,
|
|
[
|
|
{
|
|
token: dai.address,
|
|
from: taker,
|
|
to: liquidityProvider.address,
|
|
value: liquidityProviderSubcall.sellAmount,
|
|
},
|
|
{
|
|
token: zrx.address,
|
|
from: liquidityProvider.address,
|
|
to: taker,
|
|
},
|
|
{
|
|
token: zrx.address,
|
|
from: uniV3.address,
|
|
to: taker,
|
|
},
|
|
{
|
|
token: dai.address,
|
|
from: taker,
|
|
to: uniV3.address,
|
|
value: uniV3Subcall.sellAmount,
|
|
},
|
|
{
|
|
token: dai.address,
|
|
from: taker,
|
|
to: sushiswap.address,
|
|
value: sushiswapSubcall.sellAmount.minus(1),
|
|
},
|
|
{
|
|
token: zrx.address,
|
|
from: sushiswap.address,
|
|
to: taker,
|
|
},
|
|
],
|
|
TestMintableERC20TokenEvents.Transfer,
|
|
);
|
|
});
|
|
it('proportional fill amounts', async () => {
|
|
const order = getTestRfqOrder();
|
|
const uniswap = await createUniswapV2PoolAsync(uniV2Factory, dai, zrx);
|
|
const sellAmount = toBaseUnitAmount(1);
|
|
await mintToAsync(dai, taker, sellAmount);
|
|
|
|
const rfqFillProportion = 0.42;
|
|
const rfqSubcall = await getRfqSubcallAsync(order, encodeFractionalFillAmount(rfqFillProportion));
|
|
// fractional fill amount 100% => the rest of the total sell amount is sold to Uniswap
|
|
const uniswapV2Subcall = getUniswapV2BatchSubcall(
|
|
[dai.address, zrx.address],
|
|
encodeFractionalFillAmount(1),
|
|
);
|
|
const tx = await multiplex
|
|
.multiplexBatchSellTokenForToken(
|
|
dai.address,
|
|
zrx.address,
|
|
[rfqSubcall, uniswapV2Subcall],
|
|
sellAmount,
|
|
constants.ZERO_AMOUNT,
|
|
)
|
|
.awaitTransactionSuccessAsync({ from: taker });
|
|
verifyEventsFromLogs<TransferEvent>(
|
|
tx.logs,
|
|
[
|
|
{
|
|
token: dai.address,
|
|
from: taker,
|
|
to: order.maker,
|
|
value: sellAmount.times(rfqFillProportion),
|
|
},
|
|
{
|
|
token: zrx.address,
|
|
from: order.maker,
|
|
to: taker,
|
|
},
|
|
{
|
|
token: dai.address,
|
|
from: taker,
|
|
to: uniswap.address,
|
|
value: sellAmount.minus(sellAmount.times(rfqFillProportion)),
|
|
},
|
|
{
|
|
token: zrx.address,
|
|
from: uniswap.address,
|
|
to: taker,
|
|
},
|
|
],
|
|
TestMintableERC20TokenEvents.Transfer,
|
|
);
|
|
});
|
|
it('RFQ, MultiHop(UniV3, UniV2)', async () => {
|
|
const order = getTestRfqOrder();
|
|
const rfqSubcall = await getRfqSubcallAsync(order);
|
|
const uniV3 = await createUniswapV3PoolAsync(dai, shib);
|
|
const uniV3Subcall = getUniswapV3MultiHopSubcall([dai, shib]);
|
|
const uniV2 = await createUniswapV2PoolAsync(uniV2Factory, shib, zrx);
|
|
const uniV2Subcall = getUniswapV2MultiHopSubcall([shib.address, zrx.address]);
|
|
const nestedMultiHopSubcall = getNestedMultiHopSellSubcall(
|
|
[dai.address, shib.address, zrx.address],
|
|
[uniV3Subcall, uniV2Subcall],
|
|
);
|
|
const sellAmount = rfqSubcall.sellAmount.plus(nestedMultiHopSubcall.sellAmount);
|
|
await mintToAsync(dai, taker, sellAmount);
|
|
|
|
const tx = await multiplex
|
|
.multiplexBatchSellTokenForToken(
|
|
dai.address,
|
|
zrx.address,
|
|
[rfqSubcall, nestedMultiHopSubcall],
|
|
sellAmount,
|
|
constants.ZERO_AMOUNT,
|
|
)
|
|
.awaitTransactionSuccessAsync({ from: taker });
|
|
verifyEventsFromLogs<TransferEvent>(
|
|
tx.logs,
|
|
[
|
|
{
|
|
token: dai.address,
|
|
from: taker,
|
|
to: order.maker,
|
|
value: order.takerAmount,
|
|
},
|
|
{
|
|
token: zrx.address,
|
|
from: order.maker,
|
|
to: taker,
|
|
value: order.makerAmount,
|
|
},
|
|
{
|
|
token: shib.address,
|
|
from: uniV3.address,
|
|
to: uniV2.address,
|
|
},
|
|
{
|
|
token: dai.address,
|
|
from: taker,
|
|
to: uniV3.address,
|
|
value: nestedMultiHopSubcall.sellAmount,
|
|
},
|
|
{
|
|
token: zrx.address,
|
|
from: uniV2.address,
|
|
to: taker,
|
|
},
|
|
],
|
|
TestMintableERC20TokenEvents.Transfer,
|
|
);
|
|
});
|
|
});
|
|
describe('multiplexBatchSellEthForToken', () => {
|
|
it('RFQ', async () => {
|
|
const order = getTestRfqOrder({ takerToken: weth.address });
|
|
const rfqSubcall = await getRfqSubcallAsync(order);
|
|
|
|
const tx = await multiplex
|
|
.multiplexBatchSellEthForToken(zrx.address, [rfqSubcall], constants.ZERO_AMOUNT)
|
|
.awaitTransactionSuccessAsync({ from: taker, value: order.takerAmount });
|
|
verifyEventsFromLogs(
|
|
tx.logs,
|
|
[{ owner: zeroEx.address, value: order.takerAmount }],
|
|
TestWethEvents.Deposit,
|
|
);
|
|
verifyEventsFromLogs<TransferEvent>(
|
|
tx.logs,
|
|
[
|
|
{
|
|
token: weth.address,
|
|
from: zeroEx.address,
|
|
to: order.maker,
|
|
value: order.takerAmount,
|
|
},
|
|
{
|
|
token: zrx.address,
|
|
from: order.maker,
|
|
to: taker,
|
|
value: order.makerAmount,
|
|
},
|
|
],
|
|
TestMintableERC20TokenEvents.Transfer,
|
|
);
|
|
});
|
|
it('OTC', async () => {
|
|
const order = getTestOtcOrder({ takerToken: weth.address });
|
|
const otcSubcall = await getOtcSubcallAsync(order);
|
|
|
|
const tx = await multiplex
|
|
.multiplexBatchSellEthForToken(zrx.address, [otcSubcall], constants.ZERO_AMOUNT)
|
|
.awaitTransactionSuccessAsync({ from: taker, value: order.takerAmount });
|
|
verifyEventsFromLogs(
|
|
tx.logs,
|
|
[{ owner: zeroEx.address, value: order.takerAmount }],
|
|
TestWethEvents.Deposit,
|
|
);
|
|
verifyEventsFromLogs<TransferEvent>(
|
|
tx.logs,
|
|
[
|
|
{
|
|
token: weth.address,
|
|
from: zeroEx.address,
|
|
to: order.maker,
|
|
value: order.takerAmount,
|
|
},
|
|
{
|
|
token: zrx.address,
|
|
from: order.maker,
|
|
to: taker,
|
|
value: order.makerAmount,
|
|
},
|
|
],
|
|
TestMintableERC20TokenEvents.Transfer,
|
|
);
|
|
});
|
|
it('UniswapV2', async () => {
|
|
const uniswap = await createUniswapV2PoolAsync(uniV2Factory, weth, zrx);
|
|
const uniswapV2Subcall = getUniswapV2BatchSubcall([weth.address, zrx.address]);
|
|
|
|
const tx = await multiplex
|
|
.multiplexBatchSellEthForToken(zrx.address, [uniswapV2Subcall], constants.ZERO_AMOUNT)
|
|
.awaitTransactionSuccessAsync({ from: taker, value: uniswapV2Subcall.sellAmount });
|
|
verifyEventsFromLogs(
|
|
tx.logs,
|
|
[{ owner: zeroEx.address, value: uniswapV2Subcall.sellAmount }],
|
|
TestWethEvents.Deposit,
|
|
);
|
|
verifyEventsFromLogs<TransferEvent>(
|
|
tx.logs,
|
|
[
|
|
{
|
|
token: weth.address,
|
|
from: zeroEx.address,
|
|
to: uniswap.address,
|
|
value: uniswapV2Subcall.sellAmount,
|
|
},
|
|
{
|
|
token: zrx.address,
|
|
from: uniswap.address,
|
|
to: taker,
|
|
},
|
|
],
|
|
TestMintableERC20TokenEvents.Transfer,
|
|
);
|
|
});
|
|
it('UniswapV3', async () => {
|
|
const uniV3 = await createUniswapV3PoolAsync(weth, zrx);
|
|
const uniswapV3Subcall = getUniswapV3BatchSubcall([weth, zrx]);
|
|
const tx = await multiplex
|
|
.multiplexBatchSellEthForToken(zrx.address, [uniswapV3Subcall], constants.ZERO_AMOUNT)
|
|
.awaitTransactionSuccessAsync({ from: taker, value: uniswapV3Subcall.sellAmount });
|
|
verifyEventsFromLogs(
|
|
tx.logs,
|
|
[{ owner: zeroEx.address, value: uniswapV3Subcall.sellAmount }],
|
|
TestWethEvents.Deposit,
|
|
);
|
|
verifyEventsFromLogs<TransferEvent>(
|
|
tx.logs,
|
|
[
|
|
{
|
|
token: zrx.address,
|
|
from: uniV3.address,
|
|
to: taker,
|
|
},
|
|
{
|
|
token: weth.address,
|
|
from: zeroEx.address,
|
|
to: uniV3.address,
|
|
value: uniswapV3Subcall.sellAmount,
|
|
},
|
|
],
|
|
TestMintableERC20TokenEvents.Transfer,
|
|
);
|
|
});
|
|
it('LiquidityProvider', async () => {
|
|
const liquidityProviderSubcall = getLiquidityProviderBatchSubcall();
|
|
const tx = await multiplex
|
|
.multiplexBatchSellEthForToken(zrx.address, [liquidityProviderSubcall], constants.ZERO_AMOUNT)
|
|
.awaitTransactionSuccessAsync({ from: taker, value: liquidityProviderSubcall.sellAmount });
|
|
verifyEventsFromLogs(
|
|
tx.logs,
|
|
[{ owner: zeroEx.address, value: liquidityProviderSubcall.sellAmount }],
|
|
TestWethEvents.Deposit,
|
|
);
|
|
verifyEventsFromLogs<TransferEvent>(
|
|
tx.logs,
|
|
[
|
|
{
|
|
token: weth.address,
|
|
from: zeroEx.address,
|
|
to: liquidityProvider.address,
|
|
value: liquidityProviderSubcall.sellAmount,
|
|
},
|
|
{
|
|
token: zrx.address,
|
|
from: liquidityProvider.address,
|
|
to: taker,
|
|
},
|
|
],
|
|
TestMintableERC20TokenEvents.Transfer,
|
|
);
|
|
});
|
|
it('TransformERC20', async () => {
|
|
const transformERC20Subcall = getTransformERC20Subcall(weth.address, zrx.address);
|
|
const tx = await multiplex
|
|
.multiplexBatchSellEthForToken(zrx.address, [transformERC20Subcall], constants.ZERO_AMOUNT)
|
|
.awaitTransactionSuccessAsync({ from: taker, value: transformERC20Subcall.sellAmount });
|
|
verifyEventsFromLogs(
|
|
tx.logs,
|
|
[{ owner: zeroEx.address, value: transformERC20Subcall.sellAmount }],
|
|
TestWethEvents.Deposit,
|
|
);
|
|
verifyEventsFromLogs<TransferEvent>(
|
|
tx.logs,
|
|
[
|
|
{
|
|
token: weth.address,
|
|
from: zeroEx.address,
|
|
to: flashWalletAddress,
|
|
value: transformERC20Subcall.sellAmount,
|
|
},
|
|
{
|
|
token: weth.address,
|
|
from: flashWalletAddress,
|
|
to: constants.NULL_ADDRESS,
|
|
},
|
|
{
|
|
token: zrx.address,
|
|
from: flashWalletAddress,
|
|
to: taker,
|
|
},
|
|
],
|
|
TestMintableERC20TokenEvents.Transfer,
|
|
);
|
|
});
|
|
it('RFQ, MultiHop(UniV3, UniV2)', async () => {
|
|
const order = getTestRfqOrder({ takerToken: weth.address, makerToken: zrx.address });
|
|
const rfqSubcall = await getRfqSubcallAsync(order);
|
|
const uniV3 = await createUniswapV3PoolAsync(weth, shib);
|
|
const uniV3Subcall = getUniswapV3MultiHopSubcall([weth, shib]);
|
|
const uniV2 = await createUniswapV2PoolAsync(uniV2Factory, shib, zrx);
|
|
const uniV2Subcall = getUniswapV2MultiHopSubcall([shib.address, zrx.address]);
|
|
const nestedMultiHopSubcall = getNestedMultiHopSellSubcall(
|
|
[weth.address, shib.address, zrx.address],
|
|
[uniV3Subcall, uniV2Subcall],
|
|
);
|
|
const sellAmount = rfqSubcall.sellAmount.plus(nestedMultiHopSubcall.sellAmount);
|
|
|
|
const tx = await multiplex
|
|
.multiplexBatchSellEthForToken(
|
|
zrx.address,
|
|
[rfqSubcall, nestedMultiHopSubcall],
|
|
constants.ZERO_AMOUNT,
|
|
)
|
|
.awaitTransactionSuccessAsync({ from: taker, value: sellAmount });
|
|
verifyEventsFromLogs(tx.logs, [{ owner: zeroEx.address, value: sellAmount }], TestWethEvents.Deposit);
|
|
verifyEventsFromLogs<TransferEvent>(
|
|
tx.logs,
|
|
[
|
|
{
|
|
token: weth.address,
|
|
from: zeroEx.address,
|
|
to: order.maker,
|
|
value: order.takerAmount,
|
|
},
|
|
{
|
|
token: zrx.address,
|
|
from: order.maker,
|
|
to: taker,
|
|
value: order.makerAmount,
|
|
},
|
|
{
|
|
token: shib.address,
|
|
from: uniV3.address,
|
|
to: uniV2.address,
|
|
},
|
|
{
|
|
token: weth.address,
|
|
from: zeroEx.address,
|
|
to: uniV3.address,
|
|
value: nestedMultiHopSubcall.sellAmount,
|
|
},
|
|
{
|
|
token: zrx.address,
|
|
from: uniV2.address,
|
|
to: taker,
|
|
},
|
|
],
|
|
TestMintableERC20TokenEvents.Transfer,
|
|
);
|
|
});
|
|
});
|
|
describe('multiplexBatchSellTokenForEth', () => {
|
|
it('RFQ', async () => {
|
|
const order = getTestRfqOrder({ makerToken: weth.address });
|
|
const rfqSubcall = await getRfqSubcallAsync(order);
|
|
await mintToAsync(dai, taker, order.takerAmount);
|
|
const tx = await multiplex
|
|
.multiplexBatchSellTokenForEth(dai.address, [rfqSubcall], order.takerAmount, constants.ZERO_AMOUNT)
|
|
.awaitTransactionSuccessAsync({ from: taker });
|
|
verifyEventsFromLogs(tx.logs, [{ owner: zeroEx.address }], TestWethEvents.Withdrawal);
|
|
verifyEventsFromLogs<TransferEvent>(
|
|
tx.logs,
|
|
[
|
|
{
|
|
token: dai.address,
|
|
from: taker,
|
|
to: order.maker,
|
|
value: order.takerAmount,
|
|
},
|
|
{
|
|
token: weth.address,
|
|
from: order.maker,
|
|
to: zeroEx.address,
|
|
value: order.makerAmount,
|
|
},
|
|
],
|
|
TestMintableERC20TokenEvents.Transfer,
|
|
);
|
|
});
|
|
it('OTC', async () => {
|
|
const order = getTestOtcOrder({ makerToken: weth.address });
|
|
const otcSubcall = await getOtcSubcallAsync(order);
|
|
await mintToAsync(dai, taker, order.takerAmount);
|
|
const tx = await multiplex
|
|
.multiplexBatchSellTokenForEth(dai.address, [otcSubcall], order.takerAmount, constants.ZERO_AMOUNT)
|
|
.awaitTransactionSuccessAsync({ from: taker });
|
|
verifyEventsFromLogs(tx.logs, [{ owner: zeroEx.address }], TestWethEvents.Withdrawal);
|
|
verifyEventsFromLogs<TransferEvent>(
|
|
tx.logs,
|
|
[
|
|
{
|
|
token: dai.address,
|
|
from: taker,
|
|
to: order.maker,
|
|
value: order.takerAmount,
|
|
},
|
|
{
|
|
token: weth.address,
|
|
from: order.maker,
|
|
to: zeroEx.address,
|
|
value: order.makerAmount,
|
|
},
|
|
],
|
|
TestMintableERC20TokenEvents.Transfer,
|
|
);
|
|
});
|
|
it('UniswapV2', async () => {
|
|
const uniswapV2Subcall = getUniswapV2BatchSubcall([dai.address, weth.address]);
|
|
const uniswap = await createUniswapV2PoolAsync(uniV2Factory, dai, weth);
|
|
await mintToAsync(dai, taker, uniswapV2Subcall.sellAmount);
|
|
|
|
const tx = await multiplex
|
|
.multiplexBatchSellTokenForEth(
|
|
dai.address,
|
|
[uniswapV2Subcall],
|
|
uniswapV2Subcall.sellAmount,
|
|
constants.ZERO_AMOUNT,
|
|
)
|
|
.awaitTransactionSuccessAsync({ from: taker });
|
|
verifyEventsFromLogs(tx.logs, [{ owner: zeroEx.address }], TestWethEvents.Withdrawal);
|
|
verifyEventsFromLogs<TransferEvent>(
|
|
tx.logs,
|
|
[
|
|
{
|
|
token: dai.address,
|
|
from: taker,
|
|
to: uniswap.address,
|
|
value: uniswapV2Subcall.sellAmount,
|
|
},
|
|
{
|
|
token: weth.address,
|
|
from: uniswap.address,
|
|
to: zeroEx.address,
|
|
},
|
|
],
|
|
TestMintableERC20TokenEvents.Transfer,
|
|
);
|
|
});
|
|
it('UniswapV3', async () => {
|
|
const uniswapV3Subcall = getUniswapV3BatchSubcall([dai, weth]);
|
|
const uniV3 = await createUniswapV3PoolAsync(dai, weth);
|
|
await mintToAsync(dai, taker, uniswapV3Subcall.sellAmount);
|
|
|
|
const tx = await multiplex
|
|
.multiplexBatchSellTokenForEth(
|
|
dai.address,
|
|
[uniswapV3Subcall],
|
|
uniswapV3Subcall.sellAmount,
|
|
constants.ZERO_AMOUNT,
|
|
)
|
|
.awaitTransactionSuccessAsync({ from: taker });
|
|
verifyEventsFromLogs(tx.logs, [{ owner: zeroEx.address }], TestWethEvents.Withdrawal);
|
|
verifyEventsFromLogs<TransferEvent>(
|
|
tx.logs,
|
|
[
|
|
{
|
|
token: weth.address,
|
|
from: uniV3.address,
|
|
to: zeroEx.address,
|
|
},
|
|
{
|
|
token: dai.address,
|
|
from: taker,
|
|
to: uniV3.address,
|
|
value: uniswapV3Subcall.sellAmount,
|
|
},
|
|
],
|
|
TestMintableERC20TokenEvents.Transfer,
|
|
);
|
|
});
|
|
it('LiquidityProvider', async () => {
|
|
const liquidityProviderSubcall = getLiquidityProviderBatchSubcall();
|
|
await mintToAsync(dai, taker, liquidityProviderSubcall.sellAmount);
|
|
|
|
const tx = await multiplex
|
|
.multiplexBatchSellTokenForEth(
|
|
dai.address,
|
|
[liquidityProviderSubcall],
|
|
liquidityProviderSubcall.sellAmount,
|
|
constants.ZERO_AMOUNT,
|
|
)
|
|
.awaitTransactionSuccessAsync({ from: taker });
|
|
verifyEventsFromLogs(tx.logs, [{ owner: zeroEx.address }], TestWethEvents.Withdrawal);
|
|
verifyEventsFromLogs<TransferEvent>(
|
|
tx.logs,
|
|
[
|
|
{
|
|
token: dai.address,
|
|
from: taker,
|
|
to: liquidityProvider.address,
|
|
value: liquidityProviderSubcall.sellAmount,
|
|
},
|
|
{
|
|
token: weth.address,
|
|
from: liquidityProvider.address,
|
|
to: zeroEx.address,
|
|
},
|
|
],
|
|
TestMintableERC20TokenEvents.Transfer,
|
|
);
|
|
});
|
|
it('TransformERC20', async () => {
|
|
const transformERC20Subcall = getTransformERC20Subcall(
|
|
dai.address,
|
|
weth.address,
|
|
undefined,
|
|
constants.ZERO_AMOUNT,
|
|
);
|
|
await mintToAsync(dai, taker, transformERC20Subcall.sellAmount);
|
|
|
|
const tx = await multiplex
|
|
.multiplexBatchSellTokenForEth(
|
|
dai.address,
|
|
[transformERC20Subcall],
|
|
transformERC20Subcall.sellAmount,
|
|
constants.ZERO_AMOUNT,
|
|
)
|
|
.awaitTransactionSuccessAsync({ from: taker });
|
|
verifyEventsFromLogs(tx.logs, [{ owner: zeroEx.address }], TestWethEvents.Withdrawal);
|
|
verifyEventsFromLogs<TransferEvent>(
|
|
tx.logs,
|
|
[
|
|
{
|
|
token: dai.address,
|
|
from: taker,
|
|
to: flashWalletAddress,
|
|
value: transformERC20Subcall.sellAmount,
|
|
},
|
|
{
|
|
token: dai.address,
|
|
from: flashWalletAddress,
|
|
to: constants.NULL_ADDRESS,
|
|
},
|
|
{
|
|
token: weth.address,
|
|
from: flashWalletAddress,
|
|
to: zeroEx.address,
|
|
},
|
|
],
|
|
TestMintableERC20TokenEvents.Transfer,
|
|
);
|
|
});
|
|
it('RFQ, MultiHop(UniV3, UniV2)', async () => {
|
|
const order = getTestRfqOrder({ takerToken: dai.address, makerToken: weth.address });
|
|
const rfqSubcall = await getRfqSubcallAsync(order);
|
|
const uniV3 = await createUniswapV3PoolAsync(dai, shib);
|
|
const uniV3Subcall = getUniswapV3MultiHopSubcall([dai, shib]);
|
|
const uniV2 = await createUniswapV2PoolAsync(uniV2Factory, shib, weth);
|
|
const uniV2Subcall = getUniswapV2MultiHopSubcall([shib.address, weth.address]);
|
|
const nestedMultiHopSubcall = getNestedMultiHopSellSubcall(
|
|
[dai.address, shib.address, weth.address],
|
|
[uniV3Subcall, uniV2Subcall],
|
|
);
|
|
const sellAmount = rfqSubcall.sellAmount.plus(nestedMultiHopSubcall.sellAmount);
|
|
await mintToAsync(dai, taker, sellAmount);
|
|
|
|
const tx = await multiplex
|
|
.multiplexBatchSellTokenForEth(
|
|
dai.address,
|
|
[rfqSubcall, nestedMultiHopSubcall],
|
|
sellAmount,
|
|
constants.ZERO_AMOUNT,
|
|
)
|
|
.awaitTransactionSuccessAsync({ from: taker });
|
|
verifyEventsFromLogs<TransferEvent>(
|
|
tx.logs,
|
|
[
|
|
{
|
|
token: dai.address,
|
|
from: taker,
|
|
to: order.maker,
|
|
value: order.takerAmount,
|
|
},
|
|
{
|
|
token: weth.address,
|
|
from: order.maker,
|
|
to: zeroEx.address,
|
|
value: order.makerAmount,
|
|
},
|
|
{
|
|
token: shib.address,
|
|
from: uniV3.address,
|
|
to: uniV2.address,
|
|
},
|
|
{
|
|
token: dai.address,
|
|
from: taker,
|
|
to: uniV3.address,
|
|
value: nestedMultiHopSubcall.sellAmount,
|
|
},
|
|
{
|
|
token: weth.address,
|
|
from: uniV2.address,
|
|
to: zeroEx.address,
|
|
},
|
|
],
|
|
TestMintableERC20TokenEvents.Transfer,
|
|
);
|
|
verifyEventsFromLogs(tx.logs, [{ owner: zeroEx.address }], TestWethEvents.Withdrawal);
|
|
});
|
|
});
|
|
});
|
|
describe('multihop sells', () => {
|
|
describe('multiplexMultiHopSellTokenForToken', () => {
|
|
it('reverts if given an invalid subcall type', async () => {
|
|
const invalidSubcall: MultiHopSellSubcall = {
|
|
id: MultiplexSubcall.Invalid,
|
|
data: constants.NULL_BYTES,
|
|
};
|
|
const tx = multiplex
|
|
.multiplexMultiHopSellTokenForToken(
|
|
[dai.address, zrx.address],
|
|
[invalidSubcall],
|
|
toBaseUnitAmount(1),
|
|
constants.ZERO_AMOUNT,
|
|
)
|
|
.awaitTransactionSuccessAsync({ from: taker });
|
|
return expect(tx).to.revertWith('MultiplexFeature::_computeHopTarget/INVALID_SUBCALL');
|
|
});
|
|
it('reverts if minBuyAmount is not satisfied', async () => {
|
|
const sellAmount = getRandomInteger(1, toBaseUnitAmount(1));
|
|
await createUniswapV2PoolAsync(uniV2Factory, dai, zrx);
|
|
const uniswapV2Subcall = getUniswapV2MultiHopSubcall([dai.address, zrx.address]);
|
|
await mintToAsync(dai, taker, sellAmount);
|
|
|
|
const tx = multiplex
|
|
.multiplexMultiHopSellTokenForToken(
|
|
[dai.address, zrx.address],
|
|
[uniswapV2Subcall],
|
|
sellAmount,
|
|
constants.MAX_UINT256,
|
|
)
|
|
.awaitTransactionSuccessAsync({ from: taker });
|
|
return expect(tx).to.revertWith('MultiplexFeature::_multiplexMultiHopSell/UNDERBOUGHT');
|
|
});
|
|
it('reverts if array lengths are mismatched', async () => {
|
|
const sellAmount = getRandomInteger(1, toBaseUnitAmount(1));
|
|
await createUniswapV2PoolAsync(uniV2Factory, dai, zrx);
|
|
const uniswapV2Subcall = getUniswapV2MultiHopSubcall([dai.address, zrx.address]);
|
|
await mintToAsync(dai, taker, sellAmount);
|
|
|
|
const tx = multiplex
|
|
.multiplexMultiHopSellTokenForToken(
|
|
[dai.address, zrx.address],
|
|
[uniswapV2Subcall, uniswapV2Subcall],
|
|
sellAmount,
|
|
constants.ZERO_AMOUNT,
|
|
)
|
|
.awaitTransactionSuccessAsync({ from: taker });
|
|
return expect(tx).to.revertWith('MultiplexFeature::_multiplexMultiHopSell/MISMATCHED_ARRAY_LENGTHS');
|
|
});
|
|
it('UniswapV2 -> LiquidityProvider', async () => {
|
|
const sellAmount = getRandomInteger(1, toBaseUnitAmount(1));
|
|
const buyAmount = getRandomInteger(1, toBaseUnitAmount(1));
|
|
const uniswap = await createUniswapV2PoolAsync(uniV2Factory, dai, shib);
|
|
const uniswapV2Subcall = getUniswapV2MultiHopSubcall([dai.address, shib.address]);
|
|
const liquidityProviderSubcall = getLiquidityProviderMultiHopSubcall();
|
|
await mintToAsync(dai, taker, sellAmount);
|
|
await mintToAsync(zrx, liquidityProvider.address, buyAmount);
|
|
|
|
const tx = await multiplex
|
|
.multiplexMultiHopSellTokenForToken(
|
|
[dai.address, shib.address, zrx.address],
|
|
[uniswapV2Subcall, liquidityProviderSubcall],
|
|
sellAmount,
|
|
buyAmount,
|
|
)
|
|
.awaitTransactionSuccessAsync({ from: taker });
|
|
verifyEventsFromLogs<TransferEvent>(
|
|
tx.logs,
|
|
[
|
|
{
|
|
token: dai.address,
|
|
from: taker,
|
|
to: uniswap.address,
|
|
value: sellAmount,
|
|
},
|
|
{
|
|
token: shib.address,
|
|
from: uniswap.address,
|
|
to: liquidityProvider.address,
|
|
},
|
|
{
|
|
token: zrx.address,
|
|
from: liquidityProvider.address,
|
|
to: taker,
|
|
value: buyAmount,
|
|
},
|
|
],
|
|
TestMintableERC20TokenEvents.Transfer,
|
|
);
|
|
});
|
|
it('LiquidityProvider -> Sushiswap', async () => {
|
|
const sellAmount = getRandomInteger(1, toBaseUnitAmount(1));
|
|
const shibAmount = getRandomInteger(1, toBaseUnitAmount(1));
|
|
const liquidityProviderSubcall = getLiquidityProviderMultiHopSubcall();
|
|
const sushiswap = await createUniswapV2PoolAsync(sushiFactory, shib, zrx);
|
|
const sushiswapSubcall = getUniswapV2MultiHopSubcall([shib.address, zrx.address], true);
|
|
await mintToAsync(dai, taker, sellAmount);
|
|
await mintToAsync(shib, liquidityProvider.address, shibAmount);
|
|
|
|
const tx = await multiplex
|
|
.multiplexMultiHopSellTokenForToken(
|
|
[dai.address, shib.address, zrx.address],
|
|
[liquidityProviderSubcall, sushiswapSubcall],
|
|
sellAmount,
|
|
constants.ZERO_AMOUNT,
|
|
)
|
|
.awaitTransactionSuccessAsync({ from: taker });
|
|
verifyEventsFromLogs<TransferEvent>(
|
|
tx.logs,
|
|
[
|
|
{
|
|
token: dai.address,
|
|
from: taker,
|
|
to: liquidityProvider.address,
|
|
value: sellAmount,
|
|
},
|
|
{
|
|
token: shib.address,
|
|
from: liquidityProvider.address,
|
|
to: sushiswap.address,
|
|
value: shibAmount,
|
|
},
|
|
{
|
|
token: zrx.address,
|
|
from: sushiswap.address,
|
|
to: taker,
|
|
},
|
|
],
|
|
TestMintableERC20TokenEvents.Transfer,
|
|
);
|
|
});
|
|
it('UniswapV3 -> BatchSell(RFQ, UniswapV2)', async () => {
|
|
const sellAmount = getRandomInteger(1, toBaseUnitAmount(1));
|
|
await mintToAsync(dai, taker, sellAmount);
|
|
const uniV3 = await createUniswapV3PoolAsync(dai, shib);
|
|
const uniV3Subcall = getUniswapV3MultiHopSubcall([dai, shib]);
|
|
const rfqOrder = getTestRfqOrder({ takerToken: shib.address, makerToken: zrx.address });
|
|
const rfqFillProportion = 0.42;
|
|
const rfqSubcall = await getRfqSubcallAsync(rfqOrder, encodeFractionalFillAmount(rfqFillProportion));
|
|
const uniV2 = await createUniswapV2PoolAsync(uniV2Factory, shib, zrx);
|
|
const uniV2Subcall = getUniswapV2BatchSubcall(
|
|
[shib.address, zrx.address],
|
|
encodeFractionalFillAmount(1),
|
|
);
|
|
const nestedBatchSellSubcall = getNestedBatchSellSubcall([rfqSubcall, uniV2Subcall]);
|
|
|
|
const tx = await multiplex
|
|
.multiplexMultiHopSellTokenForToken(
|
|
[dai.address, shib.address, zrx.address],
|
|
[uniV3Subcall, nestedBatchSellSubcall],
|
|
sellAmount,
|
|
constants.ZERO_AMOUNT,
|
|
)
|
|
.awaitTransactionSuccessAsync({ from: taker });
|
|
verifyEventsFromLogs<TransferEvent>(
|
|
tx.logs,
|
|
[
|
|
{
|
|
token: shib.address,
|
|
from: uniV3.address,
|
|
to: zeroEx.address,
|
|
},
|
|
{
|
|
token: dai.address,
|
|
from: taker,
|
|
to: uniV3.address,
|
|
value: sellAmount,
|
|
},
|
|
{
|
|
token: shib.address,
|
|
from: zeroEx.address,
|
|
to: maker,
|
|
},
|
|
{
|
|
token: zrx.address,
|
|
from: maker,
|
|
to: taker,
|
|
},
|
|
{
|
|
token: shib.address,
|
|
from: zeroEx.address,
|
|
to: uniV2.address,
|
|
},
|
|
{
|
|
token: zrx.address,
|
|
from: uniV2.address,
|
|
to: taker,
|
|
},
|
|
],
|
|
TestMintableERC20TokenEvents.Transfer,
|
|
);
|
|
});
|
|
it('BatchSell(RFQ, UniswapV2) -> UniswapV3', async () => {
|
|
const rfqOrder = getTestRfqOrder({ takerToken: dai.address, makerToken: shib.address });
|
|
const rfqSubcall = await getRfqSubcallAsync(rfqOrder, rfqOrder.takerAmount);
|
|
const uniV2 = await createUniswapV2PoolAsync(uniV2Factory, dai, shib);
|
|
const uniV2Subcall = getUniswapV2BatchSubcall([dai.address, shib.address]);
|
|
const sellAmount = rfqSubcall.sellAmount.plus(uniV2Subcall.sellAmount);
|
|
await mintToAsync(dai, taker, sellAmount);
|
|
const nestedBatchSellSubcall = getNestedBatchSellSubcall([rfqSubcall, uniV2Subcall]);
|
|
|
|
const uniV3 = await createUniswapV3PoolAsync(shib, zrx);
|
|
const uniV3Subcall = getUniswapV3MultiHopSubcall([shib, zrx]);
|
|
|
|
const tx = await multiplex
|
|
.multiplexMultiHopSellTokenForToken(
|
|
[dai.address, shib.address, zrx.address],
|
|
[nestedBatchSellSubcall, uniV3Subcall],
|
|
sellAmount,
|
|
constants.ZERO_AMOUNT,
|
|
)
|
|
.awaitTransactionSuccessAsync({ from: taker });
|
|
verifyEventsFromLogs<TransferEvent>(
|
|
tx.logs,
|
|
[
|
|
{
|
|
token: dai.address,
|
|
from: taker,
|
|
to: maker,
|
|
value: rfqOrder.takerAmount,
|
|
},
|
|
{
|
|
token: shib.address,
|
|
from: maker,
|
|
to: zeroEx.address,
|
|
value: rfqOrder.makerAmount,
|
|
},
|
|
{
|
|
token: dai.address,
|
|
from: taker,
|
|
to: uniV2.address,
|
|
value: uniV2Subcall.sellAmount,
|
|
},
|
|
{
|
|
token: shib.address,
|
|
from: uniV2.address,
|
|
to: zeroEx.address,
|
|
},
|
|
{
|
|
token: zrx.address,
|
|
from: uniV3.address,
|
|
to: taker,
|
|
},
|
|
{
|
|
token: shib.address,
|
|
from: zeroEx.address,
|
|
to: uniV3.address,
|
|
},
|
|
],
|
|
TestMintableERC20TokenEvents.Transfer,
|
|
);
|
|
});
|
|
});
|
|
describe('multiplexMultiHopSellEthForToken', () => {
|
|
it('reverts if first token is not WETH', async () => {
|
|
const sellAmount = getRandomInteger(1, toBaseUnitAmount(1));
|
|
await createUniswapV2PoolAsync(uniV2Factory, weth, zrx);
|
|
const uniswapV2Subcall = getUniswapV2MultiHopSubcall([weth.address, zrx.address]);
|
|
await mintToAsync(weth, taker, sellAmount);
|
|
|
|
const tx = multiplex
|
|
.multiplexMultiHopSellEthForToken(
|
|
[dai.address, zrx.address],
|
|
[uniswapV2Subcall],
|
|
constants.ZERO_AMOUNT,
|
|
)
|
|
.awaitTransactionSuccessAsync({ from: taker, value: sellAmount });
|
|
return expect(tx).to.revertWith('MultiplexFeature::multiplexMultiHopSellEthForToken/NOT_WETH');
|
|
});
|
|
it('UniswapV2 -> LiquidityProvider', async () => {
|
|
const sellAmount = getRandomInteger(1, toBaseUnitAmount(1));
|
|
const buyAmount = getRandomInteger(1, toBaseUnitAmount(1));
|
|
const uniswap = await createUniswapV2PoolAsync(uniV2Factory, weth, shib);
|
|
const uniswapV2Subcall = getUniswapV2MultiHopSubcall([weth.address, shib.address]);
|
|
const liquidityProviderSubcall = getLiquidityProviderMultiHopSubcall();
|
|
await mintToAsync(zrx, liquidityProvider.address, buyAmount);
|
|
|
|
const tx = await multiplex
|
|
.multiplexMultiHopSellEthForToken(
|
|
[weth.address, shib.address, zrx.address],
|
|
[uniswapV2Subcall, liquidityProviderSubcall],
|
|
buyAmount,
|
|
)
|
|
.awaitTransactionSuccessAsync({ from: taker, value: sellAmount });
|
|
verifyEventsFromLogs(tx.logs, [{ owner: zeroEx.address, value: sellAmount }], TestWethEvents.Deposit);
|
|
verifyEventsFromLogs<TransferEvent>(
|
|
tx.logs,
|
|
[
|
|
{
|
|
token: weth.address,
|
|
from: zeroEx.address,
|
|
to: uniswap.address,
|
|
value: sellAmount,
|
|
},
|
|
{
|
|
token: shib.address,
|
|
from: uniswap.address,
|
|
to: liquidityProvider.address,
|
|
},
|
|
{
|
|
token: zrx.address,
|
|
from: liquidityProvider.address,
|
|
to: taker,
|
|
value: buyAmount,
|
|
},
|
|
],
|
|
TestMintableERC20TokenEvents.Transfer,
|
|
);
|
|
});
|
|
it('LiquidityProvider -> Sushiswap', async () => {
|
|
const sellAmount = getRandomInteger(1, toBaseUnitAmount(1));
|
|
const shibAmount = getRandomInteger(1, toBaseUnitAmount(1));
|
|
const liquidityProviderSubcall = getLiquidityProviderMultiHopSubcall();
|
|
const sushiswap = await createUniswapV2PoolAsync(sushiFactory, shib, zrx);
|
|
const sushiswapSubcall = getUniswapV2MultiHopSubcall([shib.address, zrx.address], true);
|
|
await mintToAsync(shib, liquidityProvider.address, shibAmount);
|
|
|
|
const tx = await multiplex
|
|
.multiplexMultiHopSellEthForToken(
|
|
[weth.address, shib.address, zrx.address],
|
|
[liquidityProviderSubcall, sushiswapSubcall],
|
|
constants.ZERO_AMOUNT,
|
|
)
|
|
.awaitTransactionSuccessAsync({ from: taker, value: sellAmount });
|
|
verifyEventsFromLogs(tx.logs, [{ owner: zeroEx.address, value: sellAmount }], TestWethEvents.Deposit);
|
|
verifyEventsFromLogs<TransferEvent>(
|
|
tx.logs,
|
|
[
|
|
{
|
|
token: weth.address,
|
|
from: zeroEx.address,
|
|
to: liquidityProvider.address,
|
|
value: sellAmount,
|
|
},
|
|
{
|
|
token: shib.address,
|
|
from: liquidityProvider.address,
|
|
to: sushiswap.address,
|
|
value: shibAmount,
|
|
},
|
|
{
|
|
token: zrx.address,
|
|
from: sushiswap.address,
|
|
to: taker,
|
|
},
|
|
],
|
|
TestMintableERC20TokenEvents.Transfer,
|
|
);
|
|
});
|
|
it('UniswapV3 -> BatchSell(RFQ, UniswapV2)', async () => {
|
|
const sellAmount = getRandomInteger(1, toBaseUnitAmount(1));
|
|
const uniV3 = await createUniswapV3PoolAsync(weth, shib);
|
|
const uniV3Subcall = getUniswapV3MultiHopSubcall([weth, shib]);
|
|
const rfqOrder = getTestRfqOrder({ takerToken: shib.address, makerToken: zrx.address });
|
|
const rfqFillProportion = 0.42;
|
|
const rfqSubcall = await getRfqSubcallAsync(rfqOrder, encodeFractionalFillAmount(rfqFillProportion));
|
|
const uniV2 = await createUniswapV2PoolAsync(uniV2Factory, shib, zrx);
|
|
const uniV2Subcall = getUniswapV2BatchSubcall(
|
|
[shib.address, zrx.address],
|
|
encodeFractionalFillAmount(1),
|
|
);
|
|
const nestedBatchSellSubcall = getNestedBatchSellSubcall([rfqSubcall, uniV2Subcall]);
|
|
|
|
const tx = await multiplex
|
|
.multiplexMultiHopSellEthForToken(
|
|
[weth.address, shib.address, zrx.address],
|
|
[uniV3Subcall, nestedBatchSellSubcall],
|
|
constants.ZERO_AMOUNT,
|
|
)
|
|
.awaitTransactionSuccessAsync({ from: taker, value: sellAmount });
|
|
verifyEventsFromLogs(tx.logs, [{ owner: zeroEx.address, value: sellAmount }], TestWethEvents.Deposit);
|
|
verifyEventsFromLogs<TransferEvent>(
|
|
tx.logs,
|
|
[
|
|
{
|
|
token: shib.address,
|
|
from: uniV3.address,
|
|
to: zeroEx.address,
|
|
},
|
|
{
|
|
token: weth.address,
|
|
from: zeroEx.address,
|
|
to: uniV3.address,
|
|
value: sellAmount,
|
|
},
|
|
{
|
|
token: shib.address,
|
|
from: zeroEx.address,
|
|
to: maker,
|
|
},
|
|
{
|
|
token: zrx.address,
|
|
from: maker,
|
|
to: taker,
|
|
},
|
|
{
|
|
token: shib.address,
|
|
from: zeroEx.address,
|
|
to: uniV2.address,
|
|
},
|
|
{
|
|
token: zrx.address,
|
|
from: uniV2.address,
|
|
to: taker,
|
|
},
|
|
],
|
|
TestMintableERC20TokenEvents.Transfer,
|
|
);
|
|
});
|
|
it('BatchSell(RFQ, UniswapV2) -> UniswapV3', async () => {
|
|
const rfqOrder = getTestRfqOrder({ takerToken: weth.address, makerToken: shib.address });
|
|
const rfqSubcall = await getRfqSubcallAsync(rfqOrder, rfqOrder.takerAmount);
|
|
const uniV2 = await createUniswapV2PoolAsync(uniV2Factory, weth, shib);
|
|
const uniV2Subcall = getUniswapV2BatchSubcall([weth.address, shib.address]);
|
|
const sellAmount = rfqSubcall.sellAmount.plus(uniV2Subcall.sellAmount);
|
|
const nestedBatchSellSubcall = getNestedBatchSellSubcall([rfqSubcall, uniV2Subcall]);
|
|
|
|
const uniV3 = await createUniswapV3PoolAsync(shib, zrx);
|
|
const uniV3Subcall = getUniswapV3MultiHopSubcall([shib, zrx]);
|
|
|
|
const tx = await multiplex
|
|
.multiplexMultiHopSellEthForToken(
|
|
[weth.address, shib.address, zrx.address],
|
|
[nestedBatchSellSubcall, uniV3Subcall],
|
|
constants.ZERO_AMOUNT,
|
|
)
|
|
.awaitTransactionSuccessAsync({ from: taker, value: sellAmount });
|
|
verifyEventsFromLogs(tx.logs, [{ owner: zeroEx.address, value: sellAmount }], TestWethEvents.Deposit);
|
|
verifyEventsFromLogs<TransferEvent>(
|
|
tx.logs,
|
|
[
|
|
{
|
|
token: weth.address,
|
|
from: zeroEx.address,
|
|
to: maker,
|
|
value: rfqOrder.takerAmount,
|
|
},
|
|
{
|
|
token: shib.address,
|
|
from: maker,
|
|
to: zeroEx.address,
|
|
value: rfqOrder.makerAmount,
|
|
},
|
|
{
|
|
token: weth.address,
|
|
from: zeroEx.address,
|
|
to: uniV2.address,
|
|
value: uniV2Subcall.sellAmount,
|
|
},
|
|
{
|
|
token: shib.address,
|
|
from: uniV2.address,
|
|
to: zeroEx.address,
|
|
},
|
|
{
|
|
token: zrx.address,
|
|
from: uniV3.address,
|
|
to: taker,
|
|
},
|
|
{
|
|
token: shib.address,
|
|
from: zeroEx.address,
|
|
to: uniV3.address,
|
|
},
|
|
],
|
|
TestMintableERC20TokenEvents.Transfer,
|
|
);
|
|
});
|
|
});
|
|
describe('multiplexMultiHopSellTokenForEth', () => {
|
|
it('reverts if last token is not WETH', async () => {
|
|
const sellAmount = getRandomInteger(1, toBaseUnitAmount(1));
|
|
await createUniswapV2PoolAsync(uniV2Factory, zrx, weth);
|
|
const uniswapV2Subcall = getUniswapV2MultiHopSubcall([zrx.address, weth.address]);
|
|
await mintToAsync(zrx, taker, sellAmount);
|
|
|
|
const tx = multiplex
|
|
.multiplexMultiHopSellTokenForEth(
|
|
[zrx.address, dai.address],
|
|
[uniswapV2Subcall],
|
|
sellAmount,
|
|
constants.ZERO_AMOUNT,
|
|
)
|
|
.awaitTransactionSuccessAsync({ from: taker });
|
|
return expect(tx).to.revertWith('MultiplexFeature::multiplexMultiHopSellTokenForEth/NOT_WETH');
|
|
});
|
|
it('UniswapV2 -> LiquidityProvider', async () => {
|
|
const sellAmount = getRandomInteger(1, toBaseUnitAmount(1));
|
|
const buyAmount = getRandomInteger(1, toBaseUnitAmount(1));
|
|
const uniswap = await createUniswapV2PoolAsync(uniV2Factory, dai, shib);
|
|
const uniswapV2Subcall = getUniswapV2MultiHopSubcall([dai.address, shib.address]);
|
|
const liquidityProviderSubcall = getLiquidityProviderMultiHopSubcall();
|
|
await mintToAsync(dai, taker, sellAmount);
|
|
await mintToAsync(weth, liquidityProvider.address, buyAmount);
|
|
|
|
const tx = await multiplex
|
|
.multiplexMultiHopSellTokenForEth(
|
|
[dai.address, shib.address, weth.address],
|
|
[uniswapV2Subcall, liquidityProviderSubcall],
|
|
sellAmount,
|
|
buyAmount,
|
|
)
|
|
.awaitTransactionSuccessAsync({ from: taker });
|
|
verifyEventsFromLogs<TransferEvent>(
|
|
tx.logs,
|
|
[
|
|
{
|
|
token: dai.address,
|
|
from: taker,
|
|
to: uniswap.address,
|
|
value: sellAmount,
|
|
},
|
|
{
|
|
token: shib.address,
|
|
from: uniswap.address,
|
|
to: liquidityProvider.address,
|
|
},
|
|
{
|
|
token: weth.address,
|
|
from: liquidityProvider.address,
|
|
to: zeroEx.address,
|
|
value: buyAmount,
|
|
},
|
|
],
|
|
TestMintableERC20TokenEvents.Transfer,
|
|
);
|
|
verifyEventsFromLogs(tx.logs, [{ owner: zeroEx.address, value: buyAmount }], TestWethEvents.Withdrawal);
|
|
});
|
|
it('LiquidityProvider -> Sushiswap', async () => {
|
|
const sellAmount = getRandomInteger(1, toBaseUnitAmount(1));
|
|
const shibAmount = getRandomInteger(1, toBaseUnitAmount(1));
|
|
const liquidityProviderSubcall = getLiquidityProviderMultiHopSubcall();
|
|
const sushiswap = await createUniswapV2PoolAsync(sushiFactory, shib, weth);
|
|
const sushiswapSubcall = getUniswapV2MultiHopSubcall([shib.address, weth.address], true);
|
|
await mintToAsync(dai, taker, sellAmount);
|
|
await mintToAsync(shib, liquidityProvider.address, shibAmount);
|
|
|
|
const tx = await multiplex
|
|
.multiplexMultiHopSellTokenForEth(
|
|
[dai.address, shib.address, weth.address],
|
|
[liquidityProviderSubcall, sushiswapSubcall],
|
|
sellAmount,
|
|
constants.ZERO_AMOUNT,
|
|
)
|
|
.awaitTransactionSuccessAsync({ from: taker });
|
|
verifyEventsFromLogs<TransferEvent>(
|
|
tx.logs,
|
|
[
|
|
{
|
|
token: dai.address,
|
|
from: taker,
|
|
to: liquidityProvider.address,
|
|
value: sellAmount,
|
|
},
|
|
{
|
|
token: shib.address,
|
|
from: liquidityProvider.address,
|
|
to: sushiswap.address,
|
|
value: shibAmount,
|
|
},
|
|
{
|
|
token: weth.address,
|
|
from: sushiswap.address,
|
|
to: zeroEx.address,
|
|
},
|
|
],
|
|
TestMintableERC20TokenEvents.Transfer,
|
|
);
|
|
verifyEventsFromLogs(tx.logs, [{ owner: zeroEx.address }], TestWethEvents.Withdrawal);
|
|
});
|
|
it('UniswapV3 -> BatchSell(RFQ, UniswapV2)', async () => {
|
|
const sellAmount = getRandomInteger(1, toBaseUnitAmount(1));
|
|
const uniV3 = await createUniswapV3PoolAsync(dai, shib);
|
|
const uniV3Subcall = getUniswapV3MultiHopSubcall([dai, shib]);
|
|
const rfqOrder = getTestRfqOrder({ takerToken: shib.address, makerToken: weth.address });
|
|
const rfqFillProportion = 0.42;
|
|
const rfqSubcall = await getRfqSubcallAsync(rfqOrder, encodeFractionalFillAmount(rfqFillProportion));
|
|
const uniV2 = await createUniswapV2PoolAsync(uniV2Factory, shib, weth);
|
|
const uniV2Subcall = getUniswapV2BatchSubcall(
|
|
[shib.address, weth.address],
|
|
encodeFractionalFillAmount(1),
|
|
);
|
|
const nestedBatchSellSubcall = getNestedBatchSellSubcall([rfqSubcall, uniV2Subcall]);
|
|
await mintToAsync(dai, taker, sellAmount);
|
|
|
|
const tx = await multiplex
|
|
.multiplexMultiHopSellTokenForEth(
|
|
[dai.address, shib.address, weth.address],
|
|
[uniV3Subcall, nestedBatchSellSubcall],
|
|
sellAmount,
|
|
constants.ZERO_AMOUNT,
|
|
)
|
|
.awaitTransactionSuccessAsync({ from: taker });
|
|
verifyEventsFromLogs<TransferEvent>(
|
|
tx.logs,
|
|
[
|
|
{
|
|
token: shib.address,
|
|
from: uniV3.address,
|
|
to: zeroEx.address,
|
|
},
|
|
{
|
|
token: dai.address,
|
|
from: taker,
|
|
to: uniV3.address,
|
|
value: sellAmount,
|
|
},
|
|
{
|
|
token: shib.address,
|
|
from: zeroEx.address,
|
|
to: maker,
|
|
},
|
|
{
|
|
token: weth.address,
|
|
from: maker,
|
|
to: zeroEx.address,
|
|
},
|
|
{
|
|
token: shib.address,
|
|
from: zeroEx.address,
|
|
to: uniV2.address,
|
|
},
|
|
{
|
|
token: weth.address,
|
|
from: uniV2.address,
|
|
to: zeroEx.address,
|
|
},
|
|
],
|
|
TestMintableERC20TokenEvents.Transfer,
|
|
);
|
|
verifyEventsFromLogs(tx.logs, [{ owner: zeroEx.address }], TestWethEvents.Withdrawal);
|
|
});
|
|
it('BatchSell(RFQ, UniswapV2) -> UniswapV3', async () => {
|
|
const rfqOrder = getTestRfqOrder({ takerToken: dai.address, makerToken: shib.address });
|
|
const rfqSubcall = await getRfqSubcallAsync(rfqOrder, rfqOrder.takerAmount);
|
|
const uniV2 = await createUniswapV2PoolAsync(uniV2Factory, dai, shib);
|
|
const uniV2Subcall = getUniswapV2BatchSubcall([dai.address, shib.address]);
|
|
const sellAmount = rfqSubcall.sellAmount.plus(uniV2Subcall.sellAmount);
|
|
const nestedBatchSellSubcall = getNestedBatchSellSubcall([rfqSubcall, uniV2Subcall]);
|
|
await mintToAsync(dai, taker, sellAmount);
|
|
const uniV3 = await createUniswapV3PoolAsync(shib, weth);
|
|
const uniV3Subcall = getUniswapV3MultiHopSubcall([shib, weth]);
|
|
|
|
const tx = await multiplex
|
|
.multiplexMultiHopSellTokenForEth(
|
|
[dai.address, shib.address, weth.address],
|
|
[nestedBatchSellSubcall, uniV3Subcall],
|
|
sellAmount,
|
|
constants.ZERO_AMOUNT,
|
|
)
|
|
.awaitTransactionSuccessAsync({ from: taker });
|
|
verifyEventsFromLogs<TransferEvent>(
|
|
tx.logs,
|
|
[
|
|
{
|
|
token: dai.address,
|
|
from: taker,
|
|
to: maker,
|
|
value: rfqOrder.takerAmount,
|
|
},
|
|
{
|
|
token: shib.address,
|
|
from: maker,
|
|
to: zeroEx.address,
|
|
value: rfqOrder.makerAmount,
|
|
},
|
|
{
|
|
token: dai.address,
|
|
from: taker,
|
|
to: uniV2.address,
|
|
value: uniV2Subcall.sellAmount,
|
|
},
|
|
{
|
|
token: shib.address,
|
|
from: uniV2.address,
|
|
to: zeroEx.address,
|
|
},
|
|
{
|
|
token: weth.address,
|
|
from: uniV3.address,
|
|
to: zeroEx.address,
|
|
},
|
|
{
|
|
token: shib.address,
|
|
from: zeroEx.address,
|
|
to: uniV3.address,
|
|
},
|
|
],
|
|
TestMintableERC20TokenEvents.Transfer,
|
|
);
|
|
verifyEventsFromLogs(tx.logs, [{ owner: zeroEx.address }], TestWethEvents.Withdrawal);
|
|
});
|
|
});
|
|
});
|
|
});
|