df8039f494
new file: Interchain-message/.eslintrc.js new file: Interchain-message/.gitignore new file: Interchain-message/.prettierrc new file: Interchain-message/.solhint.json new file: Interchain-message/.solhintignore new file: Interchain-message/contracts/interfaces/IBridge.sol new file: Interchain-message/contracts/interfaces/IMessageBus.sol new file: Interchain-message/contracts/interfaces/IMessageReceiverApp.sol new file: Interchain-message/contracts/interfaces/IUniswapRouterV3.sol new file: Interchain-message/contracts/interfaces/IWETH.sol new file: Interchain-message/contracts/message/apps/BridgeSwap.sol new file: Interchain-message/contracts/message/apps/RubicRouterV2.sol new file: Interchain-message/contracts/message/apps/RubicRouterV2ETH.sol new file: Interchain-message/contracts/message/apps/SwapBase.sol new file: Interchain-message/contracts/message/apps/TransferSwapInch.sol new file: Interchain-message/contracts/message/apps/TransferSwapV2.sol new file: Interchain-message/contracts/message/apps/TransferSwapV3.sol new file: Interchain-message/contracts/message/framework/MessageReceiverApp.sol new file: Interchain-message/contracts/message/framework/MessageSenderApp.sol new file: Interchain-message/contracts/message/libraries/MessageSenderLib.sol new file: Interchain-message/contracts/message/libraries/MsgDataTypes.sol new file: Interchain-message/contracts/test/MessageBusSender.sol new file: Interchain-message/contracts/test/TestERC20.sol new file: Interchain-message/contracts/test/TestMessages.sol new file: Interchain-message/contracts/test/WETH9.sol new file: Interchain-message/deployments/Readme.md new file: Interchain-message/executor/config/cbridge.toml new file: Interchain-message/executor/config/executor.toml new file: Interchain-message/executor/eth-ks/signer.json new file: Interchain-message/hardhat.config.ts new file: Interchain-message/package-lock.json new file: Interchain-message/package.json new file: Interchain-message/reports/contract_sizes.txt new file: Interchain-message/reports/gas_usage/summary.txt new file: Interchain-message/scripts/deploy/deploy.js new file: Interchain-message/scripts/deploy/deployAVAX.ts new file: Interchain-message/scripts/deploy/deployArbitrum.ts new file: Interchain-message/scripts/deploy/deployAurora.ts new file: Interchain-message/scripts/deploy/deployBSC.ts new file: Interchain-message/scripts/deploy/deployEth.ts new file: Interchain-message/scripts/deploy/deployFantom.ts new file: Interchain-message/scripts/deploy/deployPoly.ts new file: Interchain-message/scripts/privateKey.js new file: Interchain-message/scripts/sendTx/avaxToFantomBridge.js new file: Interchain-message/scripts/sendTx/avaxToFantomNativeV2.js new file: Interchain-message/test/RubicCrossChainBridge.spec.ts new file: Interchain-message/test/RubicCrossChainV2.spec.ts new file: Interchain-message/test/RubicCrossChainV3.spec.ts new file: Interchain-message/test/RubicFallback.spec.ts new file: Interchain-message/test/RubicSettings.spec.ts new file: Interchain-message/test/shared/consts.ts new file: Interchain-message/test/shared/fixtures.ts new file: Interchain-message/test/shared/utils.ts new file: Interchain-message/tsconfig.json deleted: Rubic-Inter-chain-Message-develop.zip deleted: proxy-instant-trades-master.zip deleted: rubic-app-master.zip deleted: rubic-sdk-master.zip
479 lines
18 KiB
TypeScript
479 lines
18 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 { calcCryptoFees, calcTokenFees } from 'rubic-bridge-base/lib';
|
||
import { expect } from 'chai';
|
||
import {
|
||
DEADLINE,
|
||
DST_CHAIN_ID,
|
||
DEFAULT_AMOUNT_IN,
|
||
VERSION_V2,
|
||
ZERO_ADDRESS,
|
||
DEFAULT_AMOUNT_OUT_MIN,
|
||
EXECUTOR_ADDRESS,
|
||
INTEGRATOR,
|
||
DEFAULT_AMOUNT_IN_USDC,
|
||
MESSAGE_BUS_FEE
|
||
} 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('RubicCrossChainBridge', () => {
|
||
let wallet: Wallet, other: Wallet;
|
||
//let swapToken: TestERC20;
|
||
let transitToken: TestERC20;
|
||
let swapMain: RubicRouterV2;
|
||
let router: string;
|
||
let wnative: WETH9;
|
||
//let chainId: number;
|
||
|
||
let testMessagesContract: TestMessages;
|
||
|
||
let loadFixture: ReturnType<typeof createFixtureLoader>;
|
||
|
||
async function callbridgeWithSwapNative({
|
||
receiver = null,
|
||
amountIn = DEFAULT_AMOUNT_IN,
|
||
dstChainID = DST_CHAIN_ID,
|
||
srcBridgeToken = wnative.address,
|
||
nativeIn = null,
|
||
integrator = ZERO_ADDRESS,
|
||
nativeOut = true
|
||
} = {}): Promise<ContractTransaction> {
|
||
const { totalCryptoFee } = await calcCryptoFees({
|
||
bridge: swapMain,
|
||
integrator,
|
||
dstChainID
|
||
});
|
||
|
||
return swapMain.bridgeWithSwapNative(
|
||
receiver === null ? wallet.address : receiver,
|
||
amountIn,
|
||
dstChainID,
|
||
srcBridgeToken,
|
||
{
|
||
dex: router,
|
||
nativeOut: nativeOut,
|
||
receiverEOA: other.address,
|
||
integrator: integrator,
|
||
version: VERSION_V2,
|
||
path: [wnative.address, transitToken.address],
|
||
pathV3: '0x',
|
||
deadline: DEADLINE,
|
||
amountOutMinimum: DEFAULT_AMOUNT_OUT_MIN
|
||
},
|
||
'10000',
|
||
{
|
||
value:
|
||
nativeIn === null ? amountIn.add(totalCryptoFee).add(MESSAGE_BUS_FEE) : nativeIn
|
||
}
|
||
);
|
||
}
|
||
|
||
async function callbridgeWithSwap({
|
||
receiver = null,
|
||
amountIn = DEFAULT_AMOUNT_IN,
|
||
dstChainID = DST_CHAIN_ID,
|
||
srcBridgeToken = transitToken.address,
|
||
nativeIn = null,
|
||
nativeOut = true,
|
||
integrator = ZERO_ADDRESS
|
||
} = {}): Promise<ContractTransaction> {
|
||
const { totalCryptoFee } = await calcCryptoFees({
|
||
bridge: swapMain,
|
||
integrator,
|
||
dstChainID
|
||
});
|
||
|
||
return swapMain.bridgeWithSwap(
|
||
receiver === null ? wallet.address : receiver,
|
||
amountIn,
|
||
dstChainID,
|
||
srcBridgeToken,
|
||
{
|
||
dex: router,
|
||
nativeOut: nativeOut,
|
||
receiverEOA: other.address,
|
||
integrator: integrator,
|
||
version: VERSION_V2,
|
||
path: [wnative.address, transitToken.address],
|
||
pathV3: '0x',
|
||
deadline: DEADLINE,
|
||
amountOutMinimum: DEFAULT_AMOUNT_OUT_MIN
|
||
},
|
||
'10000',
|
||
{ value: nativeIn === null ? totalCryptoFee.add(MESSAGE_BUS_FEE) : nativeIn }
|
||
);
|
||
}
|
||
|
||
async function getMessage(
|
||
messagesContract: TestMessages,
|
||
_nonce: BigNumberish,
|
||
dstChainId: BigNumberish,
|
||
{
|
||
dex = router,
|
||
receiverEOA = other.address,
|
||
integrator = ethers.constants.AddressZero,
|
||
version = VERSION_V2,
|
||
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 getID(
|
||
// messagesContract: TestMessages,
|
||
// _nonce: BigNumberish,
|
||
// {
|
||
// dex = router,
|
||
// integrator = ZERO_ADDRESS,
|
||
// version = VERSION,
|
||
// path = [wnative.address, transitToken.address],
|
||
// pathV3 = '0x',
|
||
// deadline = DEADLINE,
|
||
// amountOutMinimum = DEFAULT_AMOUNT_OUT_MIN,
|
||
// _receiver = wallet.address,
|
||
// nativeOut = true,
|
||
// _srcChainId = chainId,
|
||
// _dstChainId = DST_CHAIN_ID
|
||
// } = {}
|
||
// ): Promise<string> {
|
||
// return messagesContract.getID(
|
||
// _receiver,
|
||
// _srcChainId,
|
||
// _dstChainId,
|
||
// {
|
||
// dex,
|
||
// nativeOut,
|
||
// integrator,
|
||
// version,
|
||
// path,
|
||
// pathV3,
|
||
// deadline,
|
||
// amountOutMinimum
|
||
// },
|
||
// _nonce
|
||
// );
|
||
// }
|
||
|
||
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, transitToken, wnative, router, 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('#WithBridgeTests', () => {
|
||
describe('#bridgeWithSwapNative', () => {
|
||
it('Should bridge native and transfer through Celer', async () => {
|
||
await swapMain.setMaxTokenAmount(wnative.address, ethers.utils.parseEther('1000'));
|
||
//const ID = await getID(testMessagesContract, (await swapMain.nonce()).add('1'));
|
||
|
||
await expect(callbridgeWithSwapNative()).to.emit(swapMain, 'CrossChainRequestSent');
|
||
//.withArgs(ID, DST_CHAIN_ID, DEFAULT_AMOUNT_IN, wnative.address);
|
||
});
|
||
});
|
||
describe('#bridgeWithSwap', () => {
|
||
it('Should fail transfering with big amount', async () => {
|
||
await transitToken.approve(swapMain.address, ethers.constants.MaxUint256);
|
||
await swapMain.setMaxTokenAmount(
|
||
transitToken.address,
|
||
ethers.utils.parseEther('1000')
|
||
);
|
||
|
||
await expect(callbridgeWithSwap()).to.be.revertedWith('amount too large');
|
||
});
|
||
|
||
it('Should fail transfering with small amount', async () => {
|
||
await transitToken.approve(swapMain.address, ethers.constants.MaxUint256);
|
||
await swapMain.setMaxTokenAmount(
|
||
transitToken.address,
|
||
ethers.utils.parseEther('1000')
|
||
);
|
||
|
||
await expect(
|
||
callbridgeWithSwap({
|
||
amountIn: ethers.BigNumber.from('10000000')
|
||
})
|
||
).to.be.revertedWith('amount too small');
|
||
});
|
||
it('Should bridge transitToken and transfer through Сeler', async () => {
|
||
await transitToken.approve(swapMain.address, ethers.constants.MaxUint256);
|
||
await swapMain.setMaxTokenAmount(
|
||
transitToken.address,
|
||
ethers.utils.parseEther('1000')
|
||
);
|
||
|
||
//const ID = await getID(testMessagesContract, (await swapMain.nonce()).add('1'));
|
||
|
||
await expect(
|
||
callbridgeWithSwap({
|
||
amountIn: DEFAULT_AMOUNT_IN_USDC
|
||
})
|
||
).to.emit(swapMain, 'CrossChainRequestSent');
|
||
//.withArgs(ID, DST_CHAIN_ID, DEFAULT_AMOUNT_IN_USDC, transitToken.address);
|
||
});
|
||
});
|
||
describe('#executeMessageWithTransfer', () => {
|
||
beforeEach('setup for target executions', async () => {
|
||
// transfer 1000 USDC
|
||
await transitToken.transfer(swapMain.address, ethers.BigNumber.from('1000000000'));
|
||
});
|
||
describe('target bridge should emit correct event', async () => {
|
||
let nonce: BN;
|
||
let message: string;
|
||
|
||
beforeEach('setup before bridge', async () => {
|
||
nonce = (await swapMain.nonce()).add('1');
|
||
|
||
message = await getMessage(testMessagesContract, nonce, DST_CHAIN_ID, {
|
||
dex: ZERO_ADDRESS,
|
||
version: 2, // bridge version
|
||
path: [transitToken.address],
|
||
amountOutMinimum: ethers.BigNumber.from('0') // not used
|
||
});
|
||
});
|
||
it('should successfully bridge token with rubic fee', async () => {
|
||
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);
|
||
|
||
//let tokenBalanceBefore = await transitToken.balanceOf(swapMain.address);
|
||
await expect(
|
||
_swapMain.executeMessageWithTransfer(
|
||
ethers.constants.AddressZero,
|
||
transitToken.address,
|
||
ethers.BigNumber.from('1000000000'),
|
||
DST_CHAIN_ID,
|
||
message,
|
||
EXECUTOR_ADDRESS
|
||
)
|
||
).to.emit(swapMain, 'CrossChainProcessed');
|
||
let tokenBalanceAfter = await transitToken.balanceOf(swapMain.address);
|
||
// take only platform comission in transit token
|
||
const { feeAmount } = await calcTokenFees({
|
||
bridge: swapMain,
|
||
amountWithFee: ethers.BigNumber.from('1000000000')
|
||
});
|
||
await expect(Number(tokenBalanceAfter)).to.be.eq(feeAmount);
|
||
});
|
||
|
||
it('should successfully bridge native with rubic fee', async () => {
|
||
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 abiCoder = ethers.utils.defaultAbiCoder;
|
||
|
||
const storageBalancePositionWeth = ethers.utils.keccak256(
|
||
abiCoder.encode(['address'], [bus.address]) +
|
||
abiCoder.encode(['uint256'], [3]).slice(2, 66)
|
||
);
|
||
|
||
await network.provider.send('hardhat_setStorageAt', [
|
||
wnative.address,
|
||
storageBalancePositionWeth,
|
||
abiCoder.encode(['uint256'], [ethers.utils.parseEther('100000')])
|
||
]);
|
||
|
||
await wnative
|
||
.connect(bus)
|
||
.transfer(swapMain.address, ethers.utils.parseEther('1'));
|
||
|
||
const _swapMain = swapMain.connect(bus);
|
||
|
||
message = await getMessage(testMessagesContract, nonce, DST_CHAIN_ID, {
|
||
dex: ZERO_ADDRESS,
|
||
version: 2, // bridge version
|
||
path: [wnative.address],
|
||
amountOutMinimum: ethers.BigNumber.from('0') // not used
|
||
});
|
||
|
||
//let tokenBalanceBefore = await wnative.balanceOf(swapMain.address);
|
||
await expect(
|
||
_swapMain.executeMessageWithTransfer(
|
||
ethers.constants.AddressZero,
|
||
wnative.address,
|
||
ethers.utils.parseEther('1'), // 1 ether
|
||
DST_CHAIN_ID,
|
||
message,
|
||
EXECUTOR_ADDRESS
|
||
)
|
||
).to.emit(swapMain, 'CrossChainProcessed');
|
||
let tokenBalanceAfter = await wnative.balanceOf(swapMain.address);
|
||
const { feeAmount } = await calcTokenFees({
|
||
bridge: swapMain,
|
||
amountWithFee: ethers.utils.parseEther('1')
|
||
});
|
||
await expect(tokenBalanceAfter).to.be.eq(feeAmount);
|
||
});
|
||
|
||
it('should fail bridge with incorrect path', async () => {
|
||
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);
|
||
|
||
message = await getMessage(testMessagesContract, nonce, DST_CHAIN_ID, {
|
||
dex: ZERO_ADDRESS,
|
||
version: 2, // bridge version
|
||
path: [transitToken.address, wnative.address],
|
||
amountOutMinimum: ethers.BigNumber.from('0') // not used
|
||
});
|
||
|
||
await expect(
|
||
_swapMain.executeMessageWithTransfer(
|
||
ethers.constants.AddressZero,
|
||
transitToken.address,
|
||
ethers.BigNumber.from('1000000000'),
|
||
DST_CHAIN_ID,
|
||
message,
|
||
EXECUTOR_ADDRESS
|
||
)
|
||
).to.be.revertedWith('dst bridge expected');
|
||
});
|
||
|
||
describe('target bridge should take integrator & rubic fee', async () => {
|
||
beforeEach('set integrator and rubic fee', async () => {
|
||
await swapMain.setIntegratorInfo(INTEGRATOR, {
|
||
isIntegrator: true,
|
||
tokenFee: '3000',
|
||
RubicTokenShare: '400000',
|
||
RubicFixedCryptoShare: '800000',
|
||
fixedFeeAmount: ethers.utils.parseEther('2')
|
||
}); // 0.3 %
|
||
|
||
message = await getMessage(testMessagesContract, nonce, DST_CHAIN_ID, {
|
||
dex: ZERO_ADDRESS,
|
||
integrator: INTEGRATOR,
|
||
version: 2, // bridge version
|
||
path: [transitToken.address],
|
||
amountOutMinimum: ethers.BigNumber.from('0') // not used
|
||
});
|
||
});
|
||
|
||
it('should successfully bridge transitToken with rubic & integrator fee', async () => {
|
||
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);
|
||
|
||
//let tokenBalanceBefore = await transitToken.balanceOf(swapMain.address);
|
||
await expect(
|
||
_swapMain.executeMessageWithTransfer(
|
||
ethers.constants.AddressZero,
|
||
transitToken.address,
|
||
ethers.BigNumber.from('1000000000'),
|
||
DST_CHAIN_ID,
|
||
message,
|
||
EXECUTOR_ADDRESS
|
||
)
|
||
).to.emit(swapMain, 'CrossChainProcessed');
|
||
|
||
const tokenBalanceAfter = await transitToken.balanceOf(swapMain.address);
|
||
|
||
const collectedFee1 = await swapMain.availableRubicTokenFee(
|
||
transitToken.address
|
||
);
|
||
|
||
const integratorCollectedFee1 = await swapMain.availableIntegratorTokenFee(
|
||
transitToken.address,
|
||
INTEGRATOR
|
||
);
|
||
|
||
const { integratorFee, RubicFee, feeAmount } = await calcTokenFees({
|
||
bridge: swapMain,
|
||
amountWithFee: ethers.BigNumber.from('1000000000'),
|
||
integrator: INTEGRATOR
|
||
});
|
||
|
||
await expect(integratorCollectedFee1).to.be.eq(integratorFee);
|
||
|
||
// take platform comission in transit token
|
||
await expect(collectedFee1).to.be.eq(RubicFee);
|
||
|
||
await expect(tokenBalanceAfter).to.be.eq(feeAmount);
|
||
});
|
||
});
|
||
});
|
||
});
|
||
});
|
||
});
|