diff --git a/packages/protocol/contracts/L1/libs/LibProposing.sol b/packages/protocol/contracts/L1/libs/LibProposing.sol index 07f65619058..124d0969310 100644 --- a/packages/protocol/contracts/L1/libs/LibProposing.sol +++ b/packages/protocol/contracts/L1/libs/LibProposing.sol @@ -102,7 +102,7 @@ library LibProposing { { uint256 fee; (newFeeBase, fee, deposit) = getBlockFee(state, config); - TkoToken(resolver.resolve("tko_token")).burn( + TkoToken(resolver.resolve("tko_token", false)).burn( msg.sender, fee + deposit ); diff --git a/packages/protocol/contracts/L1/libs/LibProving.sol b/packages/protocol/contracts/L1/libs/LibProving.sol index dc384a256c3..34cc5962779 100644 --- a/packages/protocol/contracts/L1/libs/LibProving.sol +++ b/packages/protocol/contracts/L1/libs/LibProving.sol @@ -81,7 +81,8 @@ library LibProving { ); require(_tx.txType == 0, "L1:anchor:type"); require( - _tx.destination == resolver.resolve(config.chainId, "taiko"), + _tx.destination == + resolver.resolve(config.chainId, "taiko", false), "L1:anchor:dest" ); require( @@ -107,7 +108,7 @@ library LibProving { } IProofVerifier proofVerifier = IProofVerifier( - resolver.resolve("proof_verifier") + resolver.resolve("proof_verifier", false) ); // Check anchor tx is the 1st tx in the block @@ -175,7 +176,7 @@ library LibProving { ); IProofVerifier proofVerifier = IProofVerifier( - resolver.resolve("proof_verifier") + resolver.resolve("proof_verifier", false) ); // Check the event is the first one in the throw-away block @@ -200,7 +201,7 @@ library LibProving { LibReceiptDecoder.Log memory log = receipt.logs[0]; require( log.contractAddress == - resolver.resolve(config.chainId, "taiko"), + resolver.resolve(config.chainId, "taiko", false), "L1:receipt:addr" ); require(log.data.length == 0, "L1:receipt:data"); @@ -249,7 +250,7 @@ library LibProving { require( proofVerifier.verifyZKP({ verificationKey: ConfigManager( - resolver.resolve("config_manager") + resolver.resolve("config_manager", false) ).getValue(string(abi.encodePacked("zk_vkey_", i))), zkproof: evidence.proofs[i], blockHash: blockHash, diff --git a/packages/protocol/contracts/L1/libs/LibVerifying.sol b/packages/protocol/contracts/L1/libs/LibVerifying.sol index d44fb4de870..2403ca310b6 100644 --- a/packages/protocol/contracts/L1/libs/LibVerifying.sol +++ b/packages/protocol/contracts/L1/libs/LibVerifying.sol @@ -190,7 +190,9 @@ library LibVerifying { proposedAt: target.proposedAt }); - TkoToken tkoToken = TkoToken(resolver.resolve("tko_token")); + TkoToken tkoToken = TkoToken( + resolver.resolve("tko_token", false) + ); _rewardProvers(fc, reward, tkoToken); _refundProposerDeposit(target, tRelBp, tkoToken); diff --git a/packages/protocol/contracts/bridge/Bridge.sol b/packages/protocol/contracts/bridge/Bridge.sol index ca3c924ec99..8b4ae3b7f6b 100644 --- a/packages/protocol/contracts/bridge/Bridge.sol +++ b/packages/protocol/contracts/bridge/Bridge.sol @@ -14,7 +14,6 @@ import "./libs/LibBridgeData.sol"; import "./libs/LibBridgeProcess.sol"; import "./libs/LibBridgeRetry.sol"; import "./libs/LibBridgeSend.sol"; -import "./libs/LibBridgeSignal.sol"; import "./libs/LibBridgeStatus.sol"; /** @@ -39,7 +38,7 @@ contract Bridge is EssentialContract, IBridge { *********************/ event MessageStatusChanged( - bytes32 indexed signal, + bytes32 indexed msgHash, LibBridgeStatus.MessageStatus status ); @@ -59,7 +58,7 @@ contract Bridge is EssentialContract, IBridge { function sendMessage( Message calldata message - ) external payable nonReentrant returns (bytes32 signal) { + ) external payable nonReentrant returns (bytes32 msgHash) { return LibBridgeSend.sendMessage({ state: state, @@ -68,11 +67,6 @@ contract Bridge is EssentialContract, IBridge { }); } - function sendSignal(bytes32 signal) external override { - LibBridgeSignal.sendSignal(msg.sender, signal); - emit SignalSent(msg.sender, signal); - } - function processMessage( Message calldata message, bytes calldata proof @@ -103,54 +97,28 @@ contract Bridge is EssentialContract, IBridge { * Public Functions * *********************/ - function isMessageSent(bytes32 signal) public view virtual returns (bool) { - return LibBridgeSignal.isSignalSent(address(this), signal); + function isMessageSent(bytes32 msgHash) public view virtual returns (bool) { + return LibBridgeSend.isMessageSent(AddressResolver(this), msgHash); } function isMessageReceived( - bytes32 signal, - uint256 srcChainId, - bytes calldata proof - ) public view virtual override returns (bool) { - address srcBridge = resolve(srcChainId, "bridge"); - return - LibBridgeSignal.isSignalReceived({ - resolver: AddressResolver(this), - srcBridge: srcBridge, - sender: srcBridge, - signal: signal, - proof: proof - }); - } - - function isSignalSent( - address sender, - bytes32 signal - ) public view virtual override returns (bool) { - return LibBridgeSignal.isSignalSent(sender, signal); - } - - function isSignalReceived( - bytes32 signal, + bytes32 msgHash, uint256 srcChainId, - address sender, bytes calldata proof ) public view virtual override returns (bool) { - address srcBridge = resolve(srcChainId, "bridge"); return - LibBridgeSignal.isSignalReceived({ + LibBridgeSend.isMessageReceived({ resolver: AddressResolver(this), - srcBridge: srcBridge, - sender: sender, - signal: signal, + msgHash: msgHash, + srcChainId: srcChainId, proof: proof }); } function getMessageStatus( - bytes32 signal + bytes32 msgHash ) public view virtual returns (LibBridgeStatus.MessageStatus) { - return LibBridgeStatus.getMessageStatus(signal); + return LibBridgeStatus.getMessageStatus(msgHash); } function context() public view returns (Context memory) { @@ -162,9 +130,13 @@ contract Bridge is EssentialContract, IBridge { LibBridgeSend.isDestChainEnabled(AddressResolver(this), _chainId); } + function hashMessage(Message memory message) public pure returns (bytes32) { + return LibBridgeData.hashMessage(message); + } + function getMessageStatusSlot( - bytes32 signal + bytes32 msgHash ) public pure returns (bytes32) { - return LibBridgeStatus.getMessageStatusSlot(signal); + return LibBridgeStatus.getMessageStatusSlot(msgHash); } } diff --git a/packages/protocol/contracts/bridge/IBridge.sol b/packages/protocol/contracts/bridge/IBridge.sol index cff90965146..dc3bec40878 100644 --- a/packages/protocol/contracts/bridge/IBridge.sol +++ b/packages/protocol/contracts/bridge/IBridge.sol @@ -31,50 +31,30 @@ interface IBridge { } struct Context { - bytes32 signal; // messageHash + bytes32 msgHash; // messageHash address sender; uint256 srcChainId; } - event SignalSent(address sender, bytes32 signal); + event SignalSent(address sender, bytes32 msgHash); - event MessageSent(bytes32 indexed signal, Message message); + event MessageSent(bytes32 indexed msgHash, Message message); /// Sends a message to the destination chain and takes custody /// of Ether required in this contract. All extra Ether will be refunded. function sendMessage( Message memory message - ) external payable returns (bytes32 signal); + ) external payable returns (bytes32 msgHash); - /// Stores a signal on the bridge contract and emits an event for the - /// relayer to pick up. - function sendSignal(bytes32 signal) external; - - /// Checks if a signal has been stored on the bridge contract by the + /// Checks if a msgHash has been stored on the bridge contract by the /// current address. - function isMessageSent(bytes32 signal) external view returns (bool); + function isMessageSent(bytes32 msgHash) external view returns (bool); - /// Checks if a signal has been received on the destination chain and + /// Checks if a msgHash has been received on the destination chain and /// sent by the src chain. function isMessageReceived( - bytes32 signal, - uint256 srcChainId, - bytes calldata proof - ) external view returns (bool); - - /// Checks if a signal has been stored on the bridge contract by the - /// specified address. - function isSignalSent( - address sender, - bytes32 signal - ) external view returns (bool); - - /// Check if a signal has been received on the destination chain and sent - /// by the specified sender. - function isSignalReceived( - bytes32 signal, + bytes32 msgHash, uint256 srcChainId, - address sender, bytes calldata proof ) external view returns (bool); diff --git a/packages/protocol/contracts/bridge/TokenVault.sol b/packages/protocol/contracts/bridge/TokenVault.sol index 3c74b349d02..f719128de31 100644 --- a/packages/protocol/contracts/bridge/TokenVault.sol +++ b/packages/protocol/contracts/bridge/TokenVault.sol @@ -120,7 +120,8 @@ contract TokenVault is EssentialContract { string memory memo ) external payable nonReentrant { require( - to != address(0) && to != resolve(destChainId, "token_vault"), + to != address(0) && + to != resolve(destChainId, "token_vault", false), "V:to" ); require(msg.value > processingFee, "V:msgValue"); @@ -138,7 +139,7 @@ contract TokenVault is EssentialContract { // Ether are held by the Bridge on L1 and by the EtherVault on L2, not // the TokenVault - bytes32 signal = IBridge(resolve("bridge")).sendMessage{ + bytes32 signal = IBridge(resolve("bridge", false)).sendMessage{ value: msg.value }(message); @@ -175,7 +176,8 @@ contract TokenVault is EssentialContract { string memory memo ) external payable nonReentrant { require( - to != address(0) && to != resolve(destChainId, "token_vault"), + to != address(0) && + to != resolve(destChainId, "token_vault", false), "V:to" ); require(token != address(0), "V:token"); @@ -209,7 +211,7 @@ contract TokenVault is EssentialContract { message.destChainId = destChainId; message.owner = msg.sender; - message.to = resolve(destChainId, "token_vault"); + message.to = resolve(destChainId, "token_vault", false); message.data = abi.encodeWithSelector( TokenVault.receiveERC20.selector, canonicalToken, @@ -224,7 +226,7 @@ contract TokenVault is EssentialContract { message.refundAddress = refundAddress; message.memo = memo; - bytes32 signal = IBridge(resolve("bridge")).sendMessage{ + bytes32 signal = IBridge(resolve("bridge", false)).sendMessage{ value: msg.value }(message); @@ -249,7 +251,7 @@ contract TokenVault is EssentialContract { ) external nonReentrant onlyFromNamed("bridge") { IBridge.Context memory ctx = IBridge(msg.sender).context(); require( - ctx.sender == resolve(ctx.srcChainId, "token_vault"), + ctx.sender == resolve(ctx.srcChainId, "token_vault", false), "V:sender" ); diff --git a/packages/protocol/contracts/bridge/libs/LibBridgeData.sol b/packages/protocol/contracts/bridge/libs/LibBridgeData.sol index b4b99b4421f..e7f50cb6a2f 100644 --- a/packages/protocol/contracts/bridge/libs/LibBridgeData.sol +++ b/packages/protocol/contracts/bridge/libs/LibBridgeData.sol @@ -25,13 +25,13 @@ library LibBridgeData { uint256[46] __gap; } - bytes32 internal constant SIGNAL_PLACEHOLDER = bytes32(uint256(1)); + bytes32 internal constant MESSAGE_HASH_PLACEHOLDER = bytes32(uint256(1)); uint256 internal constant CHAINID_PLACEHOLDER = type(uint256).max; address internal constant SRC_CHAIN_SENDER_PLACEHOLDER = - 0x0000000000000000000000000000000000000001; + address(uint160(uint256(1))); // Note: These events must match the ones defined in Bridge.sol. - event MessageSent(bytes32 indexed signal, IBridge.Message message); + event MessageSent(bytes32 indexed msgHash, IBridge.Message message); event DestChainEnabled(uint256 indexed chainId, bool enabled); diff --git a/packages/protocol/contracts/bridge/libs/LibBridgeInvoke.sol b/packages/protocol/contracts/bridge/libs/LibBridgeInvoke.sol index dc8539e950e..db4e09d46f9 100644 --- a/packages/protocol/contracts/bridge/libs/LibBridgeInvoke.sol +++ b/packages/protocol/contracts/bridge/libs/LibBridgeInvoke.sol @@ -24,13 +24,13 @@ library LibBridgeInvoke { function invokeMessageCall( LibBridgeData.State storage state, IBridge.Message memory message, - bytes32 signal, + bytes32 msgHash, uint256 gasLimit ) internal returns (bool success) { require(gasLimit > 0, "B:gasLimit"); state.ctx = IBridge.Context({ - signal: signal, + msgHash: msgHash, sender: message.sender, srcChainId: message.srcChainId }); @@ -40,7 +40,7 @@ library LibBridgeInvoke { ); state.ctx = IBridge.Context({ - signal: LibBridgeData.SIGNAL_PLACEHOLDER, + msgHash: LibBridgeData.MESSAGE_HASH_PLACEHOLDER, sender: LibBridgeData.SRC_CHAIN_SENDER_PLACEHOLDER, srcChainId: LibBridgeData.CHAINID_PLACEHOLDER }); diff --git a/packages/protocol/contracts/bridge/libs/LibBridgeProcess.sol b/packages/protocol/contracts/bridge/libs/LibBridgeProcess.sol index 4a739e12cf0..b597a2db521 100644 --- a/packages/protocol/contracts/bridge/libs/LibBridgeProcess.sol +++ b/packages/protocol/contracts/bridge/libs/LibBridgeProcess.sol @@ -8,10 +8,10 @@ // ╱╱╰╯╰╯╰┻┻╯╰┻━━╯╰━━━┻╯╰┻━━┻━━╯ pragma solidity ^0.8.9; +import "../../signal/ISignalService.sol"; import "../EtherVault.sol"; import "./LibBridgeData.sol"; import "./LibBridgeInvoke.sol"; -import "./LibBridgeSignal.sol"; import "./LibBridgeStatus.sol"; /** @@ -37,7 +37,7 @@ library LibBridgeProcess { * @param state The bridge state. * @param resolver The address resolver. * @param message The message to process. - * @param proof The proof of the signal being sent on the source chain. + * @param proof The msgHash proof from the source chain. */ function processMessage( LibBridgeData.State storage state, @@ -54,28 +54,32 @@ library LibBridgeProcess { // The status of the message must be "NEW"; RETRIABLE is handled in // LibBridgeRetry.sol - bytes32 signal = message.hashMessage(); + bytes32 msgHash = message.hashMessage(); require( - LibBridgeStatus.getMessageStatus(signal) == + LibBridgeStatus.getMessageStatus(msgHash) == LibBridgeStatus.MessageStatus.NEW, "B:status" ); // Message must have been "received" on the destChain (current chain) - address srcBridge = resolver.resolve(message.srcChainId, "bridge"); + address srcBridge = resolver.resolve( + message.srcChainId, + "bridge", + false + ); require( - LibBridgeSignal.isSignalReceived({ - resolver: resolver, - srcBridge: srcBridge, - sender: srcBridge, - signal: signal, - proof: proof - }), + ISignalService(resolver.resolve("signal_service", false)) + .isSignalReceived({ + srcChainId: message.srcChainId, + app: srcBridge, + signal: msgHash, + proof: proof + }), "B:notReceived" ); // We retrieve the necessary ether from EtherVault - address ethVault = resolver.resolve("ether_vault"); + address ethVault = resolver.resolve("ether_vault", false); if (ethVault != address(0)) { EtherVault(payable(ethVault)).receiveEther( message.depositValue + message.callValue + message.processingFee @@ -101,7 +105,7 @@ library LibBridgeProcess { bool success = LibBridgeInvoke.invokeMessageCall({ state: state, message: message, - signal: signal, + msgHash: msgHash, gasLimit: gasLimit }); @@ -115,7 +119,7 @@ library LibBridgeProcess { } } - LibBridgeStatus.updateMessageStatus(signal, status); + LibBridgeStatus.updateMessageStatus(msgHash, 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 c84890b4ebb..636bf672ff3 100644 --- a/packages/protocol/contracts/bridge/libs/LibBridgeRetry.sol +++ b/packages/protocol/contracts/bridge/libs/LibBridgeRetry.sol @@ -49,14 +49,14 @@ library LibBridgeRetry { require(msg.sender == message.owner, "B:denied"); } - bytes32 signal = message.hashMessage(); + bytes32 msgHash = message.hashMessage(); require( - LibBridgeStatus.getMessageStatus(signal) == + LibBridgeStatus.getMessageStatus(msgHash) == LibBridgeStatus.MessageStatus.RETRIABLE, "B:notFound" ); - address ethVault = resolver.resolve("ether_vault"); + address ethVault = resolver.resolve("ether_vault", true); if (ethVault != address(0)) { EtherVault(payable(ethVault)).receiveEther(message.callValue); } @@ -66,17 +66,17 @@ library LibBridgeRetry { LibBridgeInvoke.invokeMessageCall({ state: state, message: message, - signal: signal, + msgHash: msgHash, gasLimit: gasleft() }) ) { LibBridgeStatus.updateMessageStatus( - signal, + msgHash, LibBridgeStatus.MessageStatus.DONE ); } else if (isLastAttempt) { LibBridgeStatus.updateMessageStatus( - signal, + msgHash, LibBridgeStatus.MessageStatus.FAILED ); diff --git a/packages/protocol/contracts/bridge/libs/LibBridgeSend.sol b/packages/protocol/contracts/bridge/libs/LibBridgeSend.sol index b237b39cb2a..916cbbbcf9e 100644 --- a/packages/protocol/contracts/bridge/libs/LibBridgeSend.sol +++ b/packages/protocol/contracts/bridge/libs/LibBridgeSend.sol @@ -8,8 +8,8 @@ // ╱╱╰╯╰╯╰┻┻╯╰┻━━╯╰━━━┻╯╰┻━━┻━━╯ pragma solidity ^0.8.9; +import "../../signal/ISignalService.sol"; import "./LibBridgeData.sol"; -import "./LibBridgeSignal.sol"; /** * Entry point for starting a bridge transaction. @@ -29,7 +29,7 @@ library LibBridgeSend { * `destChainId` which must have a `bridge` address set on the * AddressResolver and differ from the current chain ID. * - * @return signal The message is hashed, stored, and emitted as a signal. + * @return msgHash The hash of message sent. * This is picked up by an off-chain relayer which indicates a * bridge message has been sent and is ready to be processed on the * destination chain. @@ -38,7 +38,7 @@ library LibBridgeSend { LibBridgeData.State storage state, AddressResolver resolver, IBridge.Message memory message - ) internal returns (bytes32 signal) { + ) internal returns (bytes32 msgHash) { require(message.owner != address(0), "B:owner"); require( message.destChainId != block.chainid && @@ -53,7 +53,7 @@ library LibBridgeSend { // For each message, expectedAmount is sent to ethVault to be handled. // Processing will retrieve these funds directly from ethVault. - address ethVault = resolver.resolve("ether_vault"); + address ethVault = resolver.resolve("ether_vault", true); if (ethVault != address(0)) { ethVault.sendEther(expectedAmount); } @@ -62,17 +62,45 @@ library LibBridgeSend { message.sender = msg.sender; message.srcChainId = block.chainid; - signal = message.hashMessage(); + msgHash = message.hashMessage(); // Store a key which is the hash of this contract address and the - // signal, with a value of 1. - LibBridgeSignal.sendSignal(address(this), signal); - emit LibBridgeData.MessageSent(signal, message); + // msgHash, with a value of 1. + ISignalService(resolver.resolve("signal_service", false)).sendSignal( + msgHash + ); + emit LibBridgeData.MessageSent(msgHash, message); } function isDestChainEnabled( AddressResolver resolver, uint256 chainId ) internal view returns (bool) { - return resolver.resolve(chainId, "bridge") != address(0); + return resolver.resolve(chainId, "bridge", true) != address(0); + } + + function isMessageSent( + AddressResolver resolver, + bytes32 msgHash + ) internal view returns (bool) { + return + ISignalService(resolver.resolve("signal_service", false)) + .isSignalSent({app: address(this), signal: msgHash}); + } + + function isMessageReceived( + AddressResolver resolver, + bytes32 msgHash, + uint256 srcChainId, + bytes calldata proof + ) internal view returns (bool) { + address srcBridge = resolver.resolve(srcChainId, "bridge", false); + return + ISignalService(resolver.resolve("signal_service", false)) + .isSignalReceived({ + srcChainId: srcChainId, + app: srcBridge, + signal: msgHash, + proof: proof + }); } } diff --git a/packages/protocol/contracts/bridge/libs/LibBridgeSignal.sol b/packages/protocol/contracts/bridge/libs/LibBridgeSignal.sol deleted file mode 100644 index abc70768b25..00000000000 --- a/packages/protocol/contracts/bridge/libs/LibBridgeSignal.sol +++ /dev/null @@ -1,115 +0,0 @@ -// SPDX-License-Identifier: MIT -// -// ╭━━━━╮╱╱╭╮╱╱╱╱╱╭╮╱╱╱╱╱╭╮ -// ┃╭╮╭╮┃╱╱┃┃╱╱╱╱╱┃┃╱╱╱╱╱┃┃ -// ╰╯┃┃┣┻━┳┫┃╭┳━━╮┃┃╱╱╭━━┫╰━┳━━╮ -// ╱╱┃┃┃╭╮┣┫╰╯┫╭╮┃┃┃╱╭┫╭╮┃╭╮┃━━┫ -// ╱╱┃┃┃╭╮┃┃╭╮┫╰╯┃┃╰━╯┃╭╮┃╰╯┣━━┃ -// ╱╱╰╯╰╯╰┻┻╯╰┻━━╯╰━━━┻╯╰┻━━┻━━╯ -pragma solidity ^0.8.9; - -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. - * - * @title LibBridgeSignal - * @author dantaik - */ -library LibBridgeSignal { - using LibBlockHeader for BlockHeader; - - struct SignalProof { - BlockHeader header; - bytes proof; - } - - modifier onlyValidSenderAndSignal(address sender, bytes32 signal) { - require(sender != address(0), "B:sender"); - require(signal != 0, "B:signal"); - _; - } - - /** - * Send a signal by storing the key with a value of 1. - * - * @param sender The address sending the signal. - * @param signal The signal to send. - */ - function sendSignal( - address sender, - bytes32 signal - ) internal onlyValidSenderAndSignal(sender, signal) { - bytes32 k = _signalSlot(sender, signal); - assembly { - sstore(k, 1) - } - } - - /** - * Check if a signal has been sent (key stored with a value of 1). - * - * @param sender The sender of the signal. - * @param signal The signal to check. - */ - function isSignalSent( - address sender, - bytes32 signal - ) internal view onlyValidSenderAndSignal(sender, signal) returns (bool) { - bytes32 k = _signalSlot(sender, signal); - uint256 v; - assembly { - v := sload(k) - } - return v == 1; - } - - /** - * Check if signal has been received on the destination chain (current). - * - * @param resolver The address resolver. - * @param srcBridge Address of the source bridge where the bridge - * was initiated. - * @param sender Address of the sender of the signal - * (also should be srcBridge). - * @param signal The signal to check. - * @param proof The proof of the signal being sent on the source chain. - */ - function isSignalReceived( - AddressResolver resolver, - address srcBridge, - address sender, - bytes32 signal, - bytes calldata proof - ) internal view onlyValidSenderAndSignal(sender, signal) returns (bool) { - require(srcBridge != address(0), "B:srcBridge"); - - SignalProof memory sp = abi.decode(proof, (SignalProof)); - LibTrieProof.verify({ - stateRoot: sp.header.stateRoot, - addr: srcBridge, - key: _signalSlot(sender, signal), - value: bytes32(uint256(1)), - mkproof: sp.proof - }); - // get synced header hash of the header height specified in the proof - bytes32 syncedHeaderHash = IHeaderSync(resolver.resolve("taiko")) - .getSyncedHeader(sp.header.height); - // check header hash specified in the proof matches the current chain - return - syncedHeaderHash != 0 && - syncedHeaderHash == sp.header.hashBlockHeader(); - } - - function _signalSlot( - address sender, - bytes32 signal - ) private pure returns (bytes32) { - 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 index 1fd3b2d5a80..5a73f40930e 100644 --- a/packages/protocol/contracts/bridge/libs/LibBridgeStatus.sol +++ b/packages/protocol/contracts/bridge/libs/LibBridgeStatus.sol @@ -19,28 +19,28 @@ library LibBridgeStatus { FAILED } - event MessageStatusChanged(bytes32 indexed signal, MessageStatus status); + event MessageStatusChanged(bytes32 indexed msgHash, MessageStatus status); /** * @dev If messageStatus is same as in the messageStatus mapping, * does nothing. - * @param signal The messageHash of the message. + * @param msgHash The messageHash of the message. * @param status The status of the message. */ function updateMessageStatus( - bytes32 signal, + bytes32 msgHash, MessageStatus status ) internal { - if (getMessageStatus(signal) != status) { - _setMessageStatus(signal, status); - emit LibBridgeStatus.MessageStatusChanged(signal, status); + if (getMessageStatus(msgHash) != status) { + _setMessageStatus(msgHash, status); + emit LibBridgeStatus.MessageStatusChanged(msgHash, status); } } function getMessageStatus( - bytes32 signal + bytes32 msgHash ) internal view returns (MessageStatus) { - bytes32 slot = getMessageStatusSlot(signal); + bytes32 slot = getMessageStatusSlot(msgHash); uint256 value; assembly { value := sload(slot) @@ -49,13 +49,13 @@ library LibBridgeStatus { } function getMessageStatusSlot( - bytes32 signal + bytes32 msgHash ) internal pure returns (bytes32) { - return keccak256(abi.encodePacked("MESSAGE_STATUS", signal)); + return keccak256(abi.encodePacked("MESSAGE_STATUS", msgHash)); } - function _setMessageStatus(bytes32 signal, MessageStatus status) private { - bytes32 slot = getMessageStatusSlot(signal); + function _setMessageStatus(bytes32 msgHash, MessageStatus status) private { + bytes32 slot = getMessageStatusSlot(msgHash); uint256 value = uint256(status); assembly { sstore(slot, value) diff --git a/packages/protocol/contracts/common/AddressResolver.sol b/packages/protocol/contracts/common/AddressResolver.sol index 24a6d01e926..1f6783f9ff6 100644 --- a/packages/protocol/contracts/common/AddressResolver.sol +++ b/packages/protocol/contracts/common/AddressResolver.sol @@ -9,7 +9,6 @@ pragma solidity ^0.8.9; import "@openzeppelin/contracts/utils/Strings.sol"; - import "./IAddressManager.sol"; /** @@ -25,15 +24,7 @@ abstract contract AddressResolver { uint256[49] private __gap; modifier onlyFromNamed(string memory name) { - require(msg.sender == resolve(name), "AR:denied"); - _; - } - - modifier onlyFromNamedEither(string memory name1, string memory name2) { - require( - msg.sender == resolve(name1) || msg.sender == resolve(name2), - "AR:denied" - ); + require(msg.sender == resolve(name, false), "AR:denied"); _; } @@ -42,12 +33,14 @@ abstract contract AddressResolver { * * @dev This function will throw if the resolved address is `address(0)`. * @param name The name to resolve. + * @param allowZeroAddress True to allow zero address to be returned. * @return The name's corresponding address. */ function resolve( - string memory name + string memory name, + bool allowZeroAddress ) public view virtual returns (address payable) { - return _resolve(block.chainid, name); + return _resolve(block.chainid, name, allowZeroAddress); } /** @@ -56,13 +49,15 @@ abstract contract AddressResolver { * @dev This function will throw if the resolved address is `address(0)`. * @param chainId The chainId. * @param name The name to resolve. + * @param allowZeroAddress True to allow zero address to be returned. * @return The name's corresponding address. */ function resolve( uint256 chainId, - string memory name + string memory name, + bool allowZeroAddress ) public view virtual returns (address payable) { - return _resolve(chainId, name); + return _resolve(chainId, name, allowZeroAddress); } /** @@ -81,13 +76,20 @@ abstract contract AddressResolver { function _resolve( uint256 chainId, - string memory name - ) private view returns (address payable) { + string memory name, + bool allowZeroAddress + ) private view returns (address payable addr) { bytes memory key = abi.encodePacked( Strings.toString(chainId), ".", name ); - return payable(_addressManager.getAddress(string(key))); + addr = payable(_addressManager.getAddress(string(key))); + if (!allowZeroAddress) { + require( + addr != address(0), + string(abi.encodePacked("AR:zeroAddr:", key)) + ); + } } } diff --git a/packages/protocol/contracts/libs/LibTrieProof.sol b/packages/protocol/contracts/libs/LibTrieProof.sol index aa2c04f4a40..d786963fe77 100644 --- a/packages/protocol/contracts/libs/LibTrieProof.sol +++ b/packages/protocol/contracts/libs/LibTrieProof.sol @@ -30,23 +30,24 @@ library LibTrieProof { *********************/ /** - * Verifies that the value of a slot `key` in the storage tree of `addr` + * Verifies that the value of a slot in the storage tree of `addr` * is `value`. * * @param stateRoot The merkle root of state tree. * @param addr The contract address. - * @param key The slot in the contract. + * @param slot The slot in the contract. * @param value The value to be verified. * @param mkproof The proof obtained by encoding state proof and storage * proof. + * @return verified The verification result. */ function verify( bytes32 stateRoot, address addr, - bytes32 key, + bytes32 slot, bytes32 value, bytes calldata mkproof - ) public pure { + ) public pure returns (bool verified) { (bytes memory accountProof, bytes memory storageProof) = abi.decode( mkproof, (bytes, bytes) @@ -67,13 +68,11 @@ library LibTrieProof { accountState[ACCOUNT_FIELD_INDEX_STORAGE_HASH] ); - bool verified = LibSecureMerkleTrie.verifyInclusionProof( - abi.encodePacked(key), + verified = LibSecureMerkleTrie.verifyInclusionProof( + abi.encodePacked(slot), LibRLPWriter.writeBytes32(value), storageProof, storageRoot ); - - require(verified, "LTP:invalid storage proof"); } } diff --git a/packages/protocol/contracts/signal/ISignalService.sol b/packages/protocol/contracts/signal/ISignalService.sol new file mode 100644 index 00000000000..492984ac2e0 --- /dev/null +++ b/packages/protocol/contracts/signal/ISignalService.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT +// +// ╭━━━━╮╱╱╭╮╱╱╱╱╱╭╮╱╱╱╱╱╭╮ +// ┃╭╮╭╮┃╱╱┃┃╱╱╱╱╱┃┃╱╱╱╱╱┃┃ +// ╰╯┃┃┣┻━┳┫┃╭┳━━╮┃┃╱╱╭━━┫╰━┳━━╮ +// ╱╱┃┃┃╭╮┣┫╰╯┫╭╮┃┃┃╱╭┫╭╮┃╭╮┃━━┫ +// ╱╱┃┃┃╭╮┃┃╭╮┫╰╯┃┃╰━╯┃╭╮┃╰╯┣━━┃ +// ╱╱╰╯╰╯╰┻┻╯╰┻━━╯╰━━━┻╯╰┻━━┻━━╯ +pragma solidity ^0.8.9; + +interface ISignalService { + /** + * Send a signal by storing the key with a value of 1. + * + * @param signal The signal to send. + * @return storageSlot The slot in storage that this signal is persisted. + */ + function sendSignal(bytes32 signal) external returns (bytes32 storageSlot); + + /** + * Check if a signal has been sent (key stored with a value of 1). + * + * @param app The address that sent this message. + * @param signal The signal to check. + */ + function isSignalSent( + address app, + bytes32 signal + ) external view returns (bool); + + /** + * Check if signal has been received on the destination chain (current). + * + * @param srcChainId The source chain ID. + * @param app The address that sent this message. + * @param signal The signal to check. + * @param proof The proof of the signal being sent on the source chain. + */ + function isSignalReceived( + uint256 srcChainId, + address app, + bytes32 signal, + bytes calldata proof + ) external view returns (bool); +} diff --git a/packages/protocol/contracts/signal/SignalService.sol b/packages/protocol/contracts/signal/SignalService.sol new file mode 100644 index 00000000000..5d7c9802418 --- /dev/null +++ b/packages/protocol/contracts/signal/SignalService.sol @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: MIT +// +// ╭━━━━╮╱╱╭╮╱╱╱╱╱╭╮╱╱╱╱╱╭╮ +// ┃╭╮╭╮┃╱╱┃┃╱╱╱╱╱┃┃╱╱╱╱╱┃┃ +// ╰╯┃┃┣┻━┳┫┃╭┳━━╮┃┃╱╱╭━━┫╰━┳━━╮ +// ╱╱┃┃┃╭╮┣┫╰╯┫╭╮┃┃┃╱╭┫╭╮┃╭╮┃━━┫ +// ╱╱┃┃┃╭╮┃┃╭╮┫╰╯┃┃╰━╯┃╭╮┃╰╯┣━━┃ +// ╱╱╰╯╰╯╰┻┻╯╰┻━━╯╰━━━┻╯╰┻━━┻━━╯ +pragma solidity ^0.8.9; + +import "../common/EssentialContract.sol"; +import "../common/IHeaderSync.sol"; +import "../libs/LibBlockHeader.sol"; +import "../libs/LibTrieProof.sol"; +import "./ISignalService.sol"; + +contract SignalService is ISignalService, EssentialContract { + using LibBlockHeader for BlockHeader; + + struct SignalProof { + BlockHeader header; + bytes proof; + } + + /// @dev Initializer to be called after being deployed behind a proxy. + function init(address _addressManager) external initializer { + EssentialContract._init(_addressManager); + } + + function sendSignal(bytes32 signal) public returns (bytes32 storageSlot) { + require(signal != 0, "B:signal"); + + storageSlot = getSignalSlot(msg.sender, signal); + assembly { + sstore(storageSlot, 1) + } + } + + function isSignalSent( + address app, + bytes32 signal + ) public view returns (bool) { + require(app != address(0), "B:app"); + require(signal != 0, "B:signal"); + + bytes32 slot = getSignalSlot(app, signal); + uint256 value; + assembly { + value := sload(slot) + } + return value == 1; + } + + function isSignalReceived( + uint256 srcChainId, + address app, + bytes32 signal, + bytes calldata proof + ) public view returns (bool) { + require(srcChainId != block.chainid, "B:srcChainId"); + require(app != address(0), "B:app"); + require(signal != 0, "B:signal"); + + SignalProof memory sp = abi.decode(proof, (SignalProof)); + bytes32 syncedHeaderHash = IHeaderSync(resolve("taiko", false)) + .getSyncedHeader(sp.header.height); + + if ( + syncedHeaderHash == 0 || + syncedHeaderHash != sp.header.hashBlockHeader() + ) { + return false; + } + + return + LibTrieProof.verify({ + stateRoot: sp.header.stateRoot, + addr: resolve(srcChainId, "signal_service", false), + slot: getSignalSlot(app, signal), + value: bytes32(uint256(1)), + mkproof: sp.proof + }); + } + + function getSignalSlot( + address app, + bytes32 signal + ) public pure returns (bytes32) { + return keccak256(abi.encodePacked(app, signal)); + } +} diff --git a/packages/protocol/contracts/test/bridge/TestHeaderSync.sol b/packages/protocol/contracts/test/bridge/TestHeaderSync.sol index e48f34af138..2c4c90367e7 100644 --- a/packages/protocol/contracts/test/bridge/TestHeaderSync.sol +++ b/packages/protocol/contracts/test/bridge/TestHeaderSync.sol @@ -10,6 +10,8 @@ pragma solidity ^0.8.9; import "../../common/IHeaderSync.sol"; +// TODO(roger): remove this file. If you need extra functionality in +// the Bridge contract, create a TestBridge.sol contract instead. contract TestHeaderSync is IHeaderSync { bytes32 public headerHash; diff --git a/packages/protocol/contracts/test/bridge/libs/TestBadReceiver.sol b/packages/protocol/contracts/test/bridge/libs/TestBadReceiver.sol index 2a050934a03..873708a7ef2 100644 --- a/packages/protocol/contracts/test/bridge/libs/TestBadReceiver.sol +++ b/packages/protocol/contracts/test/bridge/libs/TestBadReceiver.sol @@ -8,6 +8,7 @@ // ╱╱╰╯╰╯╰┻┻╯╰┻━━╯╰━━━┻╯╰┻━━┻━━╯ pragma solidity ^0.8.9; +// TODO(roger): Merge this file into TestReceiver.sol. contract TestBadReceiver { receive() external payable { revert("can not send to this contract"); diff --git a/packages/protocol/contracts/test/bridge/libs/TestLibBridgeData.sol b/packages/protocol/contracts/test/bridge/libs/TestLibBridgeData.sol index b0fc7ad9ba2..320f24e565a 100644 --- a/packages/protocol/contracts/test/bridge/libs/TestLibBridgeData.sol +++ b/packages/protocol/contracts/test/bridge/libs/TestLibBridgeData.sol @@ -11,6 +11,8 @@ pragma solidity ^0.8.9; import "../../../bridge/libs/LibBridgeData.sol"; import "../../../bridge/libs/LibBridgeStatus.sol"; +// TODO(roger): remove this file. If you need extra functionality in +// the Bridge contract, create a TestBridge.sol contract instead. contract TestLibBridgeData { function updateMessageStatus( bytes32 signal, diff --git a/packages/protocol/contracts/test/bridge/libs/TestLibBridgeInvoke.sol b/packages/protocol/contracts/test/bridge/libs/TestLibBridgeInvoke.sol index abc17f426e5..2b640758b54 100644 --- a/packages/protocol/contracts/test/bridge/libs/TestLibBridgeInvoke.sol +++ b/packages/protocol/contracts/test/bridge/libs/TestLibBridgeInvoke.sol @@ -10,6 +10,8 @@ pragma solidity ^0.8.9; import "../../../bridge/libs/LibBridgeInvoke.sol"; +// TODO(roger): remove this file. If you need extra functionality in +// the Bridge contract, create a TestBridge.sol contract instead. contract TestLibBridgeInvoke { LibBridgeData.State public state; diff --git a/packages/protocol/contracts/test/bridge/libs/TestLibBridgeProcess.sol b/packages/protocol/contracts/test/bridge/libs/TestLibBridgeProcess.sol index 176bfe3c358..6714526f7fa 100644 --- a/packages/protocol/contracts/test/bridge/libs/TestLibBridgeProcess.sol +++ b/packages/protocol/contracts/test/bridge/libs/TestLibBridgeProcess.sol @@ -11,6 +11,8 @@ pragma solidity ^0.8.9; import "../../../common/EssentialContract.sol"; import "../../../bridge/libs/LibBridgeProcess.sol"; +// TODO(roger): remove this file. If you need extra functionality in +// the Bridge contract, create a TestBridge.sol contract instead. contract TestLibBridgeProcess is EssentialContract { LibBridgeData.State public state; diff --git a/packages/protocol/contracts/test/bridge/libs/TestLibBridgeRetry.sol b/packages/protocol/contracts/test/bridge/libs/TestLibBridgeRetry.sol index 8de54c2eaf5..03b8b577ca9 100644 --- a/packages/protocol/contracts/test/bridge/libs/TestLibBridgeRetry.sol +++ b/packages/protocol/contracts/test/bridge/libs/TestLibBridgeRetry.sol @@ -12,6 +12,8 @@ import "../../../common/EssentialContract.sol"; import "../../../bridge/libs/LibBridgeData.sol"; import "../../../bridge/libs/LibBridgeRetry.sol"; +// TODO(roger): remove this file. If you need extra functionality in +// the Bridge contract, create a TestBridge.sol contract instead. contract TestLibBridgeRetry is EssentialContract { LibBridgeData.State public state; diff --git a/packages/protocol/contracts/test/bridge/libs/TestLibBridgeSend.sol b/packages/protocol/contracts/test/bridge/libs/TestLibBridgeSend.sol index a9b21c2b83f..19cb4268931 100644 --- a/packages/protocol/contracts/test/bridge/libs/TestLibBridgeSend.sol +++ b/packages/protocol/contracts/test/bridge/libs/TestLibBridgeSend.sol @@ -11,6 +11,8 @@ pragma solidity ^0.8.9; import "../../../common/EssentialContract.sol"; import "../../../bridge/libs/LibBridgeSend.sol"; +// TODO(roger): remove this file. If you need extra functionality in +// the Bridge contract, create a TestBridge.sol contract instead. contract TestLibBridgeSend is EssentialContract { LibBridgeData.State public state; diff --git a/packages/protocol/contracts/test/bridge/libs/TestLibBridgeSignal.sol b/packages/protocol/contracts/test/bridge/libs/TestLibBridgeSignal.sol deleted file mode 100644 index 8c8e4a590e8..00000000000 --- a/packages/protocol/contracts/test/bridge/libs/TestLibBridgeSignal.sol +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: MIT -// -// ╭━━━━╮╱╱╭╮╱╱╱╱╱╭╮╱╱╱╱╱╭╮ -// ┃╭╮╭╮┃╱╱┃┃╱╱╱╱╱┃┃╱╱╱╱╱┃┃ -// ╰╯┃┃┣┻━┳┫┃╭┳━━╮┃┃╱╱╭━━┫╰━┳━━╮ -// ╱╱┃┃┃╭╮┣┫╰╯┫╭╮┃┃┃╱╭┫╭╮┃╭╮┃━━┫ -// ╱╱┃┃┃╭╮┃┃╭╮┫╰╯┃┃╰━╯┃╭╮┃╰╯┣━━┃ -// ╱╱╰╯╰╯╰┻┻╯╰┻━━╯╰━━━┻╯╰┻━━┻━━╯ -pragma solidity ^0.8.9; - -import "../../../bridge/libs/LibBridgeData.sol"; -import "../../../bridge/libs/LibBridgeSignal.sol"; - -contract TestLibBridgeSignal { - function sendSignal(address sender, bytes32 signal) public { - LibBridgeSignal.sendSignal(sender, signal); - } - - function isSignalSent( - address sender, - bytes32 signal - ) public view returns (bool) { - return LibBridgeSignal.isSignalSent(sender, signal); - } -} diff --git a/packages/protocol/contracts/test/libs/TestLibTrieProof.sol b/packages/protocol/contracts/test/libs/TestLibTrieProof.sol index 7cb94f91ca8..c4e987ef4bf 100644 --- a/packages/protocol/contracts/test/libs/TestLibTrieProof.sol +++ b/packages/protocol/contracts/test/libs/TestLibTrieProof.sol @@ -11,13 +11,19 @@ pragma solidity ^0.8.9; import "../../libs/LibTrieProof.sol"; contract TestLibTrieProof { + function writeStorageAt(bytes32 slot, bytes32 val) public { + assembly { + sstore(slot, val) + } + } + function verify( bytes32 stateRoot, address addr, - bytes32 key, + bytes32 slot, bytes32 value, bytes calldata mkproof - ) public pure { - LibTrieProof.verify(stateRoot, addr, key, value, mkproof); + ) public pure returns (bool) { + return LibTrieProof.verify(stateRoot, addr, slot, value, mkproof); } } diff --git a/packages/protocol/tasks/deploy_L1.ts b/packages/protocol/tasks/deploy_L1.ts index ecca9458826..19d93f10570 100644 --- a/packages/protocol/tasks/deploy_L1.ts +++ b/packages/protocol/tasks/deploy_L1.ts @@ -184,6 +184,18 @@ export async function deployContracts(hre: any) { ); } + // SignalService + const SignalService = await deploySignalSerive(hre, AddressManager.address); + + // Used by Bridge + await utils.waitTx( + hre, + await AddressManager.setAddress( + `${chainId}.signal_service`, + SignalService.address + ) + ); + // save deployments const deployments = { network, @@ -195,6 +207,7 @@ export async function deployContracts(hre: any) { { TkoToken: TkoToken.address }, { TaikoL1: TaikoL1.address }, { Bridge: Bridge.address }, + { SignalService: SignalService.address }, { TokenVault: TokenVault.address } ), }; @@ -227,18 +240,13 @@ async function deployBaseLibs(hre: any) { } async function deployBridge(hre: any, addressManager: string): Promise { - const libTrieProof = await utils.deployContract(hre, "LibTrieProof"); const libBridgeRetry = await utils.deployContract(hre, "LibBridgeRetry"); const libBridgeProcess = await utils.deployContract( hre, - "LibBridgeProcess", - { - LibTrieProof: libTrieProof.address, - } + "LibBridgeProcess" ); const Bridge = await utils.deployContract(hre, "Bridge", { - LibTrieProof: libTrieProof.address, LibBridgeRetry: libBridgeRetry.address, LibBridgeProcess: libBridgeProcess.address, }); @@ -258,3 +266,18 @@ async function deployTokenVault( return TokenVault; } + +async function deploySignalSerive( + hre: any, + addressManager: string +): Promise { + const libTrieProof = await utils.deployContract(hre, "LibTrieProof"); + + const SignalService = await utils.deployContract(hre, "SignalService", { + LibTrieProof: libTrieProof.address, + }); + + await utils.waitTx(hre, await SignalService.init(addressManager)); + + return SignalService; +} diff --git a/packages/protocol/test/bridge/Bridge.integration.test.ts b/packages/protocol/test/bridge/Bridge.integration.test.ts index 983f717bcda..3f3642b1548 100644 --- a/packages/protocol/test/bridge/Bridge.integration.test.ts +++ b/packages/protocol/test/bridge/Bridge.integration.test.ts @@ -4,8 +4,8 @@ import hre, { ethers } from "hardhat"; import { AddressManager, Bridge, + SignalService, TestHeaderSync, - TestLibBridgeData, } from "../../typechain"; import deployAddressManager from "../utils/addressManager"; import { @@ -14,11 +14,11 @@ import { sendAndProcessMessage, sendMessage, } from "../utils/bridge"; -import { randomBytes32 } from "../utils/bytes"; +// import { randomBytes32 } from "../utils/bytes"; import { Message } from "../utils/message"; import { getDefaultL2Signer, getL2Provider } from "../utils/provider"; import { Block, getBlockHeader } from "../utils/rpc"; -import { getSignalProof, getSignalSlot } from "../utils/signal"; +import { deploySignalService, getSignalProof } from "../utils/signal"; describe("integration:Bridge", function () { let owner: any; @@ -27,6 +27,8 @@ describe("integration:Bridge", function () { let srcChainId: number; let enabledDestChainId: number; let l2NonOwner: ethersLib.Signer; + let l1SignalService: SignalService; + let l2SignalService: SignalService; let l1Bridge: Bridge; let l2Bridge: Bridge; let m: Message; @@ -58,17 +60,37 @@ describe("integration:Bridge", function () { l2Signer ); + ({ signalService: l1SignalService } = await deploySignalService( + owner, + addressManager, + srcChainId + )); + + ({ signalService: l2SignalService } = await deploySignalService( + l2Signer, + l2AddressManager, + enabledDestChainId + )); + + await addressManager.setAddress( + `${enabledDestChainId}.signal_service`, + l2SignalService.address + ); + + await l2AddressManager.setAddress( + `${srcChainId}.signal_service`, + l1SignalService.address + ); + ({ bridge: l1Bridge } = await deployBridge( owner, addressManager, - enabledDestChainId, srcChainId )); ({ bridge: l2Bridge } = await deployBridge( l2Signer, l2AddressManager, - srcChainId, enabledDestChainId )); @@ -156,6 +178,7 @@ describe("integration:Bridge", function () { hre.ethers.provider, headerSync, m, + l1SignalService, l1Bridge, l2Bridge ); @@ -167,15 +190,7 @@ describe("integration:Bridge", function () { }); it("should throw if message signalproof is not valid", async function () { - const libData: TestLibBridgeData = await ( - await ethers.getContractFactory("TestLibBridgeData") - ).deploy(); - - const signal = await libData.hashMessage(m); - - const sender = l1Bridge.address; - - const key = getSignalSlot(sender, signal); + const msgHash = await l1Bridge.hashMessage(m); const { block, blockHeader } = await getBlockHeader( hre.ethers.provider ); @@ -184,40 +199,40 @@ describe("integration:Bridge", function () { const signalProof = await getSignalProof( hre.ethers.provider, - l1Bridge.address, - key, + l1SignalService.address, + await l1SignalService.getSignalSlot(l1Bridge.address, msgHash), block.number, blockHeader ); await expect( l2Bridge.processMessage(m, signalProof) - ).to.be.revertedWith("LTP:invalid storage proof"); + ).to.be.revertedWith("B:notReceived"); }); it("should throw if message has not been received", async function () { - const { signal, message } = await sendMessage(l1Bridge, m); + const { msgHash, message } = await sendMessage(l1Bridge, m); - expect(signal).not.to.be.eq(ethers.constants.HashZero); + expect(msgHash).not.to.be.eq(ethers.constants.HashZero); - const messageStatus = await l1Bridge.getMessageStatus(signal); + const messageStatus = await l1Bridge.getMessageStatus(msgHash); expect(messageStatus).to.be.eq(0); const sender = l1Bridge.address; - const key = getSignalSlot(sender, signal); - const { block, blockHeader } = await getBlockHeader( hre.ethers.provider ); await headerSync.setSyncedHeader(ethers.constants.HashZero); + const slot = await l1SignalService.getSignalSlot(sender, msgHash); + // get storageValue for the key const storageValue = await ethers.provider.getStorageAt( - l1Bridge.address, - key, + l1SignalService.address, + slot, block.number ); // make sure it equals 1 so our proof will pass @@ -227,8 +242,8 @@ describe("integration:Bridge", function () { const signalProof = await getSignalProof( hre.ethers.provider, - l1Bridge.address, - key, + l1SignalService.address, + slot, block.number, blockHeader ); @@ -239,31 +254,30 @@ describe("integration:Bridge", function () { }); it("processes a message when the signal has been verified from the sending chain", async () => { - const { signal, message } = await sendMessage(l1Bridge, m); + const { msgHash, message } = await sendMessage(l1Bridge, m); - expect(signal).not.to.be.eq(ethers.constants.HashZero); + expect(msgHash).not.to.be.eq(ethers.constants.HashZero); - const messageStatus = await l1Bridge.getMessageStatus(signal); + const messageStatus = await l1Bridge.getMessageStatus(msgHash); expect(messageStatus).to.be.eq(0); let block: Block; expect( ({ block } = await processMessage( + l1SignalService, l1Bridge, l2Bridge, - signal, + msgHash, hre.ethers.provider, headerSync, message )) ).to.emit(l2Bridge, "MessageStatusChanged"); - const key = getSignalSlot(l1Bridge.address, signal); - // get storageValue for the key const storageValue = await ethers.provider.getStorageAt( - l1Bridge.address, - key, + l1SignalService.address, + await l1SignalService.getSignalSlot(l1Bridge.address, msgHash), block.number ); // make sure it equals 1 so our proof will pass @@ -275,34 +289,31 @@ describe("integration:Bridge", function () { describe("isMessageSent()", function () { it("should return false, since no message was sent", async function () { - const libData = await ( - await ethers.getContractFactory("TestLibBridgeData") - ).deploy(); - const signal = await libData.hashMessage(m); + const msgHash = await l1Bridge.hashMessage(m); - expect(await l1Bridge.isMessageSent(signal)).to.be.eq(false); + expect(await l1Bridge.isMessageSent(msgHash)).to.be.eq(false); }); it("should return true if message was sent properly", async function () { - const { signal } = await sendMessage(l1Bridge, m); + const { msgHash } = await sendMessage(l1Bridge, m); - expect(signal).not.to.be.eq(ethers.constants.HashZero); + expect(msgHash).not.to.be.eq(ethers.constants.HashZero); - expect(await l1Bridge.isMessageSent(signal)).to.be.eq(true); + expect(await l1Bridge.isMessageSent(msgHash)).to.be.eq(true); }); }); describe("isMessageReceived()", function () { it("should throw if signal is not a bridge message; proof is invalid since sender != bridge.", async function () { - const signal = ethers.utils.hexlify(ethers.utils.randomBytes(32)); + const msgHash = ethers.utils.hexlify(ethers.utils.randomBytes(32)); - const tx = await l1Bridge.connect(owner).sendSignal(signal); + const tx = await l1SignalService.connect(owner).sendSignal(msgHash); await tx.wait(); const sender = owner.address; - const key = getSignalSlot(sender, signal); + const slot = await l1SignalService.getSignalSlot(sender, msgHash); const { block, blockHeader } = await getBlockHeader( hre.ethers.provider @@ -312,34 +323,34 @@ describe("integration:Bridge", function () { // get storageValue for the key const storageValue = await ethers.provider.getStorageAt( - l1Bridge.address, - key, + l1SignalService.address, + slot, block.number ); - // // make sure it equals 1 so we know sendSignal worked + // make sure it equals 1 so we know sendSignal worked expect(storageValue).to.be.eq( "0x0000000000000000000000000000000000000000000000000000000000000001" ); const signalProof = await getSignalProof( hre.ethers.provider, - l1Bridge.address, - key, + l1SignalService.address, + slot, block.number, blockHeader ); await expect( - l2Bridge.isMessageReceived(signal, srcChainId, signalProof) + l2Bridge.isMessageReceived(msgHash, srcChainId, signalProof) ).to.be.reverted; }); it("if message is valid and sent by the bridge it should return true", async function () { - const { signal } = await sendMessage(l1Bridge, m); - - const sender = l1Bridge.address; - - const key = getSignalSlot(sender, signal); + const { msgHash } = await sendMessage(l1Bridge, m); + const slot = await l1SignalService.getSignalSlot( + l1Bridge.address, + msgHash + ); const { block, blockHeader } = await getBlockHeader( hre.ethers.provider @@ -349,26 +360,26 @@ describe("integration:Bridge", function () { // get storageValue for the key const storageValue = await ethers.provider.getStorageAt( - l1Bridge.address, - key, + l1SignalService.address, + slot, block.number ); - // // make sure it equals 1 so we know sendMessage worked + // make sure it equals 1 so we know sendMessage worked expect(storageValue).to.be.eq( "0x0000000000000000000000000000000000000000000000000000000000000001" ); const signalProof = await getSignalProof( hre.ethers.provider, - l1Bridge.address, - key, + l1SignalService.address, + slot, block.number, blockHeader ); expect( await l2Bridge.isMessageReceived( - signal, + msgHash, srcChainId, signalProof ) @@ -376,6 +387,7 @@ describe("integration:Bridge", function () { }); }); + /* describe("isSignalReceived()", function () { it("should throw if sender == address(0)", async function () { const signal = randomBytes32(); @@ -383,7 +395,7 @@ describe("integration:Bridge", function () { const signalProof = ethers.constants.HashZero; await expect( - l2Bridge.isSignalReceived( + l2SignalService.isSignalReceived( signal, srcChainId, sender, @@ -398,7 +410,7 @@ describe("integration:Bridge", function () { const signalProof = ethers.constants.HashZero; await expect( - l2Bridge.isSignalReceived( + l2SignalService.isSignalReceived( signal, srcChainId, sender, @@ -410,13 +422,13 @@ describe("integration:Bridge", function () { it("should throw if calling from same layer", async function () { const signal = randomBytes32(); - const tx = await l1Bridge.connect(owner).sendSignal(signal); + const tx = await l1SignalService.connect(owner).sendSignal(signal); await tx.wait(); const sender = owner.address; - const key = getSignalSlot(sender, signal); + const key = await l1SignalService.getSignalSlot(sender, signal); const { block, blockHeader } = await getBlockHeader( hre.ethers.provider @@ -426,7 +438,7 @@ describe("integration:Bridge", function () { // get storageValue for the key const storageValue = await ethers.provider.getStorageAt( - l1Bridge.address, + l1SignalService.address, key, block.number ); @@ -437,14 +449,14 @@ describe("integration:Bridge", function () { const signalProof = await getSignalProof( hre.ethers.provider, - l1Bridge.address, + l1SignalService.address, key, block.number, blockHeader ); await expect( - l1Bridge.isSignalReceived( + l1SignalService.isSignalReceived( signal, srcChainId, sender, @@ -456,13 +468,13 @@ describe("integration:Bridge", function () { it("should return true and pass", async function () { const signal = ethers.utils.hexlify(ethers.utils.randomBytes(32)); - const tx = await l1Bridge.connect(owner).sendSignal(signal); + const tx = await l1SignalService.connect(owner).sendSignal(signal); await tx.wait(); const sender = owner.address; - const key = getSignalSlot(sender, signal); + const key = await l1SignalService.getSignalSlot(sender, signal); const { block, blockHeader } = await getBlockHeader( hre.ethers.provider @@ -472,7 +484,7 @@ describe("integration:Bridge", function () { // get storageValue for the key const storageValue = await ethers.provider.getStorageAt( - l1Bridge.address, + l1SignalService.address, key, block.number ); @@ -483,7 +495,7 @@ describe("integration:Bridge", function () { const signalProof = await getSignalProof( hre.ethers.provider, - l1Bridge.address, + l1SignalService.address, key, block.number, blockHeader @@ -491,7 +503,7 @@ describe("integration:Bridge", function () { // proving functionality; l2Bridge can check if l1Bridge receives a signal // allowing for dapp cross layer communication expect( - await l2Bridge.isSignalReceived( + await l2SignalService.isSignalReceived( signal, srcChainId, sender, @@ -500,4 +512,5 @@ describe("integration:Bridge", function () { ).to.be.eq(true); }); }); +*/ }); diff --git a/packages/protocol/test/bridge/Bridge.test.ts b/packages/protocol/test/bridge/Bridge.test.ts index dba75926406..40b93dd54ca 100644 --- a/packages/protocol/test/bridge/Bridge.test.ts +++ b/packages/protocol/test/bridge/Bridge.test.ts @@ -3,6 +3,7 @@ import { BigNumber } from "ethers"; import { ethers } from "hardhat"; import { AddressManager, Bridge, EtherVault } from "../../typechain"; import { deployBridge, sendMessage } from "../utils/bridge"; +import { deploySignalService } from "../utils/signal"; import { Message } from "../utils/message"; describe("Bridge", function () { @@ -27,10 +28,11 @@ describe("Bridge", function () { ).deploy(); await addressManager.init(); + await deploySignalService(owner, addressManager, srcChainId); + ({ bridge: l1Bridge, etherVault: l1EtherVault } = await deployBridge( owner, addressManager, - enabledDestChainId, srcChainId )); @@ -168,27 +170,28 @@ describe("Bridge", function () { }); }); - describe("sendSignal()", async function () { - it("throws when signal is empty", async function () { - await expect( - l1Bridge.connect(owner).sendSignal(ethers.constants.HashZero) - ).to.be.revertedWith("B:signal"); - }); - - it("sends signal, confirms it was sent", async function () { - const hash = - "0xf2e08f6b93d8cf4f37a3b38f91a8c37198095dde8697463ca3789e25218a8e9d"; - await expect(l1Bridge.connect(owner).sendSignal(hash)) - .to.emit(l1Bridge, "SignalSent") - .withArgs(owner.address, hash); - - const isSignalSent = await l1Bridge.isSignalSent( - owner.address, - hash - ); - expect(isSignalSent).to.be.eq(true); - }); - }); + // TODO(roger): move tests to SignalService's test file. + // describe("sendSignal()", async function () { + // it("throws when signal is empty", async function () { + // await expect( + // l1Bridge.connect(owner).sendSignal(ethers.constants.HashZero) + // ).to.be.revertedWith("B:signal"); + // }); + + // it("sends signal, confirms it was sent", async function () { + // const hash = + // "0xf2e08f6b93d8cf4f37a3b38f91a8c37198095dde8697463ca3789e25218a8e9d"; + // await expect(l1Bridge.connect(owner).sendSignal(hash)) + // .to.emit(l1Bridge, "SignalSent") + // .withArgs(owner.address, hash); + + // const isSignalSent = await l1Bridge.isSignalSent( + // owner.address, + // hash + // ); + // expect(isSignalSent).to.be.eq(true); + // }); + // }); describe("isDestChainEnabled()", function () { it("is disabled for unabled chainIds", async () => { @@ -222,31 +225,34 @@ describe("Bridge", function () { expect(messageStatus).to.be.eq(0); }); - it("returns for initiaized signal", async () => { - const message: Message = { - id: 1, - sender: owner.address, - srcChainId: 1, - destChainId: enabledDestChainId, - owner: owner.address, - to: nonOwner.address, - refundAddress: owner.address, - depositValue: 1, - callValue: 1, - processingFee: 1, - gasLimit: 100, - data: ethers.constants.HashZero, - memo: "", - }; - - const { signal } = await sendMessage(l1Bridge, message); - - expect(signal).not.to.be.eq(ethers.constants.HashZero); - - const messageStatus = await l1Bridge.getMessageStatus(signal); - - expect(messageStatus).to.be.eq(0); - }); + // TODO(jeff/roger): the following test is incorrect - getMessageStatus() + // shall be tested on the destination chain, not the source chain. + // + // it("returns for initiaized signal", async () => { + // const message: Message = { + // id: 1, + // sender: owner.address, + // srcChainId: 1, + // destChainId: enabledDestChainId, + // owner: owner.address, + // to: nonOwner.address, + // refundAddress: owner.address, + // depositValue: 1, + // callValue: 1, + // processingFee: 1, + // gasLimit: 100, + // data: ethers.constants.HashZero, + // memo: "", + // }; + + // const { signal } = await sendMessage(l1Bridge, message); + + // expect(signal).not.to.be.eq(ethers.constants.HashZero); + + // const messageStatus = await l1Bridge.getMessageStatus(signal); + + // expect(messageStatus).to.be.eq(0); + // }); }); describe("processMessage()", async function () { diff --git a/packages/protocol/test/bridge/libs/LibBridgeData.test.ts b/packages/protocol/test/bridge/libs/LibBridgeData.test.ts index c78bbd7634e..383f86c71a7 100644 --- a/packages/protocol/test/bridge/libs/LibBridgeData.test.ts +++ b/packages/protocol/test/bridge/libs/LibBridgeData.test.ts @@ -4,6 +4,8 @@ import { TestLibBridgeData } from "../../../typechain"; import { K_BRIDGE_MESSAGE } from "../../constants/messages"; import { Message, MessageStatus } from "../../utils/message"; +// TODO(roger): we should deprecate these test and test Bridge.sol +// as a whole. describe("LibBridgeData", function () { let owner: any; let nonOwner: any; diff --git a/packages/protocol/test/bridge/libs/LibBridgeInvoke.test.ts b/packages/protocol/test/bridge/libs/LibBridgeInvoke.test.ts index 7d116095fea..c705f66ed65 100644 --- a/packages/protocol/test/bridge/libs/LibBridgeInvoke.test.ts +++ b/packages/protocol/test/bridge/libs/LibBridgeInvoke.test.ts @@ -7,6 +7,8 @@ import { TestReceiver, } from "../../../typechain"; +// TODO(roger): we should deprecate these test and test Bridge.sol +// as a whole. describe("LibBridgeInvoke", function () { let owner: any; let nonOwner: any; diff --git a/packages/protocol/test/bridge/libs/LibBridgeProcess.test.ts b/packages/protocol/test/bridge/libs/LibBridgeProcess.test.ts index 4655bdb395c..8a77275ec03 100644 --- a/packages/protocol/test/bridge/libs/LibBridgeProcess.test.ts +++ b/packages/protocol/test/bridge/libs/LibBridgeProcess.test.ts @@ -13,13 +13,14 @@ import { TestLibBridgeProcess, } from "../../../typechain"; +// TODO(roger): we should deprecate these test and test Bridge.sol +// as a whole. describe("LibBridgeProcess", async function () { let owner: any; let nonOwner: any; let etherVaultOwner: any; let addressManager: AddressManager; let etherVault: EtherVault; - let libTrieLink; let libProcessLink; let libProcess: TestLibBridgeProcess; let testTaikoData: TestLibBridgeData; @@ -58,17 +59,8 @@ describe("LibBridgeProcess", async function () { value: ethers.utils.parseEther("10.0"), }); - libTrieLink = await (await ethers.getContractFactory("LibTrieProof")) - .connect(owner) - .deploy(); - await libTrieLink.deployed(); - libProcessLink = await ( - await ethers.getContractFactory("LibBridgeProcess", { - libraries: { - LibTrieProof: libTrieLink.address, - }, - }) + await ethers.getContractFactory("LibBridgeProcess") ) .connect(owner) .deploy(); diff --git a/packages/protocol/test/bridge/libs/LibBridgeRetry.test.ts b/packages/protocol/test/bridge/libs/LibBridgeRetry.test.ts index 43a474c227d..dbad717a3a6 100644 --- a/packages/protocol/test/bridge/libs/LibBridgeRetry.test.ts +++ b/packages/protocol/test/bridge/libs/LibBridgeRetry.test.ts @@ -17,6 +17,8 @@ import { } from "../../../typechain"; import deployAddressManager from "../../utils/addressManager"; +// TODO(roger): we should deprecate these test and test Bridge.sol +// as a whole. describe("LibBridgeRetry", function () { let owner: any; let nonOwner: any; diff --git a/packages/protocol/test/bridge/libs/LibBridgeSend.test.ts b/packages/protocol/test/bridge/libs/LibBridgeSend.test.ts index f40c61b6406..ca5d51a7117 100644 --- a/packages/protocol/test/bridge/libs/LibBridgeSend.test.ts +++ b/packages/protocol/test/bridge/libs/LibBridgeSend.test.ts @@ -6,7 +6,10 @@ import { EtherVault, } from "../../../typechain"; import { Message } from "../../utils/message"; +import { deploySignalService } from "../../utils/signal"; +// TODO(roger): we should deprecate these test and test Bridge.sol +// as a whole. describe("LibBridgeSend", function () { let owner: any; let nonOwner: any; @@ -27,6 +30,8 @@ describe("LibBridgeSend", function () { ).deploy(); await addressManager.init(); + await deploySignalService(owner, addressManager, blockChainId); + await addressManager.setAddress( `${enabledDestChainId}.bridge`, "0x0000000000000000000000000000000000000001" // dummy address so chain is "enabled" diff --git a/packages/protocol/test/bridge/libs/LibBridgeSignal.test.ts b/packages/protocol/test/bridge/libs/LibBridgeSignal.test.ts deleted file mode 100644 index 3282a0cffd7..00000000000 --- a/packages/protocol/test/bridge/libs/LibBridgeSignal.test.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { expect } from "chai"; -import { ethers } from "hardhat"; -import { TestLibBridgeData, TestLibBridgeSignal } from "../../../typechain"; -import { Message } from "../../utils/message"; - -describe("LibBridgeSignal", function () { - let owner: any; - let nonOwner: any; - let testMessage: Message; - let libData: TestLibBridgeData; - let libSignal: TestLibBridgeSignal; - - before(async function () { - [owner, nonOwner] = await ethers.getSigners(); - - testMessage = { - id: 1, - sender: owner.address, - srcChainId: 1, - destChainId: 2, - owner: owner.address, - to: nonOwner.address, - refundAddress: owner.address, - depositValue: 0, - callValue: 0, - processingFee: 0, - gasLimit: 0, - data: ethers.constants.HashZero, - memo: "", - }; - }); - - beforeEach(async function () { - libData = await ( - await ethers.getContractFactory("TestLibBridgeData") - ).deploy(); - - libSignal = await ( - await ethers.getContractFactory("TestLibBridgeSignal") - ).deploy(); - }); - - describe("sendSignal()", async function () { - it("throws when sender is zero address", async function () { - const signal = await libData.hashMessage(testMessage); - - await expect( - libSignal.sendSignal(ethers.constants.AddressZero, signal) - ).to.revertedWith("B:sender"); - }); - - it("throws when signal is zero", async function () { - await expect( - libSignal.sendSignal(owner.address, ethers.constants.HashZero) - ).to.be.revertedWith("B:signal"); - }); - }); - - describe("isSignalSent()", async function () { - it("properly sent signal should change storage value", async function () { - const signal = await libData.hashMessage(testMessage); - - await libSignal.sendSignal(owner.address, signal); - - expect(await libSignal.isSignalSent(owner.address, signal)).to.eq( - true - ); - }); - }); -}); diff --git a/packages/protocol/test/genesis/generate_genesis.test.sh b/packages/protocol/test/genesis/generate_genesis.test.sh index 97dd5112843..2e21f254fdc 100755 --- a/packages/protocol/test/genesis/generate_genesis.test.sh +++ b/packages/protocol/test/genesis/generate_genesis.test.sh @@ -62,7 +62,7 @@ echo '}' >> $GENESIS_JSON echo "" echo "Start docker compose network..." -docker compose -f $TESTNET_CONFIG down --remove-orphans &> /dev/null +docker compose -f $TESTNET_CONFIG down -v --remove-orphans &> /dev/null docker compose -f $TESTNET_CONFIG up -d echo "" @@ -70,4 +70,4 @@ echo "Start testing..." TEST_L2_GENESIS=true pnpm hardhat test --grep "Generate Genesis" -docker compose -f $TESTNET_CONFIG down +docker compose -f $TESTNET_CONFIG down -v diff --git a/packages/protocol/test/genesis/generate_genesis.test.ts b/packages/protocol/test/genesis/generate_genesis.test.ts index 60ab623950e..2306d032d58 100644 --- a/packages/protocol/test/genesis/generate_genesis.test.ts +++ b/packages/protocol/test/genesis/generate_genesis.test.ts @@ -131,6 +131,14 @@ action("Generate Genesis", function () { ); expect(taikoL2).to.be.equal(getContractAlloc("TaikoL2").address); + + const signalService = await addressManager.getAddress( + `${testConfig.chainId}.signal_service` + ); + + expect(signalService).to.be.equal( + getContractAlloc("SignalService").address + ); }); it("LibTxDecoder", async function () { @@ -277,6 +285,13 @@ action("Generate Genesis", function () { signer ); + await expect( + addressManager.setAddress( + "1.token_vault", + getContractAlloc("TokenVault").address + ) + ).not.to.be.reverted; + await expect( addressManager.setAddress( "1.bridge", @@ -324,6 +339,22 @@ action("Generate Genesis", function () { ).to.be.false; }); + it("SignalService", async function () { + const SignalService = new hre.ethers.Contract( + getContractAlloc("SignalService").address, + require("../../artifacts/contracts/signal/SignalService.sol/SignalService.json").abi, + signer + ); + + const owner = await SignalService.owner(); + + expect(owner).to.be.equal(testConfig.contractOwner); + + await expect( + SignalService.sendSignal(ethers.utils.randomBytes(32)) + ).not.to.reverted; + }); + it("ERC20", async function () { const ERC20 = new hre.ethers.Contract( getContractAlloc("TestERC20").address, diff --git a/packages/protocol/test/libs/LibTrieProof.test.ts b/packages/protocol/test/libs/LibTrieProof.test.ts index 434b6e3aa67..aed3860e935 100644 --- a/packages/protocol/test/libs/LibTrieProof.test.ts +++ b/packages/protocol/test/libs/LibTrieProof.test.ts @@ -4,8 +4,11 @@ import RLP from "rlp"; import { sendMessage } from "../utils/bridge"; import { Message } from "../utils/message"; import { EthGetProofResponse } from "../utils/rpc"; -import { getSignalSlot } from "../utils/signal"; +import { deploySignalService } from "../utils/signal"; +// TODO(roger): this test shall not use any file in contracts/bridge/*.sol +// Instead, it should use the `writeStorageAt` function to manipulate stroage +// values then verify the proof. describe("integration:LibTrieProof", function () { async function deployLibTrieProofFixture() { const libTrieProof = await ( @@ -39,23 +42,26 @@ describe("integration:LibTrieProof", function () { "0x0000000000000000000000000000000000000001" // dummy address so chain is "enabled" ); + const [owner] = await ethers.getSigners(); + + const { signalService } = await deploySignalService( + owner, + addressManager, + chainId + ); + const libBridgeRetry = await ( await ethers.getContractFactory("LibBridgeRetry") ).deploy(); const libBridgeProcess = await ( - await ethers.getContractFactory("LibBridgeProcess", { - libraries: { - LibTrieProof: libTrieProof.address, - }, - }) + await ethers.getContractFactory("LibBridgeProcess") ).deploy(); const BridgeFactory = await ethers.getContractFactory("Bridge", { libraries: { LibBridgeProcess: libBridgeProcess.address, LibBridgeRetry: libBridgeRetry.address, - LibTrieProof: libTrieProof.address, }, }); @@ -63,14 +69,23 @@ describe("integration:LibTrieProof", function () { await bridge.init(addressManager.address); - const [owner] = await ethers.getSigners(); - - return { owner, testLibTreProof, bridge, enabledDestChainId }; + return { + owner, + testLibTreProof, + signalService, + bridge, + enabledDestChainId, + }; } describe("verify()", function () { it("verifies", async function () { - const { owner, testLibTreProof, bridge, enabledDestChainId } = - await deployLibTrieProofFixture(); + const { + owner, + testLibTreProof, + signalService, + bridge, + enabledDestChainId, + } = await deployLibTrieProofFixture(); const { chainId } = await ethers.provider.getNetwork(); const srcChainId = chainId; @@ -91,17 +106,20 @@ describe("integration:LibTrieProof", function () { memo: "", }; - const { tx, signal } = await sendMessage(bridge, message); + const { tx, msgHash } = await sendMessage(bridge, message); await tx.wait(); - expect(signal).not.to.be.eq(ethers.constants.HashZero); + expect(msgHash).not.to.be.eq(ethers.constants.HashZero); - const messageStatus = await bridge.getMessageStatus(signal); + const messageStatus = await bridge.getMessageStatus(msgHash); expect(messageStatus).to.be.eq(0); - const key = getSignalSlot(bridge.address, signal); + const slot = await signalService.getSignalSlot( + bridge.address, + msgHash + ); // use this instead of ethers.provider.getBlock() beccause it doesnt have stateRoot // in the response @@ -111,20 +129,20 @@ describe("integration:LibTrieProof", function () { false, ]); - // get storageValue for the key + // get storageValue for the slot const storageValue = await ethers.provider.getStorageAt( - bridge.address, - key, + signalService.address, + slot, block.number ); // make sure it equals 1 so our proof will pass expect(storageValue).to.be.eq( "0x0000000000000000000000000000000000000000000000000000000000000001" ); - // rpc call to get the merkle proof what value is at key on the bridge contract + // rpc call to get the merkle proof what value is at slot on the bridge contract const proof: EthGetProofResponse = await ethers.provider.send( "eth_getProof", - [bridge.address, [key], block.hash] + [signalService.address, [slot], block.hash] ); const stateRoot = block.stateRoot; @@ -137,11 +155,11 @@ describe("integration:LibTrieProof", function () { RLP.encode(proof.storageProof[0].proof), ] ); - // proof verifies the storageValue at key is 1 + // proof verifies the storageValue at slot is 1 await testLibTreProof.verify( stateRoot, - bridge.address, - key, + signalService.address, + slot, "0x0000000000000000000000000000000000000000000000000000000000000001", encodedProof ); diff --git a/packages/protocol/test/signal/SignalService.test.ts b/packages/protocol/test/signal/SignalService.test.ts new file mode 100644 index 00000000000..41bf8c8a68b --- /dev/null +++ b/packages/protocol/test/signal/SignalService.test.ts @@ -0,0 +1,61 @@ +import { expect } from "chai"; +import { ethers } from "hardhat"; +import { AddressManager, SignalService } from "../../typechain"; +import { deploySignalService } from "../utils/signal"; +import deployAddressManager from "../utils/addressManager"; +// import {getBlockHeader } from "../utils/rpc"; + +// TODO(roger): convert to integration tests and add a test case for isSignalReceived. +describe("SignalService", function () { + let owner: any; + let addr1: any; + let addr2: any; + let signalService: SignalService; + + before(async () => { + [owner, addr1, addr2] = await ethers.getSigners(); + const { chainId } = await ethers.provider.getNetwork(); + + const addressManager: AddressManager = await deployAddressManager( + owner + ); + + ({ signalService } = await deploySignalService( + owner, + addressManager, + chainId + )); + }); + + describe("getSignalSlot()", function () { + it("should return different slots for same signal from different apps", async () => { + const signal = ethers.utils.keccak256( + ethers.utils.toUtf8Bytes("a random") + ); + const slot1 = await signalService.getSignalSlot( + addr1.address, + signal + ); + const slot2 = signalService.getSignalSlot(addr2.address, signal); + + await expect(slot1).to.be.not.equal(slot2); + }); + }); + + describe("isSignalSent()", function () { + it("should return false for unsent signal and true for sent signal", async () => { + const signal = ethers.utils.keccak256( + ethers.utils.toUtf8Bytes("another random") + ); + let isSent = await signalService.isSignalSent( + addr1.address, + signal + ); + await expect(isSent).to.be.equal(false); + + await signalService.connect(addr1).sendSignal(signal); + isSent = await signalService.isSignalSent(addr1.address, signal); + await expect(isSent).to.be.equal(true); + }); + }); +}); diff --git a/packages/protocol/test/utils/bridge.ts b/packages/protocol/test/utils/bridge.ts index 0e699b20bd9..bbaa168df1d 100644 --- a/packages/protocol/test/utils/bridge.ts +++ b/packages/protocol/test/utils/bridge.ts @@ -3,32 +3,21 @@ import { ethers as hardhatEthers } from "hardhat"; import { AddressManager, Bridge, + SignalService, EtherVault, - LibTrieProof, TestHeaderSync, } from "../../typechain"; import { Message } from "./message"; import { Block, BlockHeader, getBlockHeader } from "./rpc"; -import { getSignalProof, getSignalSlot } from "./signal"; +import { getSignalProof } from "./signal"; async function deployBridge( signer: Signer, addressManager: AddressManager, - destChain: number, - srcChain: number + chainId: number ): Promise<{ bridge: Bridge; etherVault: EtherVault }> { - const libTrieProof: LibTrieProof = await ( - await hardhatEthers.getContractFactory("LibTrieProof") - ) - .connect(signer) - .deploy(); - const libBridgeProcess = await ( - await hardhatEthers.getContractFactory("LibBridgeProcess", { - libraries: { - LibTrieProof: libTrieProof.address, - }, - }) + await hardhatEthers.getContractFactory("LibBridgeProcess") ) .connect(signer) .deploy(); @@ -43,7 +32,6 @@ async function deployBridge( libraries: { LibBridgeProcess: libBridgeProcess.address, LibBridgeRetry: libBridgeRetry.address, - LibTrieProof: libTrieProof.address, }, }); @@ -64,7 +52,7 @@ async function deployBridge( await etherVault.connect(signer).authorize(await signer.getAddress(), true); await addressManager.setAddress( - `${srcChain}.ether_vault`, + `${chainId}.ether_vault`, etherVault.address ); @@ -74,7 +62,7 @@ async function deployBridge( gasLimit: 1000000, }); - await addressManager.setAddress(`${destChain}.bridge`, bridge.address); + await addressManager.setAddress(`${chainId}.bridge`, bridge.address); return { bridge, etherVault }; } @@ -84,7 +72,7 @@ async function sendMessage( m: Message ): Promise<{ bridge: Bridge; - signal: any; + msgHash: string; messageSentEvent: any; message: Message; tx: ethers.ContractTransaction; @@ -99,12 +87,14 @@ async function sendMessage( const [messageSentEvent] = receipt.events as any as Event[]; - const { signal, message } = (messageSentEvent as any).args; + const { msgHash, message } = (messageSentEvent as any).args; - return { bridge, messageSentEvent, signal, message, tx }; + return { bridge, messageSentEvent, msgHash, message, tx }; } +// Process a L1-to-L1 message async function processMessage( + l1SignalService: SignalService, l1Bridge: Bridge, l2Bridge: Bridge, signal: string, @@ -119,7 +109,7 @@ async function processMessage( }> { const sender = l1Bridge.address; - const key = getSignalSlot(sender, signal); + const slot = await l1SignalService.getSignalSlot(sender, signal); const { block, blockHeader } = await getBlockHeader(provider); @@ -127,8 +117,8 @@ async function processMessage( const signalProof = await getSignalProof( provider, - l1Bridge.address, - key, + l1SignalService.address, + slot, block.number, blockHeader ); @@ -141,24 +131,26 @@ async function sendAndProcessMessage( provider: ethers.providers.JsonRpcProvider, headerSync: TestHeaderSync, m: Message, + l1SignalService: SignalService, l1Bridge: Bridge, l2Bridge: Bridge ): Promise<{ tx: ethers.ContractTransaction; message: Message; - signal: string; + msgHash: string; signalProof: string; }> { - const { signal, message } = await sendMessage(l1Bridge, m); + const { msgHash, message } = await sendMessage(l1Bridge, m); const { tx, signalProof } = await processMessage( + l1SignalService, l1Bridge, l2Bridge, - signal, + msgHash, provider, headerSync, message ); - return { tx, signal, message, signalProof }; + return { tx, msgHash, message, signalProof }; } export { deployBridge, sendMessage, processMessage, sendAndProcessMessage }; diff --git a/packages/protocol/test/utils/signal.ts b/packages/protocol/test/utils/signal.ts index c36a3052a17..b535ee1b2c8 100644 --- a/packages/protocol/test/utils/signal.ts +++ b/packages/protocol/test/utils/signal.ts @@ -1,26 +1,52 @@ -import { ethers } from "ethers"; +import { ethers, Signer } from "ethers"; import RLP from "rlp"; +import { ethers as hardhatEthers } from "hardhat"; import { BlockHeader, EthGetProofResponse } from "./rpc"; +import { AddressManager, SignalService, LibTrieProof } from "../../typechain"; -function getSignalSlot(sender: string, signal: any) { - return ethers.utils.keccak256( - ethers.utils.solidityPack( - ["string", "address", "bytes32"], - ["SIGNAL", sender, signal] - ) +async function deploySignalService( + signer: Signer, + addressManager: AddressManager, + srcChain: number +): Promise<{ signalService: SignalService }> { + const libTrieProof: LibTrieProof = await ( + await hardhatEthers.getContractFactory("LibTrieProof") + ) + .connect(signer) + .deploy(); + + const SignalServiceFactory = await hardhatEthers.getContractFactory( + "SignalService", + { + libraries: { + LibTrieProof: libTrieProof.address, + }, + } + ); + + const signalService: SignalService = await SignalServiceFactory.connect( + signer + ).deploy(); + + await signalService.connect(signer).init(addressManager.address); + + await addressManager.setAddress( + `${srcChain}.signal_service`, + signalService.address ); + return { signalService }; } async function getSignalProof( provider: ethers.providers.JsonRpcProvider, contractAddress: string, - key: string, + slot: string, blockNumber: number, blockHeader: BlockHeader ) { const proof: EthGetProofResponse = await provider.send("eth_getProof", [ contractAddress, - [key], + [slot], blockNumber, ]); @@ -43,4 +69,4 @@ async function getSignalProof( return signalProof; } -export { getSignalSlot, getSignalProof }; +export { deploySignalService, getSignalProof }; diff --git a/packages/protocol/utils/generate_genesis/taikoL2.ts b/packages/protocol/utils/generate_genesis/taikoL2.ts index d7e4eb3ccd8..1b9df477874 100644 --- a/packages/protocol/utils/generate_genesis/taikoL2.ts +++ b/packages/protocol/utils/generate_genesis/taikoL2.ts @@ -134,6 +134,10 @@ async function generateContractConfigs( ARTIFACTS_PATH, "./bridge/EtherVault.sol/EtherVault.json" )), + SignalService: require(path.join( + ARTIFACTS_PATH, + "./signal/SignalService.sol/SignalService.json" + )), }; const addressMap: any = {}; @@ -141,33 +145,54 @@ async function generateContractConfigs( for (const [contractName, artifact] of Object.entries(contractArtifacts)) { let bytecode = (artifact as any).bytecode; - if (contractName === "TaikoL2") { - if (!addressMap.LibTxDecoder) { - throw new Error("LibTxDecoder not initialized"); - } + switch (contractName) { + case "TaikoL2": + if (!addressMap.LibTxDecoder) { + throw new Error("LibTxDecoder not initialized"); + } - bytecode = linkContractLibs(contractArtifacts.TaikoL2, addressMap); - } else if (contractName === "LibBridgeProcess") { - if (!addressMap.LibTrieProof) { - throw new Error("LibTrieProof not initialized"); - } - - bytecode = linkContractLibs( - contractArtifacts.LibBridgeProcess, - addressMap - ); - } else if (contractName === "Bridge") { - if ( - !addressMap.LibTrieProof || - !addressMap.LibBridgeRetry || - !addressMap.LibBridgeProcess - ) { - throw new Error( - "LibTrieProof/LibBridgeRetry/LibBridgeProcess not initialized" + bytecode = linkContractLibs( + contractArtifacts.TaikoL2, + addressMap ); - } - - bytecode = linkContractLibs(contractArtifacts.Bridge, addressMap); + break; + case "LibBridgeProcess": + if (!addressMap.LibTrieProof) { + throw new Error("LibTrieProof not initialized"); + } + + bytecode = linkContractLibs( + contractArtifacts.LibBridgeProcess, + addressMap + ); + break; + case "Bridge": + if ( + !addressMap.LibBridgeRetry || + !addressMap.LibBridgeProcess + ) { + throw new Error( + "LibBridgeRetry/LibBridgeProcess not initialized" + ); + } + + bytecode = linkContractLibs( + contractArtifacts.Bridge, + addressMap + ); + break; + case "SignalService": + if (!addressMap.LibTrieProof) { + throw new Error("LibTrieProof not initialized"); + } + + bytecode = linkContractLibs( + contractArtifacts.SignalService, + addressMap + ); + break; + default: + break; } if ( @@ -242,6 +267,10 @@ async function generateContractConfigs( ["string"], [`${chainId}.ether_vault`] )}`]: addressMap.EtherVault, + [`${ethers.utils.solidityKeccak256( + ["string"], + [`${chainId}.signal_service`] + )}`]: addressMap.SignalService, }, }, }, @@ -322,6 +351,24 @@ async function generateContractConfigs( authorizedAddrs: { [`${addressMap.Bridge}`]: true }, }, }, + SignalService: { + address: addressMap.SignalService, + deployedBytecode: linkContractLibs( + contractArtifacts.SignalService, + addressMap + ), + variables: { + // initializer + _initialized: 1, + _initializing: false, + // ReentrancyGuardUpgradeable + _status: 1, // _NOT_ENTERED + // OwnableUpgradeable + _owner: contractOwner, + // AddressResolver + _addressManager: addressMap.AddressManager, + }, + }, }; }