diff --git a/helix-contract/contracts/interfaces/IMessager.sol b/helix-contract/contracts/interfaces/IMessager.sol index 34d3aa12..dcdc830d 100644 --- a/helix-contract/contracts/interfaces/IMessager.sol +++ b/helix-contract/contracts/interfaces/IMessager.sol @@ -14,5 +14,5 @@ interface ILowLevelMessageReceiver { interface IMessageId { function latestSentMessageId() external view returns(bytes32); function latestRecvMessageId() external view returns(bytes32); - function messageDelivered(bytes32 messageId) external view returns(bool); + function messageDeliveredOrSlashed(bytes32 messageId) external view returns(bool); } diff --git a/helix-contract/contracts/mapping-token/v3/base/xTokenBridgeBase.sol b/helix-contract/contracts/mapping-token/v3/base/xTokenBridgeBase.sol index 04cc8919..a7d0a499 100644 --- a/helix-contract/contracts/mapping-token/v3/base/xTokenBridgeBase.sol +++ b/helix-contract/contracts/mapping-token/v3/base/xTokenBridgeBase.sol @@ -92,7 +92,7 @@ contract xTokenBridgeBase is Initializable, Pausable, AccessController, DailyLim function _assertMessageIsDelivered(uint256 _remoteChainId, bytes32 _transferId) view internal { MessagerService memory service = messagers[_remoteChainId]; require(service.receiveService != address(0), "bridge not configured"); - require(IMessageId(service.receiveService).messageDelivered(_transferId), "message not delivered"); + require(IMessageId(service.receiveService).messageDeliveredOrSlashed(_transferId), "message not delivered"); } // the latest received message id diff --git a/helix-contract/contracts/messagers/MsglineMessager.sol b/helix-contract/contracts/messagers/MsglineMessager.sol index 4c73c228..20b3a11e 100644 --- a/helix-contract/contracts/messagers/MsglineMessager.sol +++ b/helix-contract/contracts/messagers/MsglineMessager.sol @@ -5,6 +5,9 @@ import "../utils/AccessController.sol"; import "../interfaces/IMessageLine.sol"; contract MsglineMessager is Application, AccessController { + // expire time = 1 hour + uint256 constant public SLASH_EXPIRE_TIME = 3600; + IMessageLine public immutable msgline; struct RemoteMessager { @@ -21,6 +24,9 @@ contract MsglineMessager is Application, AccessController { mapping(bytes32=>address) public remoteAppReceivers; mapping(bytes32=>address) public remoteAppSenders; + // transferId => timestamp + mapping(bytes32=>uint256) public slashTransferIds; + event CallerUnMatched(uint256 srcAppChainId, bytes32 transferId, address srcAppAddress); event CallResult(uint256 srcAppChainId, bytes32 transferId, bool result); @@ -84,6 +90,10 @@ contract MsglineMessager is Application, AccessController { bytes32 key = keccak256(abi.encodePacked(srcChainId, _localAppAddress)); bytes32 transferId = latestRecvMessageId(); + if (_messageSlashed(transferId)) { + return; + } + // check remote appSender if (_remoteAppAddress != remoteAppSenders[key]) { emit CallerUnMatched(_srcAppChainId, transferId, _remoteAppAddress); @@ -94,6 +104,15 @@ contract MsglineMessager is Application, AccessController { emit CallResult(_srcAppChainId, transferId, success); } + function slashMessage(bytes32 transferId) external { + slashTransferIds[transferId] = block.timestamp; + } + + function _messageSlashed(bytes32 transferId) internal view returns(bool) { + uint256 slashTimestamp = slashTransferIds[transferId]; + return slashTimestamp > 0 && slashTimestamp + SLASH_EXPIRE_TIME < block.timestamp; + } + function latestSentMessageId() external view returns(bytes32) { return msgline.sentMessageId(); } @@ -102,8 +121,8 @@ contract MsglineMessager is Application, AccessController { return msgline.recvMessageId(); } - function messageDelivered(bytes32 messageId) external view returns(bool) { - return msgline.dones(messageId); + function messageDeliveredOrSlashed(bytes32 transferId) external view returns(bool) { + return msgline.dones(transferId) || _messageSlashed(transferId); } function messagePayload(address _from, address _to, bytes memory _message) public view returns(bytes memory) {