diff --git a/script/test/ORMPPort.t.sol b/script/test/ORMPPort.t.sol index 5b5eeaa..a28a394 100644 --- a/script/test/ORMPPort.t.sol +++ b/script/test/ORMPPort.t.sol @@ -36,18 +36,6 @@ contract ORMPPortTest is Test { ormpPort.setURI("https://test.uri"); } - function testSetAppConfig() public { - vm.prank(dao); - ormpPort.setSenderConfig(address(0x2), address(0x3)); - UC memory uc = IORMP(vm.envOr("ORMP_ADDRESS", address(ormpProtocol))).getAppConfig(address(ormpPort)); - assertEq(uc.oracle, address(0x2)); - assertEq(uc.relayer, address(0x3)); - // Cannot - vm.expectRevert(bytes("Ownable: caller is not the owner")); - vm.prank(address(0)); - ormpPort.setSenderConfig(address(0x2), address(0x3)); - } - function testSetPort() public { // From port vm.prank(dao); diff --git a/src/ports/ORMPPort.sol b/src/ports/ORMPPort.sol index 9ba25f4..6b8a20d 100644 --- a/src/ports/ORMPPort.sol +++ b/src/ports/ORMPPort.sol @@ -32,22 +32,6 @@ contract ORMPPort is Ownable2Step, UpgradeableApplication, BaseMessagePort, Port _setURI(uri); } - function setSender(address ormp) external onlyOwner { - _setSender(ormp); - } - - function setRecver(address ormp) external onlyOwner { - _setRecver(ormp); - } - - function setSenderConfig(address oracle, address relayer) external onlyOwner { - _setSenderConfig(oracle, relayer); - } - - function setRecverConfig(address oracle, address relayer) external onlyOwner { - _setRecverConfig(oracle, relayer); - } - function setToPort(uint256 _toChainId, address _toPortAddress) external onlyOwner { _setToPort(_toChainId, _toPortAddress); } diff --git a/src/ports/ORMPUpgradeableAndRetryablePort.sol b/src/ports/ORMPUpgradeableAndRetryablePort.sol new file mode 100644 index 0000000..74d5241 --- /dev/null +++ b/src/ports/ORMPUpgradeableAndRetryablePort.sol @@ -0,0 +1,153 @@ +// This file is part of Darwinia. +// Copyright (C) 2018-2023 Darwinia Network +// SPDX-License-Identifier: GPL-3.0 +// +// Darwinia is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Darwinia is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Darwinia. If not, see . + +pragma solidity ^0.8.17; + +import "./base/BaseMessagePort.sol"; +import "./base/PortLookup.sol"; +import "ORMP/src/interfaces/IORMP.sol"; +import "ORMP/src/user/UpgradeableApplication.sol"; +import "@openzeppelin/contracts/access/Ownable2Step.sol"; +import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; + +contract ORMPUpgradeableAndRetryablePort is + Ownable2Step, + UpgradeableApplication, + BaseMessagePort, + PortLookup, + ReentrancyGuard +{ + /// msgHash => isFailed + mapping(bytes32 => bool) public fails; + + event MessageDispatchedFailure( + bytes32 indexed msgHash, bytes32 msgId, uint256 fromChainId, address fromDapp, address toDapp, bytes message + ); + event RetryFailedMessage(bytes32 indexed msgHash, bool result); + event ClearFailedMessage(bytes32 indexed msgHash); + + constructor(address dao, address ormp, string memory name) UpgradeableApplication(ormp) BaseMessagePort(name) { + _transferOwnership(dao); + } + + function setURI(string calldata uri) external onlyOwner { + _setURI(uri); + } + + function setSender(address ormp) external onlyOwner { + _setSender(ormp); + } + + function setRecver(address ormp) external onlyOwner { + _setRecver(ormp); + } + + function setSenderConfig(address oracle, address relayer) external onlyOwner { + _setSenderConfig(oracle, relayer); + } + + function setRecverConfig(address oracle, address relayer) external onlyOwner { + _setRecverConfig(oracle, relayer); + } + + function setToPort(uint256 _toChainId, address _toPortAddress) external onlyOwner { + _setToPort(_toChainId, _toPortAddress); + } + + function setFromPort(uint256 _fromChainId, address _fromPortAddress) external onlyOwner { + _setFromPort(_fromChainId, _fromPortAddress); + } + + function _send(address fromDapp, uint256 toChainId, address toDapp, bytes calldata message, bytes calldata params) + internal + override + { + (uint256 gasLimit, address refund, bytes memory ormpParams) = abi.decode(params, (uint256, address, bytes)); + bytes memory encoded = + abi.encodeWithSelector(ORMPUpgradeableAndRetryablePort.recv.selector, fromDapp, toDapp, message); + IORMP(sender).send{value: msg.value}( + toChainId, _checkedToPort(toChainId), gasLimit, encoded, refund, ormpParams + ); + } + + function recv(address fromDapp, address toDapp, bytes calldata message) external payable onlyORMP { + uint256 fromChainId = _fromChainId(); + require(_xmsgSender() == _checkedFromPort(fromChainId), "!auth"); + _recv(fromChainId, fromDapp, toDapp, message); + } + + function retryFailedMessage( + bytes32 msgId, + uint256 fromChainId, + address fromDapp, + address toDapp, + bytes calldata message + ) external payable nonReentrant returns (bool success) { + bytes32 msgHash = hashInfo(msgId, fromChainId, fromDapp, toDapp, message); + require(fails[msgHash] == true, "!failed"); + (success,) = toDapp.call{value: msg.value}(abi.encodePacked(message, fromChainId, fromDapp)); + if (success) { + delete fails[msgHash]; + } + emit RetryFailedMessage(msgHash, success); + } + + function clearFailedMessage( + bytes32 msgId, + uint256 fromChainId, + address fromDapp, + address toDapp, + bytes calldata message + ) external { + bytes32 msgHash = hashInfo(msgId, fromChainId, fromDapp, toDapp, message); + require(fails[msgHash] == true, "!failed"); + require(toDapp == msg.sender, "!auth"); + delete fails[msgHash]; + emit ClearFailedMessage(msgHash); + } + + /// NOTE: Due to gas-related issues, it is not guaranteed that failed messages will always be stored. + function _recv(uint256 fromChainId, address fromDapp, address toDapp, bytes memory message) internal override { + (bool success,) = toDapp.call{value: msg.value}(abi.encodePacked(message, fromChainId, fromDapp)); + if (!success) { + bytes32 msgId = _messageId(); + bytes32 msgHash = hashInfo(msgId, fromChainId, fromDapp, toDapp, message); + fails[msgHash] = true; + emit MessageDispatchedFailure(msgHash, msgId, fromChainId, fromDapp, toDapp, message); + } + } + + function hashInfo(bytes32 msgId, uint256 fromChainId, address fromDapp, address toDapp, bytes memory message) + internal + pure + returns (bytes32) + { + return keccak256(abi.encode(msgId, fromChainId, fromDapp, toDapp, message)); + } + + function fee(uint256 toChainId, address toDapp, bytes calldata message, bytes calldata params) + external + view + override + returns (uint256) + { + (uint256 gasLimit,, bytes memory ormpParams) = abi.decode(params, (uint256, address, bytes)); + bytes memory encoded = + abi.encodeWithSelector(ORMPUpgradeableAndRetryablePort.recv.selector, msg.sender, toDapp, message); + return IORMP(sender).fee(toChainId, address(this), gasLimit, encoded, ormpParams); + } +} diff --git a/src/ports/base/BaseMessagePort.sol b/src/ports/base/BaseMessagePort.sol index c678b06..7873210 100644 --- a/src/ports/base/BaseMessagePort.sol +++ b/src/ports/base/BaseMessagePort.sol @@ -48,15 +48,10 @@ abstract contract BaseMessagePort is IMessagePort, PortMetadata { /// @param fromDapp The message sender in source chain. /// @param toDapp The message receiver in dest chain. /// @param message The message body. - function _recv(uint256 fromChainId, address fromDapp, address toDapp, bytes memory message) - internal - returns (bytes memory) - { + function _recv(uint256 fromChainId, address fromDapp, address toDapp, bytes memory message) internal virtual { (bool success, bytes memory returndata) = toDapp.call{value: msg.value}(abi.encodePacked(message, fromChainId, fromDapp)); - if (success) { - return returndata; - } else { + if (!success) { revert MessageFailure(returndata); } }