Skip to content

Commit 419f1c7

Browse files
add DropAndBootstrapStakeRouter (#61)
* add DropAndBootstrapStakeRouter * not destruct router when bootstraping (#62) * charge drop only once for BootstrapRouter * use create2 lib for calculating router addr (#63) * not destruct router when bootstraping * bootstrap gas issue reproduce * use create2 lib for calculating router addr * cleanup * Revert "cleanup" This reverts commit 376befc. * cleanup * update * bump * fix bootstrap rescue (#64) * fix bootstrap rescue * update const * use generic rescue (#65) * fix bootstrap rescue * update const * use general resuce * use single token param for rescue * fix * only transfer ldot when more than ed (#66) --------- Co-authored-by: Shunji Zhan <[email protected]>
1 parent f4f13af commit 419f1c7

11 files changed

+1102
-12
lines changed

hardhat.config.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,12 @@ const config: HardhatUserConfig = {
3131
chainId: 597,
3232
},
3333
acalaFork: {
34-
url: 'https://crosschain-dev.polkawallet.io/forkAcala/',
34+
url: 'https://crosschain-dev.polkawallet.io/chopsticksAcalaRpc',
35+
accounts: MY_ACCOUNTS,
36+
chainId: 787,
37+
},
38+
local: {
39+
url: 'http://127.0.0.1:8545',
3540
accounts: MY_ACCOUNTS,
3641
chainId: 787,
3742
},

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@acala-network/asset-router",
3-
"version": "1.0.19-6",
3+
"version": "1.0.19-17",
44
"main": "dist/index.js",
55
"repository": "[email protected]:AcalaNetwork/asset-router.git",
66
"author": "Acala Developers <[email protected]>",
@@ -26,12 +26,14 @@
2626
"ethers": "^5.7.0"
2727
},
2828
"devDependencies": {
29+
"@acala-network/types": "^6.1.3",
2930
"@nomicfoundation/hardhat-chai-matchers": "^1.0.0",
3031
"@nomicfoundation/hardhat-foundry": "^1.0.0",
3132
"@nomicfoundation/hardhat-network-helpers": "^1.0.0",
3233
"@nomicfoundation/hardhat-toolbox": "^2.0.0",
3334
"@nomiclabs/hardhat-ethers": "^2.0.0",
3435
"@nomiclabs/hardhat-etherscan": "^3.0.0",
36+
"@polkadot/api": "^14.0.1",
3537
"@typechain/ethers-v5": "^10.1.0",
3638
"@typechain/hardhat": "^6.1.2",
3739
"@types/chai": "^4.2.0",

scripts/consts.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ export const ADDRESSES = {
5959
accountHelperAddr: '0x0252340cC347718f9169d329CEFf8B15A92badf8', // acala fork
6060
euphratesFactoryAddr: '0x2AeFc65B6E1660d2bA2796f8698120A2acB95634', // acala fork
6161
swapAndStakeFactoryAddr: '0x97B15411D65e83F0bDA8D1db628CCC5D003B754d', // acala fork
62-
dropAndSwapStakeFactoryAddr: '0xB1DC04892c8346f61aF1A922A856D96e4A51a389', // acala fork
62+
dropAndSwapStakeFactoryAddr: '0x0F2A7a306D26697876689df9bdc5274982563505', // acala fork
63+
dropAndBootstrapStakeFactoryAddr: '0x1C7426D333b18Dbc050fc306E48DaA9a4A926b9b', // acala fork
6364
},
6465
[CHAIN.KARURA]: {
6566
tokenBridgeAddr: CONTRACTS.MAINNET.karura.token_bridge,
@@ -78,7 +79,8 @@ export const ADDRESSES = {
7879
accountHelperAddr: '0x0252340cC347718f9169d329CEFf8B15A92badf8',
7980
euphratesFactoryAddr: '0x2AeFc65B6E1660d2bA2796f8698120A2acB95634',
8081
swapAndStakeFactoryAddr: '0x3923E44cf1062FBa513279Ab81e6B8727a6de3D6',
81-
dropAndSwapStakeFactoryAddr: '0xB1DC04892c8346f61aF1A922A856D96e4A51a389',
82+
dropAndSwapStakeFactoryAddr: '0x0F2A7a306D26697876689df9bdc5274982563505', // acala fork
83+
dropAndBootstrapStakeFactoryAddr: '0x1C7426D333b18Dbc050fc306E48DaA9a4A926b9b', // acala fork
8284
},
8385
} as const;
8486

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { ethers, run } from 'hardhat';
2+
3+
async function main() {
4+
const Factory = await ethers.getContractFactory('DropAndBootstrapStakeFactory');
5+
const factory = await Factory.deploy();
6+
await factory.deployed();
7+
8+
console.log(`drop and bootstrap factory address: ${factory.address}`);
9+
10+
if (process.env.VERIFY) {
11+
await run('verify:verify', {
12+
address: factory.address,
13+
constructorArguments: [],
14+
});
15+
}
16+
}
17+
18+
main().catch((error) => {
19+
console.error(error);
20+
process.exitCode = 1;
21+
});
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import '@acala-network/types';
2+
3+
import { ACA, LDOT } from '@acala-network/contracts/utils/AcalaTokens';
4+
import { ApiPromise, WsProvider } from '@polkadot/api';
5+
import { DEX } from '@acala-network/contracts/utils/Predeploy';
6+
import { TransactionReceipt } from '@ethersproject/providers';
7+
import { Wallet, constants } from 'ethers';
8+
import { ethers } from 'hardhat';
9+
import { parseUnits } from 'ethers/lib/utils';
10+
11+
import { ADDRESSES, CHAIN, ROUTER_TOKEN_INFO } from './consts';
12+
import { DropAndBootstrapStakeFactory__factory, DropAndBootstrapStakeRouter__factory, ERC20__factory } from '../typechain-types';
13+
14+
const FACTORY_ADDR = '0x05598F8A9a74f6BDc31F9Da93B051114c0a41923';
15+
const EUPHRATES_ADDR = '0x7Fe92EC600F15cD25253b421bc151c51b0276b7D';
16+
const JITOSOL_ADDR = ROUTER_TOKEN_INFO.jitosol.acalaAddr;
17+
const FEE_ADDR = ADDRESSES[CHAIN.ACALA].feeAddr;
18+
19+
const NODE_URL = 'wss://crosschain-dev.polkawallet.io/chopsticksAcala';
20+
21+
(async () => {
22+
const api = await ApiPromise.create({
23+
provider: new WsProvider(NODE_URL),
24+
});
25+
26+
const [wallet] = await ethers.getSigners();
27+
const aca = ERC20__factory.connect(ACA, wallet);
28+
const jitosol = ERC20__factory.connect(JITOSOL_ADDR, wallet);
29+
const factory = DropAndBootstrapStakeFactory__factory.connect(FACTORY_ADDR, wallet);
30+
31+
// prepare the route instructions
32+
const recipient = Wallet.createRandom().address;
33+
const dropAmount = parseUnits(process.env.GAS_DROP ?? '0', 12);
34+
35+
const insts = {
36+
euphrates: EUPHRATES_ADDR,
37+
dex: DEX,
38+
dropToken: ACA,
39+
otherContributionToken: LDOT,
40+
poolId: 7,
41+
recipient,
42+
dropFee: 0,
43+
feeReceiver: wallet.address,
44+
};
45+
46+
if (dropAmount.gt(0)) {
47+
console.log('approving ACA to fatory ...');
48+
await (await aca.approve(FACTORY_ADDR, constants.MaxUint256)).wait();
49+
}
50+
51+
const routerAddr = await factory.callStatic.deployDropAndBootstrapStakeRouter(
52+
FEE_ADDR,
53+
insts,
54+
dropAmount,
55+
);
56+
57+
console.log({ routerAddr });
58+
59+
for (let i = 0; i < 3; i++) {
60+
console.log('');
61+
console.log(`-------------------- RUN ${i} --------------------`);
62+
console.log('transfering jitosol to router ...');
63+
const transferAmount = parseUnits('0.1', 9);
64+
await (await jitosol.transfer(routerAddr, transferAmount)).wait();
65+
66+
const _routerAddr = await factory.callStatic.deployDropAndBootstrapStakeRouter(
67+
FEE_ADDR,
68+
insts,
69+
dropAmount,
70+
);
71+
72+
if (_routerAddr !== routerAddr) {
73+
throw new Error('router address mismatch');
74+
} else {
75+
console.log('router address match');
76+
}
77+
78+
const receipt = await (await factory.deployDropAndBootstrapStakeRouterAndRoute(
79+
FEE_ADDR,
80+
insts,
81+
JITOSOL_ADDR,
82+
dropAmount,
83+
)).wait();
84+
85+
console.log('querying tx info ...');
86+
const blockHash = receipt.blockHash;
87+
const apiAt = await api.at(blockHash);
88+
const events = await apiAt.query.system.events();
89+
const evmEvents = events.filter(e => e.event.section.toLocaleLowerCase() === 'evm');
90+
91+
for (const evmEvent of evmEvents) {
92+
console.log(evmEvent.event.toHuman());
93+
}
94+
}
95+
96+
await api.disconnect();
97+
})();

src/BaseRouter.sol

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,23 +21,31 @@ abstract contract BaseRouter {
2121

2222
function routeImpl(ERC20 token) internal virtual;
2323

24-
function routeNoFee(ERC20 token) public {
24+
function routeNoFee(ERC20 token, bool shouldDestruct) public {
2525
routeImpl(token);
2626

27-
// selfdestruct only if balance is zero to make sure this cannot be used to steal native tokens
28-
if (address(this).balance == 0) {
27+
// selfdestruct only if balance is zero and shouldDestruct is true
28+
if (address(this).balance == 0 && shouldDestruct) {
2929
emit RouterDestroyed(address(this));
3030
selfdestruct(payable(msg.sender));
3131
}
3232
}
3333

34-
function route(ERC20 token, address relayer) public {
34+
function routeNoFee(ERC20 token) public {
35+
routeNoFee(token, true);
36+
}
37+
38+
function route(ERC20 token, address relayer, bool shouldDestruct) public {
3539
uint256 fee = fees.getFee(address(token));
3640

3741
// should use routeNoFee if relayer is not expecting a fee
3842
require(fee > 0, "zero fee");
3943

4044
token.safeTransfer(relayer, fee);
41-
routeNoFee(token);
45+
routeNoFee(token, shouldDestruct);
46+
}
47+
48+
function route(ERC20 token, address relayer) public {
49+
route(token, relayer, true);
4250
}
4351
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.17;
3+
4+
import { ERC20 } from "solmate/tokens/ERC20.sol";
5+
import { Create2 } from "@openzeppelin/contracts/utils/Create2.sol";
6+
import { SafeTransferLib } from "solmate/utils/SafeTransferLib.sol";
7+
import { FeeRegistry } from "./FeeRegistry.sol";
8+
import { DropAndBootstrapStakeRouter, DropAndBootstrapStakeInstructions } from "./DropAndBootstrapStakeRouter.sol";
9+
10+
contract DropAndBootstrapStakeFactory {
11+
using SafeTransferLib for ERC20;
12+
13+
function deployDropAndBootstrapStakeRouter(
14+
FeeRegistry fees,
15+
DropAndBootstrapStakeInstructions memory inst,
16+
uint256 dropAmount
17+
) public returns (DropAndBootstrapStakeRouter) {
18+
require(
19+
inst.recipient != address(0) && inst.feeReceiver != address(0) && address(inst.dropToken) != address(0)
20+
&& inst.euphrates != address(0) && address(inst.otherContributionToken) != address(0),
21+
"invalid inst"
22+
);
23+
24+
// no need to use salt as we want to keep the router address the same for the same fees &instructions
25+
bytes32 salt;
26+
bytes memory bytecode = abi.encodePacked(type(DropAndBootstrapStakeRouter).creationCode, abi.encode(fees, inst));
27+
address routerAddr = Create2.computeAddress(salt, keccak256(bytecode));
28+
29+
if (routerAddr.code.length == 0) {
30+
routerAddr = Create2.deploy(0, salt, bytecode);
31+
32+
if (dropAmount > 0) {
33+
inst.dropToken.safeTransferFrom(msg.sender, routerAddr, dropAmount);
34+
}
35+
}
36+
37+
return DropAndBootstrapStakeRouter(routerAddr);
38+
}
39+
40+
function deployDropAndBootstrapStakeRouterAndRoute(
41+
FeeRegistry fees,
42+
DropAndBootstrapStakeInstructions memory inst,
43+
ERC20 token,
44+
uint256 dropAmount
45+
) public {
46+
DropAndBootstrapStakeRouter router = deployDropAndBootstrapStakeRouter(fees, inst, dropAmount);
47+
router.route(token, msg.sender, false);
48+
}
49+
50+
function deployDropAndBootstrapStakeRouterAndRouteNoFee(
51+
FeeRegistry fees,
52+
DropAndBootstrapStakeInstructions memory inst,
53+
ERC20 token,
54+
uint256 dropAmount
55+
) public {
56+
DropAndBootstrapStakeRouter router = deployDropAndBootstrapStakeRouter(fees, inst, dropAmount);
57+
router.routeNoFee(token);
58+
}
59+
60+
function deployDropAndBootstrapStakeRouterAndRescue(
61+
FeeRegistry fees,
62+
DropAndBootstrapStakeInstructions memory inst,
63+
uint256 dropAmount,
64+
ERC20 token
65+
) public {
66+
DropAndBootstrapStakeRouter router = deployDropAndBootstrapStakeRouter(fees, inst, dropAmount);
67+
router.rescue(token);
68+
}
69+
}

0 commit comments

Comments
 (0)