369 lines
14 KiB
TypeScript
369 lines
14 KiB
TypeScript
|
import { ethers, network, waffle } from 'hardhat';
|
||
|
import { swapContractFixtureInFork } from './shared/fixtures';
|
||
|
import { Wallet } from '@ethersproject/wallet';
|
||
|
import { RubicRouterV2, TestERC20, TestMessages, WETH9 } from '../typechain';
|
||
|
import { expect } from 'chai';
|
||
|
import {
|
||
|
DEADLINE,
|
||
|
DST_CHAIN_ID,
|
||
|
VERSION,
|
||
|
ZERO_ADDRESS,
|
||
|
DEFAULT_AMOUNT_OUT_MIN,
|
||
|
EXECUTOR_ADDRESS,
|
||
|
INTEGRATOR,
|
||
|
DEFAULT_AMOUNT_IN_USDC
|
||
|
} from './shared/consts';
|
||
|
import { BigNumber as BN, BigNumberish, ContractTransaction } from 'ethers';
|
||
|
const hre = require('hardhat');
|
||
|
|
||
|
const createFixtureLoader = waffle.createFixtureLoader;
|
||
|
|
||
|
const envConfig = require('dotenv').config();
|
||
|
const {
|
||
|
ROUTERS_POLYGON: TEST_ROUTERS,
|
||
|
NATIVE_POLYGON: TEST_NATIVE,
|
||
|
BUS_POLYGON_MAIN: TEST_BUS
|
||
|
} = envConfig.parsed || {};
|
||
|
|
||
|
describe('RubicFallback', () => {
|
||
|
let wallet: Wallet, other: Wallet;
|
||
|
let swapToken: TestERC20;
|
||
|
let transitToken: TestERC20;
|
||
|
let swapMain: RubicRouterV2;
|
||
|
let router: string;
|
||
|
let routerV3: string;
|
||
|
let wnative: WETH9;
|
||
|
let chainId: number;
|
||
|
|
||
|
let testMessagesContract: TestMessages;
|
||
|
|
||
|
let loadFixture: ReturnType<typeof createFixtureLoader>;
|
||
|
|
||
|
async function getMessage(
|
||
|
messagesContract: TestMessages,
|
||
|
_nonce: BigNumberish,
|
||
|
dstChainId: BigNumberish,
|
||
|
{
|
||
|
dex = router,
|
||
|
receiverEOA = other.address,
|
||
|
integrator = INTEGRATOR,
|
||
|
version = VERSION,
|
||
|
path = [wnative.address, transitToken.address],
|
||
|
pathV3 = '0x',
|
||
|
deadline = DEADLINE,
|
||
|
amountOutMinimum = DEFAULT_AMOUNT_OUT_MIN,
|
||
|
_receiver = wallet.address,
|
||
|
nativeOut = true
|
||
|
} = {}
|
||
|
): Promise<string> {
|
||
|
return messagesContract.getMessage(
|
||
|
{
|
||
|
dex,
|
||
|
nativeOut,
|
||
|
receiverEOA,
|
||
|
integrator,
|
||
|
version,
|
||
|
path,
|
||
|
pathV3,
|
||
|
deadline,
|
||
|
amountOutMinimum
|
||
|
},
|
||
|
_nonce,
|
||
|
dstChainId
|
||
|
);
|
||
|
}
|
||
|
|
||
|
async function encodePath(tokens): Promise<string> {
|
||
|
return tokens[0] + '000bb8' + tokens[1].slice(2);
|
||
|
}
|
||
|
|
||
|
async function getID(
|
||
|
messagesContract: TestMessages,
|
||
|
_nonce: BigNumberish,
|
||
|
{
|
||
|
dex = router,
|
||
|
receiverEOA = other.address,
|
||
|
integrator = ZERO_ADDRESS,
|
||
|
version = VERSION,
|
||
|
path = [wnative.address, transitToken.address],
|
||
|
pathV3 = '0x',
|
||
|
deadline = DEADLINE,
|
||
|
amountOutMinimum = DEFAULT_AMOUNT_OUT_MIN,
|
||
|
_receiver = wallet.address,
|
||
|
nativeOut = false,
|
||
|
_srcChainId = chainId,
|
||
|
_dstChainId = DST_CHAIN_ID
|
||
|
} = {}
|
||
|
): Promise<string> {
|
||
|
return messagesContract.getID(
|
||
|
_srcChainId,
|
||
|
_dstChainId,
|
||
|
{
|
||
|
dex,
|
||
|
nativeOut,
|
||
|
receiverEOA,
|
||
|
integrator,
|
||
|
version,
|
||
|
path,
|
||
|
pathV3,
|
||
|
deadline,
|
||
|
amountOutMinimum
|
||
|
},
|
||
|
_nonce
|
||
|
);
|
||
|
}
|
||
|
|
||
|
async function callExecuteMessageWithTransferFallback({
|
||
|
sender = ZERO_ADDRESS,
|
||
|
tokenIn = transitToken.address,
|
||
|
amountIn = DEFAULT_AMOUNT_IN_USDC,
|
||
|
srcChainID = chainId,
|
||
|
message = '0x',
|
||
|
executor = EXECUTOR_ADDRESS
|
||
|
} = {}): Promise<ContractTransaction> {
|
||
|
await hre.network.provider.request({
|
||
|
method: 'hardhat_impersonateAccount',
|
||
|
params: [TEST_BUS]
|
||
|
});
|
||
|
|
||
|
const bus = await ethers.getSigner(TEST_BUS);
|
||
|
|
||
|
await network.provider.send('hardhat_setBalance', [
|
||
|
bus.address,
|
||
|
'0x152D02C7E14AF6800000' // 100000 eth
|
||
|
]);
|
||
|
|
||
|
const _swapMain = swapMain.connect(bus);
|
||
|
|
||
|
return _swapMain.executeMessageWithTransferFallback(
|
||
|
sender,
|
||
|
tokenIn,
|
||
|
amountIn,
|
||
|
srcChainID,
|
||
|
message,
|
||
|
executor
|
||
|
// { value: nativeIn === null ? cryptoFee.add(ethers.utils.parseEther('0.01')) : nativeIn }
|
||
|
);
|
||
|
}
|
||
|
|
||
|
async function callExecuteMessageWithTransferRefund({
|
||
|
tokenIn = transitToken.address,
|
||
|
amountIn = DEFAULT_AMOUNT_IN_USDC,
|
||
|
message = '0x',
|
||
|
executor = EXECUTOR_ADDRESS
|
||
|
} = {}): Promise<ContractTransaction> {
|
||
|
await hre.network.provider.request({
|
||
|
method: 'hardhat_impersonateAccount',
|
||
|
params: [TEST_BUS]
|
||
|
});
|
||
|
|
||
|
const bus = await ethers.getSigner(TEST_BUS);
|
||
|
|
||
|
await network.provider.send('hardhat_setBalance', [
|
||
|
bus.address,
|
||
|
'0x152D02C7E14AF6800000' // 100000 eth
|
||
|
]);
|
||
|
|
||
|
const _swapMain = swapMain.connect(bus);
|
||
|
|
||
|
return _swapMain.executeMessageWithTransferRefund(tokenIn, amountIn, message, executor);
|
||
|
}
|
||
|
|
||
|
before('create fixture loader', async () => {
|
||
|
[wallet, other] = await (ethers as any).getSigners();
|
||
|
loadFixture = createFixtureLoader([wallet, other]);
|
||
|
chainId = (await ethers.provider.getNetwork()).chainId;
|
||
|
});
|
||
|
|
||
|
beforeEach('deploy fixture', async () => {
|
||
|
({ swapMain, swapToken, transitToken, wnative, router, routerV3, testMessagesContract } =
|
||
|
await loadFixture(swapContractFixtureInFork));
|
||
|
});
|
||
|
|
||
|
it('constructor initializes', async () => {
|
||
|
expect(await swapMain.nativeWrap()).to.eq(TEST_NATIVE);
|
||
|
expect(await swapMain.messageBus()).to.eq(TEST_BUS);
|
||
|
|
||
|
const routers = TEST_ROUTERS.split(',');
|
||
|
expect(await swapMain.getAvailableRouters()).to.deep.eq(routers);
|
||
|
});
|
||
|
|
||
|
describe('#Fallback and refund tests', () => {
|
||
|
describe('#executeMessageWithTransferFallback', () => {
|
||
|
beforeEach('Setup for target executions', async () => {
|
||
|
// transfer 1000 USDC
|
||
|
await transitToken.transfer(swapMain.address, ethers.BigNumber.from('1000000000'));
|
||
|
});
|
||
|
describe('Fallback should emit correct event in dst chain', async () => {
|
||
|
let nonce: BN;
|
||
|
let message: string;
|
||
|
|
||
|
beforeEach('setup before fallback', async () => {
|
||
|
nonce = (await swapMain.nonce()).add('1');
|
||
|
|
||
|
message = await getMessage(testMessagesContract, nonce, DST_CHAIN_ID, {
|
||
|
dex: ZERO_ADDRESS,
|
||
|
version: 2,
|
||
|
path: [transitToken.address],
|
||
|
amountOutMinimum: ethers.BigNumber.from('0')
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('Should successfully fallback token with failed bridge', async () => {
|
||
|
// const ID = await getID(
|
||
|
// testMessagesContract,
|
||
|
// (await swapMain.nonce()).add('1'),
|
||
|
// {
|
||
|
// dex: ZERO_ADDRESS,
|
||
|
// version: 2,
|
||
|
// path: [transitToken.address],
|
||
|
// amountOutMinimum: ethers.BigNumber.from('0')
|
||
|
// }
|
||
|
// );
|
||
|
|
||
|
const balanceBefore = await transitToken.balanceOf(wallet.address);
|
||
|
|
||
|
await expect(
|
||
|
await callExecuteMessageWithTransferFallback({
|
||
|
message: message
|
||
|
})
|
||
|
).to.emit(swapMain, 'CrossChainProcessed');
|
||
|
//.withArgs(ID, DEFAULT_AMOUNT_IN_USDC, '3');
|
||
|
const balanceAfter = await transitToken.balanceOf(wallet.address);
|
||
|
await expect(balanceBefore).to.be.eq(balanceAfter);
|
||
|
});
|
||
|
|
||
|
it('Should successfully fallback token with failed V3', async () => {
|
||
|
nonce = (await swapMain.nonce()).add('1');
|
||
|
|
||
|
const path = await encodePath([transitToken.address, swapToken.address]);
|
||
|
|
||
|
message = await getMessage(testMessagesContract, nonce, DST_CHAIN_ID, {
|
||
|
dex: routerV3,
|
||
|
version: 1,
|
||
|
pathV3: path
|
||
|
});
|
||
|
|
||
|
const balanceBefore = await transitToken.balanceOf(wallet.address);
|
||
|
|
||
|
await expect(
|
||
|
await callExecuteMessageWithTransferFallback({
|
||
|
message: message
|
||
|
})
|
||
|
).to.emit(swapMain, 'CrossChainProcessed');
|
||
|
const balanceAfter = await transitToken.balanceOf(wallet.address);
|
||
|
await expect(balanceBefore).to.be.eq(balanceAfter);
|
||
|
});
|
||
|
|
||
|
it('Should successfully fallback token with failed V2', async () => {
|
||
|
nonce = (await swapMain.nonce()).add('1');
|
||
|
|
||
|
message = await getMessage(testMessagesContract, nonce, DST_CHAIN_ID, {
|
||
|
dex: router,
|
||
|
version: 0,
|
||
|
path: [transitToken.address, swapToken.address]
|
||
|
});
|
||
|
const balanceBefore = await transitToken.balanceOf(wallet.address);
|
||
|
await expect(
|
||
|
await callExecuteMessageWithTransferFallback({
|
||
|
message: message
|
||
|
})
|
||
|
).to.emit(swapMain, 'CrossChainProcessed');
|
||
|
const balanceAfter = await transitToken.balanceOf(wallet.address);
|
||
|
await expect(balanceBefore).to.be.eq(balanceAfter);
|
||
|
});
|
||
|
});
|
||
|
|
||
|
describe('#executeMessageWithTransferRefund', () => {
|
||
|
beforeEach('Setup for target executions', async () => {
|
||
|
// transfer 1000 USDC
|
||
|
await transitToken.transfer(swapMain.address, '1000000000');
|
||
|
});
|
||
|
describe('Refund should emit correct event in src chain', async () => {
|
||
|
let nonce: BN;
|
||
|
let message: string;
|
||
|
|
||
|
it('Should successfully refund token with failed bridge', async () => {
|
||
|
nonce = (await swapMain.nonce()).add('1');
|
||
|
|
||
|
message = await getMessage(testMessagesContract, nonce, DST_CHAIN_ID, {
|
||
|
dex: ZERO_ADDRESS,
|
||
|
version: 2,
|
||
|
path: [transitToken.address],
|
||
|
amountOutMinimum: ethers.BigNumber.from('0')
|
||
|
});
|
||
|
|
||
|
// const ID = await getID(
|
||
|
// testMessagesContract,
|
||
|
// (await swapMain.nonce()).add('1'),
|
||
|
// {
|
||
|
// dex: ZERO_ADDRESS,
|
||
|
// version: 2,
|
||
|
// path: [transitToken.address],
|
||
|
// amountOutMinimum: ethers.BigNumber.from('0')
|
||
|
// }
|
||
|
// );
|
||
|
|
||
|
const balanceBefore = await transitToken.balanceOf(wallet.address);
|
||
|
|
||
|
await expect(
|
||
|
await callExecuteMessageWithTransferRefund({
|
||
|
message: message
|
||
|
})
|
||
|
).to.emit(swapMain, 'CrossChainProcessed');
|
||
|
// .withArgs(ID, DEFAULT_AMOUNT_IN_USDC, '3');
|
||
|
|
||
|
const balanceAfter = await transitToken.balanceOf(wallet.address);
|
||
|
await expect(balanceBefore.add(DEFAULT_AMOUNT_IN_USDC)).to.be.eq(
|
||
|
balanceAfter
|
||
|
);
|
||
|
});
|
||
|
|
||
|
it('Should successfully refund token with failed V3', async () => {
|
||
|
nonce = (await swapMain.nonce()).add('1');
|
||
|
|
||
|
const path = await encodePath([transitToken.address, swapToken.address]);
|
||
|
|
||
|
message = await getMessage(testMessagesContract, nonce, DST_CHAIN_ID, {
|
||
|
dex: routerV3,
|
||
|
version: 1,
|
||
|
pathV3: path
|
||
|
});
|
||
|
const balanceBefore = await transitToken.balanceOf(wallet.address);
|
||
|
|
||
|
await expect(
|
||
|
await callExecuteMessageWithTransferRefund({
|
||
|
message: message
|
||
|
})
|
||
|
).to.emit(swapMain, 'CrossChainProcessed');
|
||
|
const balanceAfter = await transitToken.balanceOf(wallet.address);
|
||
|
await expect(balanceBefore.add(DEFAULT_AMOUNT_IN_USDC)).to.be.eq(
|
||
|
balanceAfter
|
||
|
);
|
||
|
});
|
||
|
|
||
|
it('Should successfully refund token with failed V2', async () => {
|
||
|
nonce = (await swapMain.nonce()).add('1');
|
||
|
|
||
|
message = await getMessage(testMessagesContract, nonce, DST_CHAIN_ID, {
|
||
|
dex: router,
|
||
|
version: 0,
|
||
|
path: [transitToken.address, swapToken.address]
|
||
|
});
|
||
|
const balanceBefore = await transitToken.balanceOf(wallet.address);
|
||
|
await expect(
|
||
|
await callExecuteMessageWithTransferRefund({
|
||
|
message: message
|
||
|
})
|
||
|
).to.emit(swapMain, 'CrossChainProcessed');
|
||
|
const balanceAfter = await transitToken.balanceOf(wallet.address);
|
||
|
await expect(balanceBefore.add(DEFAULT_AMOUNT_IN_USDC)).to.be.eq(
|
||
|
balanceAfter
|
||
|
);
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|