diff --git a/hardhat.config.ts b/hardhat.config.ts index 21041a3..4aab922 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -10,7 +10,7 @@ const TEST_ACCOUNTS = { path: 'm/44\'/60\'/0\'/0', }; -const PROD_ACCOUNTS = process.env.KEY ? [process.env.KEY] : []; +const PROD_ACCOUNTS = process.env.KEY ? process.env.KEY.split(',') : []; const config: HardhatUserConfig = { solidity: '0.8.18', @@ -26,8 +26,8 @@ const config: HardhatUserConfig = { chainId: 596, }, acalaFork: { - url: 'http://127.0.0.1:8545', - accounts: TEST_ACCOUNTS, + url: 'https://crosschain-dev.polkawallet.io/forkAcala/', + accounts: PROD_ACCOUNTS, chainId: 787, }, karura: { diff --git a/package.json b/package.json index 648240e..8fd9475 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@acala-network/asset-router", - "version": "1.0.17", + "version": "1.0.18-1", "main": "dist/index.js", "repository": "git@github.com:AcalaNetwork/asset-router.git", "author": "Acala Developers ", diff --git a/scripts/consts.ts b/scripts/consts.ts index eef8c86..2dfb170 100644 --- a/scripts/consts.ts +++ b/scripts/consts.ts @@ -9,6 +9,7 @@ export const enum CHAIN { BSC = 'BSC', ARB = 'ARB', POLYGON = 'POLYGON', + SOLANA = 'SOLANA', } export const enum TOKEN { @@ -35,6 +36,7 @@ export const enum TOKEN { TDOT = 'tdot', LCDOT = 'lcdot', KSM = 'ksm', + JITOSOL = 'jitosol', } export type CHAIN_NAME = keyof typeof CHAIN; @@ -51,11 +53,12 @@ export const ADDRESSES = { [CHAIN.ACALA_TESTNET]: { tokenBridgeAddr: CONTRACTS.TESTNET.acala.token_bridge, factoryAddr: '', - feeAddr: '0xfFAD744806522F9F9f3bef1F09Ec5421Bfc0874e', // acala fork + feeAddr: '0x346a989D3B55a82b3CD8a5ed804E3776Af90925a', // acala fork usdcAddr: '0x7E0CCD4209Ef7039901512fF9f6a01d0de0691e2', homaFactoryAddr: '0x2ed9aa0e30D52958E21Db37FfBDC3F0B0fD4b973', // acala fork accountHelperAddr: '0x0252340cC347718f9169d329CEFf8B15A92badf8', // acala fork euphratesFactoryAddr: '0x2AeFc65B6E1660d2bA2796f8698120A2acB95634', // acala fork + swapAndStakeFactoryAddr: '0x97B15411D65e83F0bDA8D1db628CCC5D003B754d', // acala fork }, [CHAIN.KARURA]: { tokenBridgeAddr: CONTRACTS.MAINNET.karura.token_bridge, @@ -73,6 +76,7 @@ export const ADDRESSES = { homaFactoryAddr: '0x2ed9aa0e30D52958E21Db37FfBDC3F0B0fD4b973', accountHelperAddr: '0x0252340cC347718f9169d329CEFf8B15A92badf8', euphratesFactoryAddr: '0x2AeFc65B6E1660d2bA2796f8698120A2acB95634', + swapAndStakeFactoryAddr: '', }, } as const; @@ -261,6 +265,14 @@ export const ROUTER_TOKEN_INFO = { decimals: 12, fee: 0.001, }, + [TOKEN.JITOSOL]: { + originChain: CHAIN.SOLANA, + originAddr: '', + karuraAddr: '', + acalaAddr: '0xa7fb00459f5896c3bd4df97870b44e868ae663d7', + decimals: 9, + fee: 0.0003, + }, } as const; export const CHAIN_NAME_TO_WORMHOLE_CHAIN_ID = { diff --git a/scripts/deploy-fee.ts b/scripts/deploy-fee.ts index 5a93907..d8c96f6 100644 --- a/scripts/deploy-fee.ts +++ b/scripts/deploy-fee.ts @@ -5,7 +5,7 @@ import { FeeStruct } from '../typechain-types/src/FeeRegistry'; import { ROUTER_TOKEN_INFO } from './consts'; async function main() { - const isAcala = network.name === 'acala'; + const isAcala = network.name.toLowerCase().includes('acala'); const feeConfig = Object.entries(ROUTER_TOKEN_INFO) .filter(([, info]) => isAcala ? !!info.acalaAddr : !!info.karuraAddr) .map(([, info]) => ({ diff --git a/test/swapAndStakeRouter.test.ts b/test/swapAndStakeRouter.test.ts new file mode 100644 index 0000000..ee38671 --- /dev/null +++ b/test/swapAndStakeRouter.test.ts @@ -0,0 +1,154 @@ +import { ACA } from '@acala-network/contracts/utils/AcalaTokens'; +import { BigNumber, constants } from 'ethers'; +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; +import { ethers } from 'hardhat'; +import { expect } from 'chai'; +import { formatUnits, parseEther, parseUnits } from 'ethers/lib/utils'; + +import { ADDRESSES } from '../scripts/consts'; +import { FeeRegistry, MockToken, SwapAndStakeEuphratesFactory } from '../typechain-types'; +import { ONE_ACA, almostEq, toHuman } from '../scripts/utils'; + +const { swapAndStakeFactoryAddr, feeAddr } = ADDRESSES.ACALA_TESTNET; + +describe('Stake and Swap Router', () => { + // fixed + let jitosol: MockToken; + let aca: MockToken; + let fee: FeeRegistry; + let factory: SwapAndStakeEuphratesFactory; + let decimals: number; + let routingFee: BigNumber; + let stakeAndSupplyAmount: BigNumber; + let user: SignerWithAddress; + let relayer: SignerWithAddress; + + // dynamic + let routerAddr: string; + let bal0: Awaited>; + let bal1: Awaited>; + + const fetchTokenBalances = async () => { + const [ + userBal, + relayerBal, + userBalJitoSol, + relayerBalJitoSol, + routerBalJitoSol, + ] = await Promise.all([ + user.getBalance(), + relayer.getBalance(), + jitosol.balanceOf(user.address), + jitosol.balanceOf(relayer.address), + jitosol.balanceOf(routerAddr), + ]); + + console.log({ + userBal: toHuman(userBal, 18), + relayerBal: toHuman(relayerBal, 18), + userBalJitoSol: toHuman(userBalJitoSol, decimals), + relayerBalJitoSol: toHuman(relayerBalJitoSol, decimals), + routerBalJitoSol: toHuman(routerBalJitoSol, decimals), + }); + + return { + userBal, + relayerBal, + userBalJitoSol, + relayerBalJitoSol, + routerBalJitoSol, + }; + }; + + before('setup', async () => { + ([user, relayer] = await ethers.getSigners()); + + const Token = await ethers.getContractFactory('MockToken'); + const Fee = await ethers.getContractFactory('FeeRegistry'); + const Factory = await ethers.getContractFactory('SwapAndStakeEuphratesFactory'); + + jitosol = Token.attach('0xa7fb00459f5896c3bd4df97870b44e868ae663d7'); + aca = Token.attach(ACA); + fee = Fee.attach(feeAddr); + factory = Factory.attach(swapAndStakeFactoryAddr).connect(relayer); + decimals = await jitosol.decimals(); + routingFee = await fee.getFee(jitosol.address); + stakeAndSupplyAmount = parseUnits('1', decimals); + + console.log(`jitosol address: ${jitosol.address}`); + console.log(`feeRegistry address: ${fee.address}`); + console.log(`factory address: ${factory.address}`); + console.log(`user address: ${user.address}`); + console.log(`relayer address: ${relayer.address}`); + console.log(`token decimals: ${decimals}`); + console.log(`router fee: ${Number(formatUnits(routingFee, decimals))}`); + }); + + describe('swap and route', async () => { + it('works', async () => { + const supplyAmount = parseUnits('0.1', decimals); + const targetAmount = parseUnits('2', 12); + const targetAmountNative = parseEther('2'); + const insts = { + recipient: user.address, + supplyAmount, + maker: relayer.address, + targetToken: ACA, + poolId: 7, + euphrates: '0x7Fe92EC600F15cD25253b421bc151c51b0276b7D', + }; + + // console.log('approving ...'); + // const approveTx = await aca.connect(relayer).approve(swapAndStakeFactoryAddr, constants.MaxUint256); + // await approveTx.wait(); + // console.log('approved'); + + routerAddr = await factory.callStatic.deploySwapAndStakeEuphratesRouter(fee.address, insts, targetAmount); + + console.log({ predictedRouterAddr: routerAddr }); + + console.log('\n-------------------- init state --------------------'); + bal0 = await fetchTokenBalances(); + expect(bal0.userBalJitoSol).to.gte(stakeAndSupplyAmount); + + // router shouldn't exist + let routerCode = await relayer.provider!.getCode(routerAddr); + expect(routerCode).to.eq('0x'); + + await (await jitosol.connect(user).transfer( + routerAddr, + stakeAndSupplyAmount, + )).wait(); + + console.log('\n-------------------- after user deposited to router --------------------'); + await fetchTokenBalances(); + + + const deployAndRoute = await factory.deploySwapAndStakeEuphratesRouterAndRoute( + fee.address, + insts, + jitosol.address, + targetAmount, + ); + await deployAndRoute.wait(); + + console.log('\n-------------------- after router routed and staked --------------------'); + bal1 = await fetchTokenBalances(); + + // router should have no remaining balance + expect(bal1.routerBalJitoSol).to.eq(0); + + // user should receive LDOT and swapped target token + expect(bal0.userBalJitoSol.sub(bal1.userBalJitoSol)).to.eq(stakeAndSupplyAmount); + almostEq(bal1.userBal.sub(bal0.userBal), targetAmountNative); + + // relayer should receive fee + expect(bal1.relayerBalJitoSol.sub(bal0.relayerBalJitoSol)).to.eq(routingFee.add(supplyAmount)); + + // router should be destroyed + routerCode = await relayer.provider!.getCode(routerAddr); + expect(routerCode).to.eq('0x'); + }); + }); + +});