From 0c458d339c7f07e911711f68bfa1c70fb916a062 Mon Sep 17 00:00:00 2001 From: xiaoch05 Date: Wed, 20 Dec 2023 16:08:32 +0800 Subject: [PATCH] base transfer & refund --- .../mapping-token/v3/base/xTokenBacking.sol | 12 +++----- .../v3/base/xTokenBridgeBase.sol | 28 +++++++++++++++++++ .../mapping-token/v3/base/xTokenIssuing.sol | 9 ++---- helix-contract/test/6_test_xtoken_v3.js | 4 +-- 4 files changed, 36 insertions(+), 17 deletions(-) diff --git a/helix-contract/contracts/mapping-token/v3/base/xTokenBacking.sol b/helix-contract/contracts/mapping-token/v3/base/xTokenBacking.sol index 8f3366c..67d0088 100644 --- a/helix-contract/contracts/mapping-token/v3/base/xTokenBacking.sol +++ b/helix-contract/contracts/mapping-token/v3/base/xTokenBacking.sol @@ -54,6 +54,8 @@ contract xTokenBacking is xTokenBridgeBase { // We use nonce to ensure that messages are not duplicated // especially in reorg scenarios, the destination chain use nonce to filter out duplicate deliveries. + // nonce is user-defined, there is no requirement that it must not be repeated. + // But the transferId generated must not be repeated. function lockAndRemoteIssuing( uint256 _remoteChainId, address _originalToken, @@ -124,8 +126,7 @@ contract xTokenBacking is xTokenBridgeBase { expendDailyLimit(_originalToken, _amount); bytes32 transferId = getTransferId(_nonce, block.chainid, _originalToken, _originSender, _recipient, _amount); - require(filledTransfers[transferId] == TRANSFER_UNFILLED, "message has been accepted"); - filledTransfers[transferId] = TRANSFER_DELIVERED; + _handleTransfer(transferId); // native token do not use guard if (address(0) == _originalToken) { @@ -182,12 +183,7 @@ contract xTokenBacking is xTokenBridgeBase { ) external payable { require(_originalSender == msg.sender || _recipient == msg.sender || dao == msg.sender, "invalid msgSender"); bytes32 transferId = getTransferId(_nonce, _remoteChainId, _originalToken, _originalSender, _recipient, _amount); - // must not exist in successful issue list - uint256 filledTransfer = filledTransfers[transferId]; - require(filledTransfer != TRANSFER_DELIVERED, "success message can't refund for failed"); - if (filledTransfer != TRANSFER_REFUNDED) { - filledTransfers[transferId] = TRANSFER_REFUNDED; - } + _requestRefund(transferId); bytes memory unlockForFailed = encodeIssuingForUnlockFailureFromRemote( _originalToken, _originalSender, diff --git a/helix-contract/contracts/mapping-token/v3/base/xTokenBridgeBase.sol b/helix-contract/contracts/mapping-token/v3/base/xTokenBridgeBase.sol index ecc1282..b63b76b 100644 --- a/helix-contract/contracts/mapping-token/v3/base/xTokenBridgeBase.sol +++ b/helix-contract/contracts/mapping-token/v3/base/xTokenBridgeBase.sol @@ -103,11 +103,18 @@ contract xTokenBridgeBase is Initializable, Pausable, AccessController, DailyLim messageId = IMessageId(service.sendService).latestSentMessageId(); } + // request a cross-chain transfer + // 1. lock and remote issue + // 2. burn and remote unlock + // save the transferId if not exist, else revert function _requestTransfer(bytes32 _transferId) internal { require(requestInfos[_transferId].isRequested == false, "request exist"); requestInfos[_transferId].isRequested = true; } + // receive a cross-chain refund request + // 1. request must be exist + // 2. can't repeat function _handleRefund(bytes32 _transferId) internal { RequestInfo memory requestInfo = requestInfos[_transferId]; require(requestInfo.isRequested == true, "request not exist"); @@ -115,6 +122,27 @@ contract xTokenBridgeBase is Initializable, Pausable, AccessController, DailyLim requestInfos[_transferId].hasRefundForFailed = true; } + // receive a cross-chain request + // must not filled + // fill the transfer with delivered transfer type + function _handleTransfer(bytes32 _transferId) internal { + require(filledTransfers[_transferId] == TRANSFER_UNFILLED, "!conflict"); + filledTransfers[_transferId] = TRANSFER_DELIVERED; + } + + // request a cross-chain refund + // 1. can retry + // 2. can't be filled by delivery + function _requestRefund(bytes32 _transferId) internal { + uint256 filledTransfer = filledTransfers[_transferId]; + // already fill by refund, retry request + if (filledTransfer == TRANSFER_REFUNDED) { + return; + } + require(filledTransfer == TRANSFER_UNFILLED, "!conflict"); + filledTransfers[_transferId] = TRANSFER_REFUNDED; + } + function getTransferId( uint256 _nonce, uint256 _targetChainId, diff --git a/helix-contract/contracts/mapping-token/v3/base/xTokenIssuing.sol b/helix-contract/contracts/mapping-token/v3/base/xTokenIssuing.sol index c56b4fb..5c3b191 100644 --- a/helix-contract/contracts/mapping-token/v3/base/xTokenIssuing.sol +++ b/helix-contract/contracts/mapping-token/v3/base/xTokenIssuing.sol @@ -107,8 +107,7 @@ contract xTokenIssuing is xTokenBridgeBase { require(_amount > 0, "can not receive amount zero"); expendDailyLimit(xToken, _amount); - require(filledTransfers[transferId] == TRANSFER_UNFILLED, "message has been accepted"); - filledTransfers[transferId] = TRANSFER_DELIVERED; + _handleTransfer(transferId); address _guard = guard; if (_guard != address(0)) { @@ -182,11 +181,7 @@ contract xTokenIssuing is xTokenBridgeBase { ) external payable { require(_originalSender == msg.sender || _recipient == msg.sender || dao == msg.sender, "invalid msgSender"); bytes32 transferId = getTransferId(_nonce, _originalChainId, _originalToken, _originalSender, _recipient, _amount); - uint256 filledTransfer = filledTransfers[transferId]; - require(filledTransfer != TRANSFER_DELIVERED, "success message can't refund for failed"); - if (filledTransfer != TRANSFER_REFUNDED) { - filledTransfers[transferId] = TRANSFER_REFUNDED; - } + _requestRefund(transferId); bytes memory handleUnlockForFailed = encodeUnlockForIssuingFailureFromRemote( _originalToken, _originalSender, diff --git a/helix-contract/test/6_test_xtoken_v3.js b/helix-contract/test/6_test_xtoken_v3.js index 43eaf14..b7ebef6 100644 --- a/helix-contract/test/6_test_xtoken_v3.js +++ b/helix-contract/test/6_test_xtoken_v3.js @@ -396,14 +396,14 @@ describe("xtoken tests", () => { nonce01, "1.1", true - )).to.be.revertedWith("success message can't refund for failed"); + )).to.be.revertedWith("!conflict"); await expect(requestRemoteIssuingForUnlockFailure( nativeTokenAddress, 100, nonce02, "1.1", true - )).to.be.revertedWith("success message can't refund for failed"); + )).to.be.revertedWith("!conflict"); // lock exceed daily limit const nonce03 = await lockAndRemoteIssuing(