|
| 1 | +import { amountToLp, mockTreeRoot, refundProposalLiveness, bondAmount } from "../constants"; |
| 2 | +import { |
| 3 | + ethers, |
| 4 | + expect, |
| 5 | + Contract, |
| 6 | + FakeContract, |
| 7 | + SignerWithAddress, |
| 8 | + getContractFactory, |
| 9 | + seedWallet, |
| 10 | + randomAddress, |
| 11 | + toWei, |
| 12 | + BigNumber, |
| 13 | +} from "../../utils/utils"; |
| 14 | +import { hubPoolFixture, enableTokensForLP } from "../fixtures/HubPool.Fixture"; |
| 15 | +import { constructSingleChainTree } from "../MerkleLib.utils"; |
| 16 | +import { smock } from "@defi-wonderland/smock"; |
| 17 | + |
| 18 | +let hubPool: Contract, |
| 19 | + lineaAdapter: Contract, |
| 20 | + weth: Contract, |
| 21 | + dai: Contract, |
| 22 | + usdc: Contract, |
| 23 | + timer: Contract, |
| 24 | + mockSpoke: Contract; |
| 25 | +let l2Weth: string, l2Dai: string, l2Usdc: string; |
| 26 | +let owner: SignerWithAddress, dataWorker: SignerWithAddress, liquidityProvider: SignerWithAddress; |
| 27 | +let lineaMessageService: FakeContract, lineaTokenBridge: FakeContract, lineaUsdcBridge: FakeContract; |
| 28 | + |
| 29 | +const lineaChainId = 59144; |
| 30 | + |
| 31 | +const lineaMessageServiceAbi = [ |
| 32 | + { |
| 33 | + inputs: [ |
| 34 | + { internalType: "address", name: "_to", type: "address" }, |
| 35 | + { internalType: "uint256", name: "_fee", type: "uint256" }, |
| 36 | + { internalType: "bytes", name: "_calldata", type: "bytes" }, |
| 37 | + ], |
| 38 | + name: "sendMessage", |
| 39 | + outputs: [], |
| 40 | + stateMutability: "payable", |
| 41 | + type: "function", |
| 42 | + }, |
| 43 | +]; |
| 44 | + |
| 45 | +const lineaTokenBridgeAbi = [ |
| 46 | + { |
| 47 | + inputs: [ |
| 48 | + { internalType: "address", name: "_token", type: "address" }, |
| 49 | + { internalType: "uint256", name: "_amount", type: "uint256" }, |
| 50 | + { internalType: "address", name: "_recipient", type: "address" }, |
| 51 | + ], |
| 52 | + name: "bridgeToken", |
| 53 | + outputs: [], |
| 54 | + stateMutability: "payable", |
| 55 | + type: "function", |
| 56 | + }, |
| 57 | +]; |
| 58 | + |
| 59 | +const lineaUsdcBridgeAbi = [ |
| 60 | + { |
| 61 | + inputs: [ |
| 62 | + { internalType: "uint256", name: "amount", type: "uint256" }, |
| 63 | + { internalType: "address", name: "to", type: "address" }, |
| 64 | + ], |
| 65 | + name: "depositTo", |
| 66 | + outputs: [], |
| 67 | + stateMutability: "payable", |
| 68 | + type: "function", |
| 69 | + }, |
| 70 | + { |
| 71 | + inputs: [], |
| 72 | + name: "usdc", |
| 73 | + outputs: [ |
| 74 | + { |
| 75 | + name: "", |
| 76 | + type: "address", |
| 77 | + }, |
| 78 | + ], |
| 79 | + stateMutability: "view", |
| 80 | + type: "function", |
| 81 | + }, |
| 82 | +]; |
| 83 | + |
| 84 | +describe("Linea Chain Adapter", function () { |
| 85 | + beforeEach(async function () { |
| 86 | + [owner, dataWorker, liquidityProvider] = await ethers.getSigners(); |
| 87 | + ({ weth, dai, usdc, l2Weth, l2Dai, l2Usdc, hubPool, mockSpoke, timer } = await hubPoolFixture()); |
| 88 | + await seedWallet(dataWorker, [dai, usdc], weth, amountToLp); |
| 89 | + await seedWallet(liquidityProvider, [dai, usdc], weth, amountToLp.mul(10)); |
| 90 | + |
| 91 | + await enableTokensForLP(owner, hubPool, weth, [weth, dai, usdc]); |
| 92 | + await weth.connect(liquidityProvider).approve(hubPool.address, amountToLp); |
| 93 | + await hubPool.connect(liquidityProvider).addLiquidity(weth.address, amountToLp); |
| 94 | + await weth.connect(dataWorker).approve(hubPool.address, bondAmount.mul(10)); |
| 95 | + await dai.connect(liquidityProvider).approve(hubPool.address, amountToLp); |
| 96 | + await hubPool.connect(liquidityProvider).addLiquidity(dai.address, amountToLp); |
| 97 | + await dai.connect(dataWorker).approve(hubPool.address, bondAmount.mul(10)); |
| 98 | + await usdc.connect(liquidityProvider).approve(hubPool.address, amountToLp); |
| 99 | + await hubPool.connect(liquidityProvider).addLiquidity(usdc.address, amountToLp); |
| 100 | + await usdc.connect(dataWorker).approve(hubPool.address, bondAmount.mul(10)); |
| 101 | + |
| 102 | + lineaMessageService = await smock.fake(lineaMessageServiceAbi, { |
| 103 | + address: "0xd19d4B5d358258f05D7B411E21A1460D11B0876F", |
| 104 | + }); |
| 105 | + lineaTokenBridge = await smock.fake(lineaTokenBridgeAbi, { address: "0x051F1D88f0aF5763fB888eC4378b4D8B29ea3319" }); |
| 106 | + lineaUsdcBridge = await smock.fake(lineaUsdcBridgeAbi, { |
| 107 | + address: "0x504a330327a089d8364c4ab3811ee26976d388ce", |
| 108 | + }); |
| 109 | + lineaUsdcBridge.usdc.returns(usdc.address); |
| 110 | + |
| 111 | + lineaAdapter = await ( |
| 112 | + await getContractFactory("Linea_Adapter", owner) |
| 113 | + ).deploy(weth.address, lineaMessageService.address, lineaTokenBridge.address, lineaUsdcBridge.address); |
| 114 | + |
| 115 | + // Seed the HubPool some funds so it can send L1->L2 messages. |
| 116 | + await hubPool.connect(liquidityProvider).loadEthForL2Calls({ value: toWei("100000") }); |
| 117 | + |
| 118 | + await hubPool.setCrossChainContracts(lineaChainId, lineaAdapter.address, mockSpoke.address); |
| 119 | + await hubPool.setPoolRebalanceRoute(lineaChainId, weth.address, l2Weth); |
| 120 | + await hubPool.setPoolRebalanceRoute(lineaChainId, dai.address, l2Dai); |
| 121 | + await hubPool.setPoolRebalanceRoute(lineaChainId, usdc.address, l2Usdc); |
| 122 | + }); |
| 123 | + |
| 124 | + it("relayMessage calls spoke pool functions", async function () { |
| 125 | + const newAdmin = randomAddress(); |
| 126 | + const functionCallData = mockSpoke.interface.encodeFunctionData("setCrossDomainAdmin", [newAdmin]); |
| 127 | + expect(await hubPool.relaySpokePoolAdminFunction(lineaChainId, functionCallData)) |
| 128 | + .to.emit(lineaAdapter.attach(hubPool.address), "MessageRelayed") |
| 129 | + .withArgs(mockSpoke.address, functionCallData); |
| 130 | + expect(lineaMessageService.sendMessage).to.have.been.calledWith(mockSpoke.address, 0, functionCallData); |
| 131 | + expect(lineaMessageService.sendMessage).to.have.been.calledWithValue(BigNumber.from(0)); |
| 132 | + }); |
| 133 | + it("Correctly calls appropriate bridge functions when making ERC20 cross chain calls", async function () { |
| 134 | + // Create an action that will send an L1->L2 tokens transfer and bundle. For this, create a relayer repayment bundle |
| 135 | + // and check that at it's finalization the L2 bridge contracts are called as expected. |
| 136 | + const { leaves, tree, tokensSendToL2 } = await constructSingleChainTree(dai.address, 1, lineaChainId); |
| 137 | + await hubPool.connect(dataWorker).proposeRootBundle([3117], 1, tree.getHexRoot(), mockTreeRoot, mockTreeRoot); |
| 138 | + await timer.setCurrentTime(Number(await timer.getCurrentTime()) + refundProposalLiveness + 1); |
| 139 | + await hubPool.connect(dataWorker).executeRootBundle(...Object.values(leaves[0]), tree.getHexProof(leaves[0])); |
| 140 | + |
| 141 | + // The correct functions should have been called on the optimism contracts. |
| 142 | + const expectedErc20L1ToL2BridgeParams = [dai.address, tokensSendToL2, mockSpoke.address]; |
| 143 | + expect(lineaTokenBridge.bridgeToken).to.have.been.calledWith(...expectedErc20L1ToL2BridgeParams); |
| 144 | + }); |
| 145 | + it("Correctly calls appropriate bridge functions when making USDC cross chain calls", async function () { |
| 146 | + // Create an action that will send an L1->L2 tokens transfer and bundle. For this, create a relayer repayment bundle |
| 147 | + // and check that at it's finalization the L2 bridge contracts are called as expected. |
| 148 | + const { leaves, tree, tokensSendToL2 } = await constructSingleChainTree(usdc.address, 1, lineaChainId); |
| 149 | + await hubPool.connect(dataWorker).proposeRootBundle([3117], 1, tree.getHexRoot(), mockTreeRoot, mockTreeRoot); |
| 150 | + await timer.setCurrentTime(Number(await timer.getCurrentTime()) + refundProposalLiveness + 1); |
| 151 | + await hubPool.connect(dataWorker).executeRootBundle(...Object.values(leaves[0]), tree.getHexProof(leaves[0])); |
| 152 | + |
| 153 | + // The correct functions should have been called on the optimism contracts. |
| 154 | + const expectedErc20L1ToL2BridgeParams = [tokensSendToL2, mockSpoke.address]; |
| 155 | + expect(lineaUsdcBridge.depositTo).to.have.been.calledWith(...expectedErc20L1ToL2BridgeParams); |
| 156 | + }); |
| 157 | + it("Correctly unwraps WETH and bridges ETH", async function () { |
| 158 | + const { leaves, tree } = await constructSingleChainTree(weth.address, 1, lineaChainId); |
| 159 | + |
| 160 | + await hubPool.connect(dataWorker).proposeRootBundle([3117], 1, tree.getHexRoot(), mockTreeRoot, mockTreeRoot); |
| 161 | + await timer.setCurrentTime(Number(await timer.getCurrentTime()) + refundProposalLiveness + 1); |
| 162 | + |
| 163 | + // Since WETH is used as proposal bond, the bond plus the WETH are debited from the HubPool's balance. |
| 164 | + // The WETH used in the Linea_Adapter is withdrawn to ETH and then paid to the Linea MessageService. |
| 165 | + const proposalBond = await hubPool.bondAmount(); |
| 166 | + await expect(() => |
| 167 | + hubPool.connect(dataWorker).executeRootBundle(...Object.values(leaves[0]), tree.getHexProof(leaves[0])) |
| 168 | + ).to.changeTokenBalance(weth, hubPool, leaves[0].netSendAmounts[0].add(proposalBond).mul(-1)); |
| 169 | + expect(lineaMessageService.sendMessage).to.have.been.calledWith(mockSpoke.address, 0, "0x"); |
| 170 | + expect(lineaMessageService.sendMessage).to.have.been.calledWithValue(leaves[0].netSendAmounts[0]); |
| 171 | + }); |
| 172 | +}); |
0 commit comments