diff --git a/packages/protocol/contracts/bridge/Bridge.sol b/packages/protocol/contracts/bridge/Bridge.sol index 4bd39766495..215f257f272 100644 --- a/packages/protocol/contracts/bridge/Bridge.sol +++ b/packages/protocol/contracts/bridge/Bridge.sol @@ -15,6 +15,7 @@ import "./libs/LibBridgeProcess.sol"; import "./libs/LibBridgeRetry.sol"; import "./libs/LibBridgeSend.sol"; import "./libs/LibBridgeSignal.sol"; +import "./libs/LibBridgeStatus.sol"; /** * Bridge contract which is deployed on both L1 and L2. Mostly a thin wrapper @@ -39,7 +40,7 @@ contract Bridge is EssentialContract, IBridge { event MessageStatusChanged( bytes32 indexed signal, - LibBridgeData.MessageStatus status + LibBridgeStatus.MessageStatus status ); event DestChainEnabled(uint256 indexed chainId, bool enabled); @@ -159,8 +160,8 @@ contract Bridge is EssentialContract, IBridge { function getMessageStatus( bytes32 signal - ) public view virtual returns (LibBridgeData.MessageStatus) { - return state.messageStatus[signal]; + ) public view virtual returns (LibBridgeStatus.MessageStatus) { + return LibBridgeStatus.getMessageStatus(signal); } function context() public view returns (Context memory) { diff --git a/packages/protocol/contracts/bridge/libs/LibBridgeData.sol b/packages/protocol/contracts/bridge/libs/LibBridgeData.sol index 8f96058cc73..8252528c53b 100644 --- a/packages/protocol/contracts/bridge/libs/LibBridgeData.sol +++ b/packages/protocol/contracts/bridge/libs/LibBridgeData.sol @@ -19,69 +19,24 @@ import "../IBridge.sol"; * @author dantaik */ library LibBridgeData { - /********************* - * Structs * - *********************/ - enum MessageStatus { - NEW, - RETRIABLE, - DONE, - FAILED - } - struct State { // chainId => isEnabled mapping(uint256 => bool) destChains; - // message hash => status - mapping(bytes32 => MessageStatus) messageStatus; uint256 nextMessageId; IBridge.Context ctx; // 3 slots - uint256[44] __gap; + uint256[45] __gap; } - /********************* - * Constants * - *********************/ - - // TODO: figure out this value bytes32 internal constant SIGNAL_PLACEHOLDER = bytes32(uint256(1)); uint256 internal constant CHAINID_PLACEHOLDER = type(uint256).max; address internal constant SRC_CHAIN_SENDER_PLACEHOLDER = 0x0000000000000000000000000000000000000001; - /********************* - * Events * - *********************/ - // Note: These events must match the ones defined in Bridge.sol. event MessageSent(bytes32 indexed signal, IBridge.Message message); - event MessageStatusChanged(bytes32 indexed signal, MessageStatus status); - event DestChainEnabled(uint256 indexed chainId, bool enabled); - /********************* - * Internal Functions* - *********************/ - - /** - * @dev If messageStatus is same as in the messageStatus mapping, - * does nothing. - * @param state The current bridge state. - * @param signal The messageHash of the message. - * @param status The status of the message. - */ - function updateMessageStatus( - State storage state, - bytes32 signal, - MessageStatus status - ) internal { - if (state.messageStatus[signal] != status) { - state.messageStatus[signal] = status; - emit LibBridgeData.MessageStatusChanged(signal, status); - } - } - /** * @dev Hashes messages and returns the hash signed with * "TAIKO_BRIDGE_MESSAGE" for verification. diff --git a/packages/protocol/contracts/bridge/libs/LibBridgeProcess.sol b/packages/protocol/contracts/bridge/libs/LibBridgeProcess.sol index 9925ccef2bf..4a739e12cf0 100644 --- a/packages/protocol/contracts/bridge/libs/LibBridgeProcess.sol +++ b/packages/protocol/contracts/bridge/libs/LibBridgeProcess.sol @@ -12,6 +12,7 @@ import "../EtherVault.sol"; import "./LibBridgeData.sol"; import "./LibBridgeInvoke.sol"; import "./LibBridgeSignal.sol"; +import "./LibBridgeStatus.sol"; /** * Process bridge messages on the destination chain. @@ -55,7 +56,8 @@ library LibBridgeProcess { // LibBridgeRetry.sol bytes32 signal = message.hashMessage(); require( - state.messageStatus[signal] == LibBridgeData.MessageStatus.NEW, + LibBridgeStatus.getMessageStatus(signal) == + LibBridgeStatus.MessageStatus.NEW, "B:status" ); // Message must have been "received" on the destChain (current chain) @@ -83,13 +85,13 @@ library LibBridgeProcess { // will actually consume the Ether. message.owner.sendEther(message.depositValue); - LibBridgeData.MessageStatus status; + LibBridgeStatus.MessageStatus status; uint256 refundAmount; if (message.to == address(this) || message.to == address(0)) { // For these two special addresses, the call will not be actually // invoked but will be marked DONE. The callValue will be refunded. - status = LibBridgeData.MessageStatus.DONE; + status = LibBridgeStatus.MessageStatus.DONE; refundAmount = message.callValue; } else { uint256 gasLimit = msg.sender == message.owner @@ -104,16 +106,16 @@ library LibBridgeProcess { }); if (success) { - status = LibBridgeData.MessageStatus.DONE; + status = LibBridgeStatus.MessageStatus.DONE; } else { - status = LibBridgeData.MessageStatus.RETRIABLE; + status = LibBridgeStatus.MessageStatus.RETRIABLE; if (ethVault != address(0)) { ethVault.sendEther(message.callValue); } } } - state.updateMessageStatus(signal, status); + LibBridgeStatus.updateMessageStatus(signal, status); address refundAddress = message.refundAddress == address(0) ? message.owner diff --git a/packages/protocol/contracts/bridge/libs/LibBridgeRetry.sol b/packages/protocol/contracts/bridge/libs/LibBridgeRetry.sol index 2a85925c7db..c84890b4ebb 100644 --- a/packages/protocol/contracts/bridge/libs/LibBridgeRetry.sol +++ b/packages/protocol/contracts/bridge/libs/LibBridgeRetry.sol @@ -11,6 +11,7 @@ pragma solidity ^0.8.9; import "../EtherVault.sol"; import "./LibBridgeInvoke.sol"; import "./LibBridgeData.sol"; +import "./LibBridgeStatus.sol"; /** * Retry bridge messages. @@ -50,8 +51,8 @@ library LibBridgeRetry { bytes32 signal = message.hashMessage(); require( - state.messageStatus[signal] == - LibBridgeData.MessageStatus.RETRIABLE, + LibBridgeStatus.getMessageStatus(signal) == + LibBridgeStatus.MessageStatus.RETRIABLE, "B:notFound" ); @@ -69,11 +70,14 @@ library LibBridgeRetry { gasLimit: gasleft() }) ) { - state.updateMessageStatus(signal, LibBridgeData.MessageStatus.DONE); + LibBridgeStatus.updateMessageStatus( + signal, + LibBridgeStatus.MessageStatus.DONE + ); } else if (isLastAttempt) { - state.updateMessageStatus( + LibBridgeStatus.updateMessageStatus( signal, - LibBridgeData.MessageStatus.FAILED + LibBridgeStatus.MessageStatus.FAILED ); address refundAddress = message.refundAddress == address(0) diff --git a/packages/protocol/contracts/bridge/libs/LibBridgeSignal.sol b/packages/protocol/contracts/bridge/libs/LibBridgeSignal.sol index a0fc61c48ff..abc70768b25 100644 --- a/packages/protocol/contracts/bridge/libs/LibBridgeSignal.sol +++ b/packages/protocol/contracts/bridge/libs/LibBridgeSignal.sol @@ -12,6 +12,8 @@ import "../../common/AddressResolver.sol"; import "../../common/IHeaderSync.sol"; import "../../libs/LibBlockHeader.sol"; import "../../libs/LibTrieProof.sol"; +import "./LibBridgeData.sol"; +import "./LibBridgeStatus.sol"; /** * Library for working with bridge signals. @@ -43,9 +45,9 @@ library LibBridgeSignal { address sender, bytes32 signal ) internal onlyValidSenderAndSignal(sender, signal) { - bytes32 key = _key(sender, signal); + bytes32 k = _signalSlot(sender, signal); assembly { - sstore(key, 1) + sstore(k, 1) } } @@ -59,10 +61,10 @@ library LibBridgeSignal { address sender, bytes32 signal ) internal view onlyValidSenderAndSignal(sender, signal) returns (bool) { - bytes32 key = _key(sender, signal); + bytes32 k = _signalSlot(sender, signal); uint256 v; assembly { - v := sload(key) + v := sload(k) } return v == 1; } @@ -87,30 +89,27 @@ library LibBridgeSignal { ) internal view onlyValidSenderAndSignal(sender, signal) returns (bool) { require(srcBridge != address(0), "B:srcBridge"); - SignalProof memory mkp = abi.decode(proof, (SignalProof)); + SignalProof memory sp = abi.decode(proof, (SignalProof)); LibTrieProof.verify({ - stateRoot: mkp.header.stateRoot, + stateRoot: sp.header.stateRoot, addr: srcBridge, - key: _key(sender, signal), + key: _signalSlot(sender, signal), value: bytes32(uint256(1)), - mkproof: mkp.proof + mkproof: sp.proof }); // get synced header hash of the header height specified in the proof bytes32 syncedHeaderHash = IHeaderSync(resolver.resolve("taiko")) - .getSyncedHeader(mkp.header.height); + .getSyncedHeader(sp.header.height); // check header hash specified in the proof matches the current chain return syncedHeaderHash != 0 && - syncedHeaderHash == mkp.header.hashBlockHeader(); + syncedHeaderHash == sp.header.hashBlockHeader(); } - /** - * Generate the storage key for a signal. - */ - function _key( + function _signalSlot( address sender, bytes32 signal ) private pure returns (bytes32) { - return keccak256(abi.encodePacked(sender, signal)); + return keccak256(abi.encodePacked("SIGNAL", sender, signal)); } } diff --git a/packages/protocol/contracts/bridge/libs/LibBridgeStatus.sol b/packages/protocol/contracts/bridge/libs/LibBridgeStatus.sol new file mode 100644 index 00000000000..0f0b2712086 --- /dev/null +++ b/packages/protocol/contracts/bridge/libs/LibBridgeStatus.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: MIT +// +// ╭━━━━╮╱╱╭╮╱╱╱╱╱╭╮╱╱╱╱╱╭╮ +// ┃╭╮╭╮┃╱╱┃┃╱╱╱╱╱┃┃╱╱╱╱╱┃┃ +// ╰╯┃┃┣┻━┳┫┃╭┳━━╮┃┃╱╱╭━━┫╰━┳━━╮ +// ╱╱┃┃┃╭╮┣┫╰╯┫╭╮┃┃┃╱╭┫╭╮┃╭╮┃━━┫ +// ╱╱┃┃┃╭╮┃┃╭╮┫╰╯┃┃╰━╯┃╭╮┃╰╯┣━━┃ +// ╱╱╰╯╰╯╰┻┻╯╰┻━━╯╰━━━┻╯╰┻━━┻━━╯ +pragma solidity ^0.8.9; + +/** + * @author dantaik + */ +library LibBridgeStatus { + enum MessageStatus { + NEW, + RETRIABLE, + DONE, + FAILED + } + + event MessageStatusChanged(bytes32 indexed signal, MessageStatus status); + + /** + * @dev If messageStatus is same as in the messageStatus mapping, + * does nothing. + * @param signal The messageHash of the message. + * @param status The status of the message. + */ + function updateMessageStatus( + bytes32 signal, + MessageStatus status + ) internal { + if (getMessageStatus(signal) != status) { + _setMessageStatus(signal, status); + emit LibBridgeStatus.MessageStatusChanged(signal, status); + } + } + + function getMessageStatus( + bytes32 signal + ) internal view returns (MessageStatus) { + bytes32 k = _statusSlot(signal); + uint256 v; + assembly { + v := sload(k) + } + return MessageStatus(v); + } + + function _setMessageStatus(bytes32 signal, MessageStatus status) private { + bytes32 k = _statusSlot(signal); + uint256 v = uint256(status); + assembly { + sstore(k, v) + } + } + + function _statusSlot(bytes32 signal) private pure returns (bytes32) { + return keccak256(abi.encodePacked("MESSAGE_STATUS", signal)); + } +} diff --git a/packages/protocol/contracts/test/bridge/libs/TestLibBridgeData.sol b/packages/protocol/contracts/test/bridge/libs/TestLibBridgeData.sol index 6a47ef886e0..b0fc7ad9ba2 100644 --- a/packages/protocol/contracts/test/bridge/libs/TestLibBridgeData.sol +++ b/packages/protocol/contracts/test/bridge/libs/TestLibBridgeData.sol @@ -9,21 +9,20 @@ pragma solidity ^0.8.9; import "../../../bridge/libs/LibBridgeData.sol"; +import "../../../bridge/libs/LibBridgeStatus.sol"; contract TestLibBridgeData { - LibBridgeData.State public state; - function updateMessageStatus( bytes32 signal, - LibBridgeData.MessageStatus status + LibBridgeStatus.MessageStatus status ) public { - LibBridgeData.updateMessageStatus(state, signal, status); + LibBridgeStatus.updateMessageStatus(signal, status); } function getMessageStatus( bytes32 signal - ) public view returns (LibBridgeData.MessageStatus) { - return state.messageStatus[signal]; + ) public view returns (LibBridgeStatus.MessageStatus) { + return LibBridgeStatus.getMessageStatus(signal); } function hashMessage( diff --git a/packages/protocol/docs/bridge/Bridge.md b/packages/protocol/docs/bridge/Bridge.md index e463d1da05b..812951db4a2 100644 --- a/packages/protocol/docs/bridge/Bridge.md +++ b/packages/protocol/docs/bridge/Bridge.md @@ -20,7 +20,7 @@ uint256[50] __gap ### MessageStatusChanged ```solidity -event MessageStatusChanged(bytes32 signal, enum LibBridgeData.MessageStatus status) +event MessageStatusChanged(bytes32 signal, enum LibBridgeStatus.MessageStatus status) ``` ### DestChainEnabled @@ -120,7 +120,7 @@ by the specified sender. ### getMessageStatus ```solidity -function getMessageStatus(bytes32 signal) public view virtual returns (enum LibBridgeData.MessageStatus) +function getMessageStatus(bytes32 signal) public view virtual returns (enum LibBridgeStatus.MessageStatus) ``` ### context diff --git a/packages/protocol/tasks/utils.ts b/packages/protocol/tasks/utils.ts index add662ae8a2..fc50bf80bc2 100644 --- a/packages/protocol/tasks/utils.ts +++ b/packages/protocol/tasks/utils.ts @@ -68,10 +68,10 @@ function getDeployments(_fileName: string) { return JSON.parse(`${json}`); } -async function getSlot(hre: any, signal: any, mappingSlot: any) { +async function getMessageStatusSlot(hre: any, signal: any) { return hre.ethers.utils.solidityKeccak256( - ["bytes", "uint256"], - [signal, mappingSlot] + ["string", "bytes"], + ["MESSAGE_STATUS", signal] ); } @@ -79,6 +79,15 @@ async function decode(hre: any, type: any, data: any) { return hre.ethers.utils.defaultAbiCoder.decode([type], data).toString(); } +function getSignalSlot(hre: any, sender: any, signal: any) { + return hre.ethers.utils.keccak256( + hre.ethers.utils.solidityPack( + ["string", "address", "bytes32"], + ["SIGNAL", sender, signal] + ) + ); +} + const MessageStatus = { NEW: 0, RETRIABLE: 1, @@ -154,7 +163,8 @@ export { getContract, saveDeployments, getDeployments, - getSlot, + getMessageStatusSlot, + getSignalSlot, decode, MessageStatus, getLatestBlockHeader, diff --git a/packages/protocol/test/bridge/Bridge.test.ts b/packages/protocol/test/bridge/Bridge.test.ts index e7d163e6172..10a127a6430 100644 --- a/packages/protocol/test/bridge/Bridge.test.ts +++ b/packages/protocol/test/bridge/Bridge.test.ts @@ -1,7 +1,11 @@ import { expect } from "chai"; import { BigNumber, Signer } from "ethers"; import hre, { ethers } from "hardhat"; -import { getLatestBlockHeader, getSignalProof } from "../../tasks/utils"; +import { + getLatestBlockHeader, + getSignalProof, + getSignalSlot, +} from "../../tasks/utils"; import { AddressManager, Bridge, @@ -607,12 +611,7 @@ describe("integration:Bridge", function () { const sender = l1Bridge.address; - const key = ethers.utils.keccak256( - ethers.utils.solidityPack( - ["address", "bytes32"], - [sender, signal] - ) - ); + const key = getSignalSlot(hre, sender, signal); const { block, blockHeader } = await getLatestBlockHeader(hre); @@ -647,12 +646,7 @@ describe("integration:Bridge", function () { const sender = l1Bridge.address; - const key = ethers.utils.keccak256( - ethers.utils.solidityPack( - ["address", "bytes32"], - [sender, signal] - ) - ); + const key = getSignalSlot(hre, sender, signal); const { block, blockHeader } = await getLatestBlockHeader(hre); await headerSync.setSyncedHeader(ethers.constants.HashZero); @@ -694,12 +688,7 @@ describe("integration:Bridge", function () { const sender = l1Bridge.address; - const key = ethers.utils.keccak256( - ethers.utils.solidityPack( - ["address", "bytes32"], - [sender, signal] - ) - ); + const key = getSignalSlot(hre, sender, signal); const { block, blockHeader } = await getLatestBlockHeader(hre); @@ -753,12 +742,7 @@ describe("integration:Bridge", function () { const sender = l1Bridge.address; - const key = ethers.utils.keccak256( - ethers.utils.solidityPack( - ["address", "bytes32"], - [sender, signal] - ) - ); + const key = getSignalSlot(hre, sender, signal); const { block, blockHeader } = await getLatestBlockHeader(hre); @@ -885,12 +869,7 @@ describe("integration:Bridge", function () { const sender = l1Bridge.address; - const key = ethers.utils.keccak256( - ethers.utils.solidityPack( - ["address", "bytes32"], - [sender, signal] - ) - ); + const key = getSignalSlot(hre, sender, signal); const { block, blockHeader } = await getLatestBlockHeader(hre); @@ -952,12 +931,7 @@ describe("integration:Bridge", function () { const sender = owner.address; - const key = ethers.utils.keccak256( - ethers.utils.solidityPack( - ["address", "bytes32"], - [sender, signal] - ) - ); + const key = getSignalSlot(hre, sender, signal); const { block, blockHeader } = await getLatestBlockHeader(hre); @@ -1005,12 +979,7 @@ describe("integration:Bridge", function () { const sender = l1Bridge.address; - const key = ethers.utils.keccak256( - ethers.utils.solidityPack( - ["address", "bytes32"], - [sender, signal] - ) - ); + const key = getSignalSlot(hre, sender, signal); const { block, blockHeader } = await getLatestBlockHeader(hre); @@ -1091,12 +1060,7 @@ describe("integration:Bridge", function () { const sender = owner.address; - const key = ethers.utils.keccak256( - ethers.utils.solidityPack( - ["address", "bytes32"], - [sender, signal] - ) - ); + const key = getSignalSlot(hre, sender, signal); const { block, blockHeader } = await getLatestBlockHeader(hre); @@ -1143,12 +1107,7 @@ describe("integration:Bridge", function () { const sender = owner.address; - const key = ethers.utils.keccak256( - ethers.utils.solidityPack( - ["address", "bytes32"], - [sender, signal] - ) - ); + const key = getSignalSlot(hre, sender, signal); const { block, blockHeader } = await getLatestBlockHeader(hre); diff --git a/packages/protocol/test/bridge/TokenVault.test.ts b/packages/protocol/test/bridge/TokenVault.test.ts index 7843a2b14b7..ad88cf554ca 100644 --- a/packages/protocol/test/bridge/TokenVault.test.ts +++ b/packages/protocol/test/bridge/TokenVault.test.ts @@ -1,8 +1,18 @@ +/* eslint-disable camelcase */ import { expect } from "chai"; -import { AddressManager, TokenVault } from "../../typechain"; +import { + AddressManager, + AddressManager__factory, + BridgedERC20, + BridgedERC20__factory, + TestMessageSender__factory, + TokenVault, + TokenVault__factory, +} from "../../typechain"; import { ethers } from "hardhat"; import { BigNumber, BigNumberish } from "ethers"; import { ADDRESS_RESOLVER_DENIED } from "../constants/errors"; +import { MockContract, smock } from "@defi-wonderland/smock"; type CanonicalERC20 = { chainId: BigNumberish; @@ -23,38 +33,78 @@ const weth: CanonicalERC20 = { describe("TokenVault", function () { let owner: any; let nonOwner: any; - let tokenVault: TokenVault; + let L1TokenVault: MockContract; + let tokenVaultAddressManager: AddressManager; + let destChainTokenVault: TokenVault; const defaultProcessingFee = 10; const destChainId = 167001; + let bridgedToken: MockContract; before(async function () { [owner, nonOwner] = await ethers.getSigners(); }); beforeEach(async function () { - const tokenVaultAddressManager: AddressManager = await ( - await ethers.getContractFactory("AddressManager") - ).deploy(); + const network = await ethers.provider.getNetwork(); + const addressManagerFactory: AddressManager__factory = + await ethers.getContractFactory("AddressManager"); + const tokenVaultFactory: TokenVault__factory = + await ethers.getContractFactory("TokenVault"); + + tokenVaultAddressManager = await addressManagerFactory.deploy(); await tokenVaultAddressManager.init(); - tokenVault = await (await ethers.getContractFactory("TokenVault")) - .connect(owner) - .deploy(); + const mockTokenVaultFactory = await smock.mock( + "TokenVault" + ); - await tokenVault.init(tokenVaultAddressManager.address); + L1TokenVault = await mockTokenVaultFactory.connect(owner).deploy(); + await L1TokenVault.init(tokenVaultAddressManager.address); - const network = await ethers.provider.getNetwork(); + destChainTokenVault = await tokenVaultFactory.connect(owner).deploy(); + await destChainTokenVault.init(tokenVaultAddressManager.address); - const TestMessageSenderFactory = await ethers.getContractFactory( - "TestMessageSender" - ); + const TestMessageSenderFactory: TestMessageSender__factory = + await ethers.getContractFactory("TestMessageSender"); const testMessageSender = await TestMessageSenderFactory.deploy(); + const testMessageSender2 = await TestMessageSenderFactory.deploy(); await tokenVaultAddressManager.setAddress( `${network.chainId}.bridge`, testMessageSender.address ); + await tokenVaultAddressManager.setAddress( + `${destChainId}.bridge`, + testMessageSender2.address + ); + await tokenVaultAddressManager.setAddress( + `${network.chainId}.token_vault`, + L1TokenVault.address + ); + await tokenVaultAddressManager.setAddress( + `${destChainId}.token_vault`, + destChainTokenVault.address + ); + + const bridgedTokenFactory = await smock.mock( + "BridgedERC20" + ); + + bridgedToken = await bridgedTokenFactory.deploy(); + + await bridgedToken.init( + tokenVaultAddressManager.address, + weth.addr, + destChainId, + 18, + weth.symbol, + weth.name + ); + + await bridgedToken.setVariable("_totalSupply", 1000000); + await bridgedToken.approve(owner.address, 1000); + await bridgedToken.setVariable("_balances", { [owner.address]: 10 }); }); describe("receiveERC20()", async () => { @@ -62,7 +112,7 @@ describe("TokenVault", function () { const amount = BigNumber.from(1); await expect( - tokenVault.receiveERC20( + L1TokenVault.receiveERC20( weth, owner.address, nonOwner.address, @@ -75,7 +125,7 @@ describe("TokenVault", function () { describe("sendEther()", async () => { it("throws when msg.value is 0", async () => { await expect( - tokenVault.sendEther( + L1TokenVault.sendEther( destChainId, owner.address, 10000, @@ -88,7 +138,7 @@ describe("TokenVault", function () { it("throws when msg.value - processing fee is 0", async () => { await expect( - tokenVault.sendEther( + L1TokenVault.sendEther( destChainId, owner.address, 10000, @@ -104,7 +154,7 @@ describe("TokenVault", function () { it("throws when msg.value is < processingFee", async () => { await expect( - tokenVault.sendEther( + L1TokenVault.sendEther( destChainId, owner.address, 10000, @@ -120,7 +170,7 @@ describe("TokenVault", function () { it("throws when to is 0", async () => { await expect( - tokenVault.sendEther( + L1TokenVault.sendEther( destChainId, ethers.constants.AddressZero, 10000, @@ -141,7 +191,7 @@ describe("TokenVault", function () { "0x3fd54831f488a22b28398de0c567a3b064b937f54f81739ae9bd545967f3abab"; await expect( - tokenVault.sendEther( + L1TokenVault.sendEther( destChainId, owner.address, 10000, @@ -153,7 +203,7 @@ describe("TokenVault", function () { } ) ) - .to.emit(tokenVault, "EtherSent") + .to.emit(L1TokenVault, "EtherSent") .withArgs( owner.address, destChainId, @@ -169,7 +219,7 @@ describe("TokenVault", function () { "0x3fd54831f488a22b28398de0c567a3b064b937f54f81739ae9bd545967f3abab"; await expect( - tokenVault.sendEther( + L1TokenVault.sendEther( destChainId, owner.address, 10000, @@ -181,7 +231,7 @@ describe("TokenVault", function () { } ) ) - .to.emit(tokenVault, "EtherSent") + .to.emit(L1TokenVault, "EtherSent") .withArgs( owner.address, destChainId, @@ -190,4 +240,127 @@ describe("TokenVault", function () { ); }); }); + + describe("sendERC20()", async () => { + it("should throw if to == address(0)", async function () { + await expect( + L1TokenVault.sendERC20( + destChainId, + ethers.constants.AddressZero, + weth.addr, + 1, + 20000000, + 1000, + owner.address, + "", + { + value: 1, + } + ) + ).to.be.revertedWith("V:to"); + }); + + it("should throw if to == destChainId.token_vault", async function () { + await expect( + L1TokenVault.sendERC20( + destChainId, + destChainTokenVault.address, + weth.addr, + 1, + 20000000, + 1000, + owner.address, + "", + { + value: 1, + } + ) + ).to.be.revertedWith("V:to"); + }); + + it("should throw if token == address(0)", async function () { + await expect( + L1TokenVault.sendERC20( + destChainId, + nonOwner.address, + ethers.constants.AddressZero, + 1, + 20000000, + 1000, + owner.address, + "", + { + value: 1, + } + ) + ).to.be.revertedWith("V:token"); + }); + + it("should throw if amount <= 0", async function () { + await expect( + L1TokenVault.sendERC20( + destChainId, + nonOwner.address, + weth.addr, + 0, + 20000000, + 1000, + owner.address, + "", + { + value: 1, + } + ) + ).to.be.revertedWith("V:amount"); + }); + + it("should throw if isBridgedToken, and canonicalToken.addr == address(0)", async function () { + await L1TokenVault.setVariable("isBridgedToken", { + [bridgedToken.address]: true, + }); + // don't need to manually set bridgedToCanonical since default value is addressZero + + await expect( + L1TokenVault.connect(owner).sendERC20( + destChainId, + nonOwner.address, + bridgedToken.address, + 1, + 20000000, + 1000, + owner.address, + "", + { + value: 1, + } + ) + ).to.be.revertedWith("V:canonicalToken"); + }); + + it("should pass and emit ERC20Sent Event", async function () { + await L1TokenVault.setVariable("isBridgedToken", { + [bridgedToken.address]: true, + }); + + await L1TokenVault.setVariable("bridgedToCanonical", { + [bridgedToken.address]: weth, + }); + + await expect( + L1TokenVault.connect(owner).sendERC20( + destChainId, + nonOwner.address, + bridgedToken.address, + 1, + 20000000, + 1000, + owner.address, + "", + { + value: 1000, + } + ) + ).to.emit(L1TokenVault, "ERC20Sent"); + }); + }); }); diff --git a/packages/protocol/test/bridge/libs/LibBridgeProcess.test.ts b/packages/protocol/test/bridge/libs/LibBridgeProcess.test.ts index 7c597e2394e..c542d640ae8 100644 --- a/packages/protocol/test/bridge/libs/LibBridgeProcess.test.ts +++ b/packages/protocol/test/bridge/libs/LibBridgeProcess.test.ts @@ -1,9 +1,7 @@ import * as helpers from "@nomicfoundation/hardhat-network-helpers"; import { expect } from "chai"; import hre, { ethers } from "hardhat"; -import * as fs from "fs"; -import * as path from "path"; -import { getSlot, MessageStatus } from "../../../tasks/utils"; +import { getMessageStatusSlot, MessageStatus } from "../../../tasks/utils"; import { Message } from "../../utils/message"; import { AddressManager, @@ -13,30 +11,6 @@ import { } from "../../../typechain"; describe("LibBridgeProcess", async function () { - function getStateSlot() { - const buildInfoDir = path.join( - __dirname, - "../../../artifacts/build-info" - ); - const contractPath = - "contracts/test/bridge/libs/TestLibBridgeProcess.sol"; - const contractName = "TestLibBridgeProcess"; - - for (const buildInfoJson of fs.readdirSync(buildInfoDir)) { - const { output } = require(path.join(buildInfoDir, buildInfoJson)); - - if (!output.contracts[contractPath]) continue; - - const slotInfo = output.contracts[contractPath][ - contractName - ].storageLayout.storage.find(({ label }: any) => label === "state"); - - if (slotInfo) return Number(slotInfo.slot); - } - - throw new Error("TestLibBridgeProcess.state slot number not found"); - } - let owner: any; let nonOwner: any; let etherVaultOwner: any; @@ -46,7 +20,6 @@ describe("LibBridgeProcess", async function () { let libProcessLink; let libProcess: TestLibBridgeProcess; let testTaikoData: TestLibBridgeData; - const stateSlot = getStateSlot(); const srcChainId = 1; const blockChainId = hre.network.config.chainId ?? 0; @@ -184,7 +157,7 @@ describe("LibBridgeProcess", async function () { await helpers.setStorageAt( libProcess.address, - await getSlot(hre, signal, stateSlot + 1), + await getMessageStatusSlot(hre, signal), MessageStatus.RETRIABLE ); diff --git a/packages/protocol/test/bridge/libs/LibBridgeRetry.test.ts b/packages/protocol/test/bridge/libs/LibBridgeRetry.test.ts index 929c7ea8444..bd99c8110e2 100644 --- a/packages/protocol/test/bridge/libs/LibBridgeRetry.test.ts +++ b/packages/protocol/test/bridge/libs/LibBridgeRetry.test.ts @@ -2,7 +2,11 @@ import * as helpers from "@nomicfoundation/hardhat-network-helpers"; import { expect } from "chai"; import hre, { ethers } from "hardhat"; import { Message } from "../../utils/message"; -import { getSlot, decode, MessageStatus } from "../../../tasks/utils"; +import { + getMessageStatusSlot, + decode, + MessageStatus, +} from "../../../tasks/utils"; import { AddressManager, EtherVault, @@ -190,7 +194,7 @@ describe("LibBridgeRetry", function () { await helpers.setStorageAt( badLibRetry.address, - await getSlot(hre, signal, 202), + await getMessageStatusSlot(hre, signal), MessageStatus.RETRIABLE ); @@ -203,7 +207,7 @@ describe("LibBridgeRetry", function () { "uint256", await ethers.provider.getStorageAt( badLibRetry.address, - getSlot(hre, signal, 202) + getMessageStatusSlot(hre, signal) ) ) ).to.equal(MessageStatus.RETRIABLE.toString()); @@ -239,7 +243,7 @@ describe("LibBridgeRetry", function () { await helpers.setStorageAt( libRetry.address, - await getSlot(hre, signal, 202), + await getMessageStatusSlot(hre, signal), MessageStatus.RETRIABLE ); @@ -253,7 +257,7 @@ describe("LibBridgeRetry", function () { "uint256", await ethers.provider.getStorageAt( libRetry.address, - getSlot(hre, signal, 202) + getMessageStatusSlot(hre, signal) ) ) ).to.equal(MessageStatus.FAILED.toString()); @@ -291,7 +295,7 @@ describe("LibBridgeRetry", function () { await helpers.setStorageAt( libRetry.address, - await getSlot(hre, signal, 202), + await getMessageStatusSlot(hre, signal), MessageStatus.RETRIABLE ); @@ -309,7 +313,7 @@ describe("LibBridgeRetry", function () { "uint256", await ethers.provider.getStorageAt( libRetry.address, - getSlot(hre, signal, 202) + getMessageStatusSlot(hre, signal) ) ) ).to.equal(MessageStatus.RETRIABLE.toString()); @@ -345,7 +349,7 @@ describe("LibBridgeRetry", function () { await helpers.setStorageAt( libRetry.address, - await getSlot(hre, signal, 202), + await getMessageStatusSlot(hre, signal), MessageStatus.RETRIABLE ); @@ -359,7 +363,7 @@ describe("LibBridgeRetry", function () { "uint256", await ethers.provider.getStorageAt( libRetry.address, - getSlot(hre, signal, 202) + getMessageStatusSlot(hre, signal) ) ) ).to.equal(MessageStatus.DONE.toString()); diff --git a/packages/protocol/test/libs/LibTrieProof.test.ts b/packages/protocol/test/libs/LibTrieProof.test.ts index 7535911a488..89b5e7f1b7c 100644 --- a/packages/protocol/test/libs/LibTrieProof.test.ts +++ b/packages/protocol/test/libs/LibTrieProof.test.ts @@ -1,8 +1,9 @@ import { expect } from "chai"; -import { ethers } from "hardhat"; +import hre, { ethers } from "hardhat"; import RLP from "rlp"; import { Message } from "../utils/message"; import { EthGetProofResponse } from "../utils/rpc"; +import { getSignalSlot } from "../../tasks/utils"; describe("integration:LibTrieProof", function () { async function deployLibTrieProofFixture() { @@ -106,12 +107,7 @@ describe("integration:LibTrieProof", function () { const sender = bridge.address; - const key = ethers.utils.keccak256( - ethers.utils.solidityPack( - ["address", "bytes32"], - [sender, signal] - ) - ); + const key = getSignalSlot(hre, sender, signal); // use this instead of ethers.provider.getBlock() beccause it doesnt have stateRoot // in the response diff --git a/packages/protocol/test/test_integration.sh b/packages/protocol/test/test_integration.sh index 6b73d4ff8a4..485cc8b4d23 100755 --- a/packages/protocol/test/test_integration.sh +++ b/packages/protocol/test/test_integration.sh @@ -38,7 +38,7 @@ docker run -d \ hardhat node --hostname "0.0.0.0" function waitTestNode { - echo "Waiting test node: $1" + echo "Waiting for test node: $1" # Wait till the test node fully started RETRIES=30 i=0