From d08fcc04fa142c6174edcdcae3fb18c692250214 Mon Sep 17 00:00:00 2001 From: xiaoch05 Date: Wed, 6 Sep 2023 22:53:02 +0800 Subject: [PATCH] repair unit test --- .../contracts/ln/LnDefaultBridge.sol | 27 +- .../contracts/ln/LnOppositeBridge.sol | 26 +- .../contracts/ln/base/LnBridgeHelper.sol | 1 - .../ln/base/LnDefaultBridgeSource.sol | 16 +- .../ln/base/LnDefaultBridgeTarget.sol | 14 +- .../ln/base/LnOppositeBridgeTarget.sol | 17 +- .../ln/interface/ILnDefaultBridgeTarget.sol | 1 + .../ln/interface/ILowLevelMessager.sol | 9 +- .../ln/messager/Eth2ArbReceiveService.sol | 41 ++ ...ArbMessager.sol => Eth2ArbSendService.sol} | 22 +- .../ln/messager/Eth2LineaReceiveService.sol | 44 +++ ...aMessager.sol => Eth2LineaSendService.sol} | 19 +- .../ln/messager/Eth2ZkSyncReceiveService.sol | 43 +++ ...Messager.sol => Eth2ZkSyncSendService.sol} | 21 +- .../ln/messager/LayerZeroMessager.sol | 22 +- .../ln/test/MockEth2ArbReceiveService.sol | 12 + helix-contract/test/4_test_ln_positive.js | 361 ++++++++++++------ helix-contract/test/5_test_ln_layerzero.js | 159 ++++++-- 18 files changed, 608 insertions(+), 247 deletions(-) create mode 100644 helix-contract/contracts/ln/messager/Eth2ArbReceiveService.sol rename helix-contract/contracts/ln/messager/{Eth2ArbMessager.sol => Eth2ArbSendService.sol} (74%) create mode 100644 helix-contract/contracts/ln/messager/Eth2LineaReceiveService.sol rename helix-contract/contracts/ln/messager/{Eth2LineaMessager.sol => Eth2LineaSendService.sol} (66%) create mode 100644 helix-contract/contracts/ln/messager/Eth2ZkSyncReceiveService.sol rename helix-contract/contracts/ln/messager/{Eth2ZkSyncMessager.sol => Eth2ZkSyncSendService.sol} (71%) create mode 100644 helix-contract/contracts/ln/test/MockEth2ArbReceiveService.sol diff --git a/helix-contract/contracts/ln/LnDefaultBridge.sol b/helix-contract/contracts/ln/LnDefaultBridge.sol index 4e6a4278..fc9134b4 100644 --- a/helix-contract/contracts/ln/LnDefaultBridge.sol +++ b/helix-contract/contracts/ln/LnDefaultBridge.sol @@ -8,9 +8,13 @@ import "./base/LnDefaultBridgeTarget.sol"; import "./interface/ILowLevelMessager.sol"; contract LnDefaultBridge is Initializable, LnAccessController, LnDefaultBridgeSource, LnDefaultBridgeTarget { + struct MessagerService { + address sendService; + address receiveService; + } // remoteChainId => messager - mapping(uint256=>address) public messagers; + mapping(uint256=>MessagerService) public messagers; receive() external payable {} @@ -20,9 +24,14 @@ contract LnDefaultBridge is Initializable, LnAccessController, LnDefaultBridgeSo } // the remote endpoint is unique, if we want multi-path to remote endpoint, then the messager should support multi-path - function setBridgeInfo(uint256 _remoteChainId, address _remoteBridge, address _messager) external onlyDao { - messagers[_remoteChainId] = _messager; - ILowLevelMessager(_messager).registerBridgePair(_remoteChainId, _remoteBridge); + function setSendService(uint256 _remoteChainId, address _remoteBridge, address _service) external onlyDao { + messagers[_remoteChainId].sendService = _service; + ILowLevelMessageSender(_service).registerRemoteReceiver(_remoteChainId, _remoteBridge); + } + + function setReceiveService(uint256 _remoteChainId, address _remoteBridge, address _service) external onlyDao { + messagers[_remoteChainId].receiveService = _service; + ILowLevelMessageReceiver(_service).registerRemoteSender(_remoteChainId, _remoteBridge); } function updateFeeReceiver(address _receiver) external onlyDao { @@ -50,14 +59,14 @@ contract LnDefaultBridge is Initializable, LnAccessController, LnDefaultBridgeSo } function _sendMessageToTarget(uint256 _remoteChainId, bytes memory _payload, bytes memory _extParams) internal override { - address messager = messagers[_remoteChainId]; - require(messager != address(0), "invalid messager"); - ILowLevelMessager(messager).sendMessage(_remoteChainId, _payload, _extParams); + address sendService = messagers[_remoteChainId].sendService; + require(sendService != address(0), "invalid messager"); + ILowLevelMessageSender(sendService).sendMessage(_remoteChainId, _payload, _extParams); } function _verifyRemote(uint256 _remoteChainId) whenNotPaused internal view override { - address messager = messagers[_remoteChainId]; - require(messager == msg.sender, "invalid messager"); + address receiveService = messagers[_remoteChainId].receiveService; + require(receiveService == msg.sender, "invalid messager"); } } diff --git a/helix-contract/contracts/ln/LnOppositeBridge.sol b/helix-contract/contracts/ln/LnOppositeBridge.sol index f97a8cd6..6b9b6e33 100644 --- a/helix-contract/contracts/ln/LnOppositeBridge.sol +++ b/helix-contract/contracts/ln/LnOppositeBridge.sol @@ -8,7 +8,11 @@ import "./base/LnOppositeBridgeTarget.sol"; import "./interface/ILowLevelMessager.sol"; contract LnOppositeBridge is Initializable, LnAccessController, LnOppositeBridgeSource, LnOppositeBridgeTarget { - mapping(uint256=>address) messagers; + struct MessagerService { + address sendService; + address receiveService; + } + mapping(uint256=>MessagerService) messagers; receive() external payable {} @@ -17,6 +21,16 @@ contract LnOppositeBridge is Initializable, LnAccessController, LnOppositeBridge _updateFeeReceiver(_dao); } + function setSendService(uint256 _remoteChainId, address _remoteBridge, address _service) external onlyDao { + messagers[_remoteChainId].sendService = _service; + ILowLevelMessageSender(_service).registerRemoteReceiver(_remoteChainId, _remoteBridge); + } + + function setReceiveService(uint256 _remoteChainId, address _remoteBridge, address _service) external onlyDao { + messagers[_remoteChainId].receiveService = _service; + ILowLevelMessageReceiver(_service).registerRemoteSender(_remoteChainId, _remoteBridge); + } + function updateFeeReceiver(address _receiver) external onlyDao { _updateFeeReceiver(_receiver); } @@ -42,14 +56,14 @@ contract LnOppositeBridge is Initializable, LnAccessController, LnOppositeBridge } function _sendMessageToTarget(uint256 _remoteChainId, bytes memory _payload, bytes memory _extParams) internal override { - address messager = messagers[_remoteChainId]; - require(messager != address(0), "invalid messager"); - ILowLevelMessager(messager).sendMessage(_remoteChainId, _payload, _extParams); + address sendService = messagers[_remoteChainId].sendService; + require(sendService != address(0), "invalid messager"); + ILowLevelMessageSender(sendService).sendMessage(_remoteChainId, _payload, _extParams); } function _verifyRemote(uint256 _remoteChainId) whenNotPaused internal view override { - address messager = messagers[_remoteChainId]; - require(messager == msg.sender, "invalid messager"); + address receiveService = messagers[_remoteChainId].receiveService; + require(receiveService == msg.sender, "invalid messager"); } } diff --git a/helix-contract/contracts/ln/base/LnBridgeHelper.sol b/helix-contract/contracts/ln/base/LnBridgeHelper.sol index 1feb9267..d7fb4662 100644 --- a/helix-contract/contracts/ln/base/LnBridgeHelper.sol +++ b/helix-contract/contracts/ln/base/LnBridgeHelper.sol @@ -13,7 +13,6 @@ library LnBridgeHelper { uint256 constant public LIQUIDITY_FEE_RATE_BASE = 100000; struct TransferParameter { - uint256 remoteChainId; bytes32 previousTransferId; address provider; address sourceToken; diff --git a/helix-contract/contracts/ln/base/LnDefaultBridgeSource.sol b/helix-contract/contracts/ln/base/LnDefaultBridgeSource.sol index 1c51073b..f6c58803 100644 --- a/helix-contract/contracts/ln/base/LnDefaultBridgeSource.sol +++ b/helix-contract/contracts/ln/base/LnDefaultBridgeSource.sol @@ -240,16 +240,17 @@ contract LnDefaultBridgeSource { function _slashAndRemoteReleaseCall( LnBridgeHelper.TransferParameter memory _params, + uint256 _remoteChainId, bytes32 _expectedTransferId ) internal view returns(bytes memory message) { - bytes32 key = keccak256(abi.encodePacked(_params.remoteChainId, _params.sourceToken, _params.targetToken)); + bytes32 key = keccak256(abi.encodePacked(_remoteChainId, _params.sourceToken, _params.targetToken)); LnBridgeHelper.TokenInfo memory tokenInfo = tokenInfos[key]; require(tokenInfo.isRegistered, "token not registered"); uint112 targetAmount = LnBridgeHelper.sourceAmountToTargetAmount(tokenInfo, _params.amount); bytes32 transferId = keccak256(abi.encodePacked( block.chainid, - _params.remoteChainId, + _remoteChainId, _params.previousTransferId, _params.provider, _params.sourceToken, @@ -259,7 +260,7 @@ contract LnDefaultBridgeSource { )); require(_expectedTransferId == transferId, "expected transfer id not match"); LockInfo memory lockInfo = lockInfos[transferId]; - require(lockInfo.timestamp == _params.timestamp, "lock info not match"); + require(lockInfo.timestamp == _params.timestamp, "invalid timestamp"); require(block.timestamp > lockInfo.timestamp + LnBridgeHelper.SLASH_EXPIRE_TIME, "invalid timestamp"); uint112 targetFee = LnBridgeHelper.sourceAmountToTargetAmount(tokenInfo, lockInfo.fee); uint112 targetPenalty = LnBridgeHelper.sourceAmountToTargetAmount(tokenInfo, lockInfo.penalty); @@ -267,6 +268,7 @@ contract LnDefaultBridgeSource { message = abi.encodeWithSelector( ILnDefaultBridgeTarget.slash.selector, _params, + _remoteChainId, msg.sender, // slasher targetFee, targetPenalty @@ -301,6 +303,7 @@ contract LnDefaultBridgeSource { function encodeSlashCall( LnBridgeHelper.TransferParameter memory _params, + uint256 _remoteChainId, address _slasher, uint112 _fee, uint112 _penalty @@ -308,6 +311,7 @@ contract LnDefaultBridgeSource { return abi.encodeWithSelector( ILnDefaultBridgeTarget.slash.selector, _params, + _remoteChainId, _slasher, _fee, _penalty @@ -338,15 +342,17 @@ contract LnDefaultBridgeSource { function requestSlashAndRemoteRelease( LnBridgeHelper.TransferParameter calldata _params, + uint256 _remoteChainId, bytes32 _expectedTransferId, bytes memory _extParams ) payable external { bytes memory slashCallMessage = _slashAndRemoteReleaseCall( _params, + _remoteChainId, _expectedTransferId ); - _sendMessageToTarget(_params.remoteChainId, slashCallMessage, _extParams); - emit SlashRequest(_params.remoteChainId, _params.sourceToken, _params.targetToken, _expectedTransferId); + _sendMessageToTarget(_remoteChainId, slashCallMessage, _extParams); + emit SlashRequest(_remoteChainId, _params.sourceToken, _params.targetToken, _expectedTransferId); } function requestWithdrawMargin( diff --git a/helix-contract/contracts/ln/base/LnDefaultBridgeTarget.sol b/helix-contract/contracts/ln/base/LnDefaultBridgeTarget.sol index 6148e5a3..926ed950 100644 --- a/helix-contract/contracts/ln/base/LnDefaultBridgeTarget.sol +++ b/helix-contract/contracts/ln/base/LnDefaultBridgeTarget.sol @@ -60,11 +60,14 @@ contract LnDefaultBridgeTarget { function transferAndReleaseMargin( LnBridgeHelper.TransferParameter calldata _params, + uint256 _remoteChainId, bytes32 _expectedTransferId ) external payable { require(_params.provider == msg.sender, "invalid provider"); require(_params.previousTransferId == bytes32(0) || fillTransfers[_params.previousTransferId].timestamp > 0, "last transfer not filled"); bytes32 transferId = keccak256(abi.encodePacked( + _remoteChainId, + block.chainid, _params.previousTransferId, _params.provider, _params.sourceToken, @@ -79,7 +82,7 @@ contract LnDefaultBridgeTarget { fillTransfers[transferId].timestamp = uint64(block.timestamp); if (block.timestamp - LnBridgeHelper.SLASH_EXPIRE_TIME > _params.timestamp) { - bytes32 providerKey = LnBridgeHelper.getProviderKey(_params.remoteChainId, msg.sender, _params.sourceToken, _params.targetToken); + bytes32 providerKey = LnBridgeHelper.getProviderKey(_remoteChainId, msg.sender, _params.sourceToken, _params.targetToken); tgtProviders[providerKey].lastExpireFillTime = uint64(block.timestamp); } @@ -165,15 +168,16 @@ contract LnDefaultBridgeTarget { function slash( LnBridgeHelper.TransferParameter memory _params, - uint256 _sourceChainId, + uint256 _remoteChainId, address _slasher, uint112 _fee, uint112 _penalty - ) external allowRemoteCall(_sourceChainId) { + ) external allowRemoteCall(_remoteChainId) { require(_params.previousTransferId == bytes32(0) || fillTransfers[_params.previousTransferId].timestamp > 0, "last transfer not filled"); bytes32 transferId = keccak256(abi.encodePacked( - _params.remoteChainId, + _remoteChainId, + block.chainid, _params.previousTransferId, _params.provider, _params.sourceToken, @@ -183,7 +187,7 @@ contract LnDefaultBridgeTarget { )); FillTransfer memory fillTransfer = fillTransfers[transferId]; require(fillTransfer.slasher == address(0), "transfer has been slashed"); - bytes32 providerKey = LnBridgeHelper.getProviderKey(_params.remoteChainId, _params.provider, _params.sourceToken, _params.targetToken); + bytes32 providerKey = LnBridgeHelper.getProviderKey(_remoteChainId, _params.provider, _params.sourceToken, _params.targetToken); TargetProviderInfo memory providerInfo = tgtProviders[providerKey]; uint256 updatedMargin = providerInfo.margin; // transfer is not filled diff --git a/helix-contract/contracts/ln/base/LnOppositeBridgeTarget.sol b/helix-contract/contracts/ln/base/LnOppositeBridgeTarget.sol index 07cacc4b..6759f977 100644 --- a/helix-contract/contracts/ln/base/LnOppositeBridgeTarget.sol +++ b/helix-contract/contracts/ln/base/LnOppositeBridgeTarget.sol @@ -65,9 +65,12 @@ contract LnOppositeBridgeTarget { // III. Both I and II => latestSlashTransferId is trusted if previousTransfer is normal relayed tranfer function _fillTransfer( LnBridgeHelper.TransferParameter calldata _params, + uint256 _remoteChainId, bytes32 _expectedTransferId ) internal { bytes32 transferId = keccak256(abi.encodePacked( + _remoteChainId, + block.chainid, _params.previousTransferId, _params.provider, _params.sourceToken, @@ -90,11 +93,12 @@ contract LnOppositeBridgeTarget { function transferAndReleaseMargin( LnBridgeHelper.TransferParameter calldata _params, + uint256 _remoteChainId, bytes32 _expectedTransferId ) payable external { // normal relay message, fill slasher as zero require(_params.provider == msg.sender, "invalid provider"); - _fillTransfer(_params, _expectedTransferId); + _fillTransfer(_params, _remoteChainId, _expectedTransferId); emit TransferFilled(_expectedTransferId, address(0)); } @@ -105,10 +109,11 @@ contract LnOppositeBridgeTarget { // So we needs to carry the the previous shash transferId to ensure that the slash is continuous. function _slashAndRemoteReleaseCall( LnBridgeHelper.TransferParameter calldata _params, + uint256 _remoteChainId, bytes32 _expectedTransferId ) internal returns(bytes memory message) { require(block.timestamp > _params.timestamp + LnBridgeHelper.SLASH_EXPIRE_TIME, "slash time not expired"); - _fillTransfer(_params, _expectedTransferId); + _fillTransfer(_params, _remoteChainId, _expectedTransferId); // slasher = msg.sender slashInfos[_expectedTransferId] = SlashInfo(_params.provider, _params.sourceToken, _params.targetToken, msg.sender, _params.timestamp); @@ -149,9 +154,9 @@ contract LnOppositeBridgeTarget { bytes32 _latestSlashTransferId, bytes32 _transferId, uint256 _timestamp, - address _provider, address _sourceToken, address _targetToken, + address _provider, address _slasher ) internal view returns(bytes memory) { return abi.encodeWithSelector( @@ -171,15 +176,17 @@ contract LnOppositeBridgeTarget { function requestSlashAndRemoteRelease( LnBridgeHelper.TransferParameter calldata _params, + uint256 _remoteChainId, bytes32 _expectedTransferId, bytes memory _extParams ) payable external { bytes memory slashCallMessage = _slashAndRemoteReleaseCall( _params, + _remoteChainId, _expectedTransferId ); - _sendMessageToTarget(_params.remoteChainId, slashCallMessage, _extParams); - emit SlashRequest(_params.remoteChainId, _params.sourceToken, _params.targetToken, _expectedTransferId); + _sendMessageToTarget(_remoteChainId, slashCallMessage, _extParams); + emit SlashRequest(_remoteChainId, _params.sourceToken, _params.targetToken, _expectedTransferId); } function requestRetrySlashAndRemoteRelease( diff --git a/helix-contract/contracts/ln/interface/ILnDefaultBridgeTarget.sol b/helix-contract/contracts/ln/interface/ILnDefaultBridgeTarget.sol index 60546c8a..a3958e08 100644 --- a/helix-contract/contracts/ln/interface/ILnDefaultBridgeTarget.sol +++ b/helix-contract/contracts/ln/interface/ILnDefaultBridgeTarget.sol @@ -6,6 +6,7 @@ import "../base/LnBridgeHelper.sol"; interface ILnDefaultBridgeTarget { function slash( LnBridgeHelper.TransferParameter memory params, + uint256 remoteChainId, address slasher, uint112 fee, uint112 penalty diff --git a/helix-contract/contracts/ln/interface/ILowLevelMessager.sol b/helix-contract/contracts/ln/interface/ILowLevelMessager.sol index d4ccae59..7b959402 100644 --- a/helix-contract/contracts/ln/interface/ILowLevelMessager.sol +++ b/helix-contract/contracts/ln/interface/ILowLevelMessager.sol @@ -1,7 +1,12 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.10; -interface ILowLevelMessager { - function registerBridgePair(uint256 remoteChainId, address remoteBridge) external; +interface ILowLevelMessageSender { + function registerRemoteReceiver(uint256 remoteChainId, address remoteBridge) external; function sendMessage(uint256 remoteChainId, bytes memory message, bytes memory params) external payable; } + +interface ILowLevelMessageReceiver { + function registerRemoteSender(uint256 remoteChainId, address remoteBridge) external; + function recvMessage(address remoteSender, address localReceiver, bytes memory payload) external; +} diff --git a/helix-contract/contracts/ln/messager/Eth2ArbReceiveService.sol b/helix-contract/contracts/ln/messager/Eth2ArbReceiveService.sol new file mode 100644 index 00000000..c4b57aaa --- /dev/null +++ b/helix-contract/contracts/ln/messager/Eth2ArbReceiveService.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.10; + +import "@arbitrum/nitro-contracts/src/libraries/AddressAliasHelper.sol"; +import "../interface/ILowLevelMessager.sol"; + +// from ethereum to arbitrum messager +contract Eth2ArbReceiveService is ILowLevelMessageReceiver { + uint256 immutable public REMOTE_CHAINID; + address public remoteMessagerAlias; + + mapping(address=>address) public appPairs; + + modifier onlyRemoteBridge() { + require(msg.sender == remoteMessagerAlias, "invalid remote caller"); + _; + } + + constructor(uint256 _remoteChainId) { + REMOTE_CHAINID = _remoteChainId; + } + + // only can be set once + function setRemoteMessager(address _remoteMessager) external { + require(remoteMessagerAlias == address(0), "remote exist"); + remoteMessagerAlias = AddressAliasHelper.applyL1ToL2Alias(_remoteMessager); + } + + function registerRemoteSender(uint256 _remoteChainId, address _remoteBridge) external { + require(_remoteChainId == REMOTE_CHAINID, "invalid remote chainId"); + appPairs[msg.sender] = _remoteBridge; + } + + function recvMessage(address _remoteApp, address _localApp, bytes memory _message) onlyRemoteBridge external { + address remoteAppAddress = appPairs[_localApp]; + require(remoteAppAddress == _remoteApp, "invalid remote app"); + (bool result,) = _localApp.call(_message); + require(result == true, "local call failed"); + } +} + diff --git a/helix-contract/contracts/ln/messager/Eth2ArbMessager.sol b/helix-contract/contracts/ln/messager/Eth2ArbSendService.sol similarity index 74% rename from helix-contract/contracts/ln/messager/Eth2ArbMessager.sol rename to helix-contract/contracts/ln/messager/Eth2ArbSendService.sol index de035b59..51498d22 100644 --- a/helix-contract/contracts/ln/messager/Eth2ArbMessager.sol +++ b/helix-contract/contracts/ln/messager/Eth2ArbSendService.sol @@ -2,23 +2,15 @@ pragma solidity ^0.8.10; import "@arbitrum/nitro-contracts/src/bridge/IInbox.sol"; -import "@arbitrum/nitro-contracts/src/libraries/AddressAliasHelper.sol"; import "../interface/ILowLevelMessager.sol"; // from ethereum to arbitrum messager -contract Eth2ArbMessager is ILowLevelMessager { +contract Eth2ArbSendService is ILowLevelMessageSender { uint256 immutable public REMOTE_CHAINID; IInbox public inbox; address public remoteMessager; - address public remoteMessagerAlias; - mapping(address=>address) public appPairs; - modifier onlyRemoteBridge() { - require(msg.sender == remoteMessagerAlias, "invalid remote caller"); - _; - } - constructor(address _inbox, uint256 _remoteChainId) { inbox = IInbox(_inbox); REMOTE_CHAINID = _remoteChainId; @@ -28,10 +20,9 @@ contract Eth2ArbMessager is ILowLevelMessager { function setRemoteMessager(address _remoteMessager) external { require(remoteMessager == address(0), "remote exist"); remoteMessager = _remoteMessager; - remoteMessagerAlias = AddressAliasHelper.applyL1ToL2Alias(_remoteMessager); } - function registerBridgePair(uint256 _remoteChainId, address _remoteBridge) external { + function registerRemoteReceiver(uint256 _remoteChainId, address _remoteBridge) external { require(_remoteChainId == REMOTE_CHAINID, "invalid remote chainId"); appPairs[msg.sender] = _remoteBridge; } @@ -44,7 +35,7 @@ contract Eth2ArbMessager is ILowLevelMessager { (uint256 maxSubmissionCost, uint256 l2GasPrice, uint256 l2GasLimit, address refunder) = abi.decode(_params, (uint256, uint256, uint256, address)); bytes memory remoteReceiveCall = abi.encodeWithSelector( - Eth2ArbMessager.recvMessage.selector, + ILowLevelMessageReceiver.recvMessage.selector, msg.sender, remoteAppAddress, _message @@ -61,13 +52,6 @@ contract Eth2ArbMessager is ILowLevelMessager { ); } - function recvMessage(address _remoteApp, address _localApp, bytes memory _message) onlyRemoteBridge external { - address remoteAppAddress = appPairs[_localApp]; - require(remoteAppAddress == _remoteApp, "invalid remote app"); - (bool result,) = _localApp.call(_message); - require(result == true, "local call failed"); - } - function fee( uint256 _callSize, uint256 _l1GasPrice, diff --git a/helix-contract/contracts/ln/messager/Eth2LineaReceiveService.sol b/helix-contract/contracts/ln/messager/Eth2LineaReceiveService.sol new file mode 100644 index 00000000..60ada98f --- /dev/null +++ b/helix-contract/contracts/ln/messager/Eth2LineaReceiveService.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.10; + +import "./interface/ILineaMessageService.sol"; +import "../interface/ILowLevelMessager.sol"; + +// from ethereum to linea messager +contract Eth2LineaReceiveService is ILowLevelMessageReceiver { + uint256 immutable public REMOTE_CHAINID; + ILineaMessageService public messageService; + address public remoteMessager; + + mapping(address=>address) public appPairs; + + modifier onlyRemoteBridge() { + require(msg.sender == address(messageService), "invalid msg.sender"); + require(messageService.sender() == remoteMessager, "invalid remote caller"); + _; + } + + constructor(address _messageService, uint256 _remoteChainId) { + messageService = ILineaMessageService(_messageService); + REMOTE_CHAINID = _remoteChainId; + } + + // only can be set once + function setRemoteMessager(address _remoteMessager) external { + require(remoteMessager == address(0), "remote exist"); + remoteMessager = _remoteMessager; + } + + function registerRemoteSender(uint256 _remoteChainId, address _remoteBridge) external { + require(_remoteChainId == REMOTE_CHAINID, "invalid remote chainId"); + appPairs[msg.sender] = _remoteBridge; + } + + function recvMessage(address _remoteApp, address _localApp, bytes memory _message) onlyRemoteBridge external { + address remoteAppAddress = appPairs[_localApp]; + require(remoteAppAddress == _remoteApp, "invalid remote app"); + (bool result,) = _localApp.call(_message); + require(result == true, "local call failed"); + } +} + diff --git a/helix-contract/contracts/ln/messager/Eth2LineaMessager.sol b/helix-contract/contracts/ln/messager/Eth2LineaSendService.sol similarity index 66% rename from helix-contract/contracts/ln/messager/Eth2LineaMessager.sol rename to helix-contract/contracts/ln/messager/Eth2LineaSendService.sol index 79b63e87..c7819bc4 100644 --- a/helix-contract/contracts/ln/messager/Eth2LineaMessager.sol +++ b/helix-contract/contracts/ln/messager/Eth2LineaSendService.sol @@ -5,19 +5,13 @@ import "./interface/ILineaMessageService.sol"; import "../interface/ILowLevelMessager.sol"; // from ethereum to linea messager -contract Eth2LineaMessager is ILowLevelMessager { +contract Eth2LineaSendService is ILowLevelMessageSender { uint256 immutable public REMOTE_CHAINID; ILineaMessageService public messageService; address public remoteMessager; mapping(address=>address) public appPairs; - modifier onlyRemoteBridge() { - require(msg.sender == address(messageService), "invalid msg.sender"); - require(messageService.sender() == remoteMessager, "invalid remote caller"); - _; - } - constructor(address _messageService, uint256 _remoteChainId) { messageService = ILineaMessageService(_messageService); REMOTE_CHAINID = _remoteChainId; @@ -29,7 +23,7 @@ contract Eth2LineaMessager is ILowLevelMessager { remoteMessager = _remoteMessager; } - function registerBridgePair(uint256 _remoteChainId, address _remoteBridge) external { + function registerRemoteReceiver(uint256 _remoteChainId, address _remoteBridge) external { require(_remoteChainId == REMOTE_CHAINID, "invalid remote chainId"); appPairs[msg.sender] = _remoteBridge; } @@ -40,7 +34,7 @@ contract Eth2LineaMessager is ILowLevelMessager { require(remoteAppAddress != address(0), "app not registered"); bytes memory remoteReceiveCall = abi.encodeWithSelector( - Eth2LineaMessager.recvMessage.selector, + ILowLevelMessageReceiver.recvMessage.selector, msg.sender, remoteAppAddress, _message @@ -51,12 +45,5 @@ contract Eth2LineaMessager is ILowLevelMessager { remoteReceiveCall ); } - - function recvMessage(address _remoteApp, address _localApp, bytes memory _message) onlyRemoteBridge external { - address remoteAppAddress = appPairs[_localApp]; - require(remoteAppAddress == _remoteApp, "invalid remote app"); - (bool result,) = _localApp.call(_message); - require(result == true, "local call failed"); - } } diff --git a/helix-contract/contracts/ln/messager/Eth2ZkSyncReceiveService.sol b/helix-contract/contracts/ln/messager/Eth2ZkSyncReceiveService.sol new file mode 100644 index 00000000..9252ad45 --- /dev/null +++ b/helix-contract/contracts/ln/messager/Eth2ZkSyncReceiveService.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.10; + +import "./interface/IZksyncMailbox.sol"; +import "../interface/ILowLevelMessager.sol"; + +// from ethereum to zkSync messager +contract Eth2ZkSyncReceiveService is ILowLevelMessageReceiver { + uint160 constant offset = uint160(0x1111000000000000000000000000000000001111); + uint256 immutable public REMOTE_CHAINID; + IMailbox public mailbox; + address public remoteMessagerAlias; + + mapping(address=>address) public appPairs; + + modifier onlyRemoteBridge() { + require(msg.sender == remoteMessagerAlias, "invalid remote caller"); + _; + } + + constructor(address _mailbox, uint256 _remoteChainId) { + mailbox = IMailbox(_mailbox); + REMOTE_CHAINID = _remoteChainId; + } + + // only can be set once + function setRemoteMessager(address _remoteMessager) external { + remoteMessagerAlias = address(uint160(_remoteMessager) + offset); + } + + function registerRemoteSender(uint256 _remoteChainId, address _remoteBridge) external { + require(_remoteChainId == REMOTE_CHAINID, "invalid remote chainId"); + appPairs[msg.sender] = _remoteBridge; + } + + function recvMessage(address _remoteApp, address _localApp, bytes memory _message) onlyRemoteBridge external { + address remoteAppAddress = appPairs[_localApp]; + require(remoteAppAddress == _remoteApp, "invalid remote app"); + (bool result,) = _localApp.call(_message); + require(result == true, "local call failed"); + } +} + diff --git a/helix-contract/contracts/ln/messager/Eth2ZkSyncMessager.sol b/helix-contract/contracts/ln/messager/Eth2ZkSyncSendService.sol similarity index 71% rename from helix-contract/contracts/ln/messager/Eth2ZkSyncMessager.sol rename to helix-contract/contracts/ln/messager/Eth2ZkSyncSendService.sol index 8f7bc359..64e6ceb0 100644 --- a/helix-contract/contracts/ln/messager/Eth2ZkSyncMessager.sol +++ b/helix-contract/contracts/ln/messager/Eth2ZkSyncSendService.sol @@ -5,20 +5,13 @@ import "./interface/IZksyncMailbox.sol"; import "../interface/ILowLevelMessager.sol"; // from ethereum to zkSync messager -contract Eth2ZkSyncMessager is ILowLevelMessager { - uint160 constant offset = uint160(0x1111000000000000000000000000000000001111); +contract Eth2ZkSyncSendService is ILowLevelMessageSender { uint256 immutable public REMOTE_CHAINID; IMailbox public mailbox; address public remoteMessager; - address public remoteMessagerAlias; mapping(address=>address) public appPairs; - modifier onlyRemoteBridge() { - require(msg.sender == remoteMessagerAlias, "invalid remote caller"); - _; - } - constructor(address _mailbox, uint256 _remoteChainId) { mailbox = IMailbox(_mailbox); REMOTE_CHAINID = _remoteChainId; @@ -28,10 +21,9 @@ contract Eth2ZkSyncMessager is ILowLevelMessager { function setRemoteMessager(address _remoteMessager) external { require(remoteMessager == address(0), "remote exist"); remoteMessager = _remoteMessager; - remoteMessagerAlias = address(uint160(_remoteMessager) + offset); } - function registerBridgePair(uint256 _remoteChainId, address _remoteBridge) external { + function registerRemoteReceiver(uint256 _remoteChainId, address _remoteBridge) external { require(_remoteChainId == REMOTE_CHAINID, "invalid remote chainId"); appPairs[msg.sender] = _remoteBridge; } @@ -44,7 +36,7 @@ contract Eth2ZkSyncMessager is ILowLevelMessager { (uint256 l2GasLimit, uint256 l2GasPerPubdataByteLimit, address refunder) = abi.decode(_params, (uint256, uint256, address)); bytes memory remoteReceiveCall = abi.encodeWithSelector( - Eth2ZkSyncMessager.recvMessage.selector, + ILowLevelMessageReceiver.recvMessage.selector, msg.sender, remoteAppAddress, _message @@ -60,13 +52,6 @@ contract Eth2ZkSyncMessager is ILowLevelMessager { ); } - function recvMessage(address _remoteApp, address _localApp, bytes memory _message) onlyRemoteBridge external { - address remoteAppAddress = appPairs[_localApp]; - require(remoteAppAddress == _remoteApp, "invalid remote app"); - (bool result,) = _localApp.call(_message); - require(result == true, "local call failed"); - } - function fee( uint256 _gasPrice, uint256 _l2GasLimit, diff --git a/helix-contract/contracts/ln/messager/LayerZeroMessager.sol b/helix-contract/contracts/ln/messager/LayerZeroMessager.sol index 0c7767d4..e03a316d 100644 --- a/helix-contract/contracts/ln/messager/LayerZeroMessager.sol +++ b/helix-contract/contracts/ln/messager/LayerZeroMessager.sol @@ -5,7 +5,7 @@ import "./interface/ILayerZeroEndpoint.sol"; import "../interface/ILowLevelMessager.sol"; import "../base/LnAccessController.sol"; -contract LayerZeroMessager is ILowLevelMessager, LnAccessController { +contract LayerZeroMessager is LnAccessController { ILayerZeroEndpoint public endpoint; struct RemoteMessager { @@ -20,7 +20,8 @@ contract LayerZeroMessager is ILowLevelMessager, LnAccessController { // token bridge pair // hash(lzRemoteChainId, localAppAddress) => remoteAppAddress - mapping(bytes32=>address) public appPairs; + mapping(bytes32=>address) public remoteAppReceivers; + mapping(bytes32=>address) public remoteAppSenders; event CallResult(uint16 lzRemoteChainId, bytes srcAddress, bool successed); @@ -40,11 +41,18 @@ contract LayerZeroMessager is ILowLevelMessager, LnAccessController { trustedRemotes[_lzRemoteChainId] = keccak256(abi.encodePacked(_remoteMessager, address(this))); } - function registerBridgePair(uint256 _remoteChainId, address _remoteBridge) external { + function registerRemoteReceiver(uint256 _remoteChainId, address _remoteBridge) external { RemoteMessager memory remoteMessager = remoteMessagers[_remoteChainId]; require(remoteMessager.messager != address(0), "remote not configured"); bytes32 key = keccak256(abi.encodePacked(remoteMessager.lzRemoteChainId, msg.sender)); - appPairs[key] = _remoteBridge; + remoteAppReceivers[key] = _remoteBridge; + } + + function registerRemoteSender(uint256 _remoteChainId, address _remoteBridge) external { + RemoteMessager memory remoteMessager = remoteMessagers[_remoteChainId]; + require(remoteMessager.messager != address(0), "remote not configured"); + bytes32 key = keccak256(abi.encodePacked(remoteMessager.lzRemoteChainId, msg.sender)); + remoteAppSenders[key] = _remoteBridge; } function sendMessage(uint256 _remoteChainId, bytes memory _message, bytes memory _params) external payable { @@ -56,7 +64,7 @@ contract LayerZeroMessager is ILowLevelMessager, LnAccessController { address(this) ); bytes32 key = keccak256(abi.encodePacked(remoteMessager.lzRemoteChainId, msg.sender)); - address remoteAppAddress = appPairs[key]; + address remoteAppAddress = remoteAppReceivers[key]; require(remoteAppAddress != address(0), "app pair not registered"); bytes memory lzPayload = abi.encode(msg.sender, remoteAppAddress, _message); endpoint.send{ value: msg.value }( @@ -76,9 +84,9 @@ contract LayerZeroMessager is ILowLevelMessager, LnAccessController { uint64, //nonce unused bytes calldata _payload) onlyRemoteBridge(_srcChainId, _srcAddress) external { // call - (address localAppAddress, address remoteAppAddress, bytes memory message) = abi.decode(_payload, (address, address, bytes)); + (address remoteAppAddress, address localAppAddress, bytes memory message) = abi.decode(_payload, (address, address, bytes)); bytes32 key = keccak256(abi.encodePacked(_srcChainId, localAppAddress)); - require(remoteAppAddress == appPairs[key], "invalid remote address"); + require(remoteAppAddress == remoteAppSenders[key], "invalid remote address"); (bool success,) = localAppAddress.call(message); // don't revert to prevent message block emit CallResult(_srcChainId, _srcAddress, success); diff --git a/helix-contract/contracts/ln/test/MockEth2ArbReceiveService.sol b/helix-contract/contracts/ln/test/MockEth2ArbReceiveService.sol new file mode 100644 index 00000000..7180c528 --- /dev/null +++ b/helix-contract/contracts/ln/test/MockEth2ArbReceiveService.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.10; + +import "../messager/Eth2ArbReceiveService.sol"; + +contract MockEth2ArbReceiveService is Eth2ArbReceiveService { + constructor(uint256 _remoteChainId) Eth2ArbReceiveService(_remoteChainId) {} + + function setRemoteMessagerAlias(address _remoteMessagerAlias) external { + remoteMessagerAlias = _remoteMessagerAlias; + } +} diff --git a/helix-contract/test/4_test_ln_positive.js b/helix-contract/test/4_test_ln_positive.js index add4698a..5a6e79bd 100644 --- a/helix-contract/test/4_test_ln_positive.js +++ b/helix-contract/test/4_test_ln_positive.js @@ -7,6 +7,9 @@ const secp256k1 = require('secp256k1'); chai.use(solidity); +// ethereum -> arbitrum using arbitrum L1->L2 message +// arbitrum -> ethereum using layerzero message + async function getBlockTimestamp() { const blockNumber = await ethers.provider.getBlockNumber(); const block = await ethers.provider.getBlock(blockNumber); @@ -14,26 +17,43 @@ async function getBlockTimestamp() { } function getTransferId( + localChainId, + remoteChainId, lastTransferId, // lastTransferId provider, // provider sourceToken, // sourceToken targetToken, // targetToken receiver, // receiver - timestamp, amount, // amount ) { const encoded = ethers.utils.solidityPack([ + "uint256", + "uint256", "bytes32", "address", "address", "address", "address", - "uint64", "uint112", - ], [lastTransferId, provider, sourceToken, targetToken, receiver, timestamp, amount]); + ], [localChainId, remoteChainId, lastTransferId, provider, sourceToken, targetToken, receiver, amount]); return ethUtil.keccak256(encoded); } +function getProviderKey( + remoteChainId, + provider, + sourceToken, + remoteToken +) { + const encode = ethers.utils.solidityPack([ + "uint256", + "address", + "address", + "address", + ], [remoteChainId, provider, sourceToken, remoteToken]); + return ethUtil.keccak256(encode); +} + describe("eth->arb lnv2 positive bridge tests", () => { before(async () => { }); @@ -49,9 +69,10 @@ describe("eth->arb lnv2 positive bridge tests", () => { const liquidityFeeRate = 1; const initTokenBalance = 1000000; const initMargin = 10000; - const initSlashReserveFund = 1000; const initTransferId = "0x0000000000000000000000000000000000000000000000000000000000000000"; const transferAmount = 30; + const ethChainId = 31337; + const arbChainId = 31337; // deploy erc20 token contract const tokenNameOnEthereum = "Darwinia Ring On Ethereum"; @@ -86,17 +107,59 @@ describe("eth->arb lnv2 positive bridge tests", () => { console.log("deploy mock inbox success"); //******* deploy inboundLane/outboundLane finished ******** - const eth2arbSourceContract = await ethers.getContractFactory("Eth2ArbSource"); - const eth2arbSource = await eth2arbSourceContract.deploy(); - await eth2arbSource.deployed(); + + const bridgeContract = await ethers.getContractFactory("LnOppositeBridge"); + + const ethBridge = await bridgeContract.deploy(); + await ethBridge.deployed(); + await ethBridge.initialize(dao); + await ethBridge.updateFeeReceiver(feeReceiver); + const arbBridge = await bridgeContract.deploy(); + await arbBridge.deployed(); + await arbBridge.initialize(dao); + await arbBridge.updateFeeReceiver(feeReceiver); + + // eth -> arb messager service + console.log("deploy etherum to arbitrum l1->l2 message service"); + const eth2arbSendServiceContract = await ethers.getContractFactory("Eth2ArbSendService"); + const eth2arbSendService = await eth2arbSendServiceContract.deploy(inbox.address, arbChainId); + await eth2arbSendService.deployed(); + const eth2arbRecvServiceContract = await ethers.getContractFactory("MockEth2ArbReceiveService"); + const eth2arbRecvService = await eth2arbRecvServiceContract.deploy(ethChainId); + await eth2arbRecvService.deployed(); + + await eth2arbRecvService.setRemoteMessagerAlias(inbox.address); + + // arb -> eth message service + console.log("deploy arbitrum to ethereum layerzero message service"); + const endpointContract = await ethers.getContractFactory("LayerZeroEndpointMock"); + const endpoint = await endpointContract.deploy(arbChainId); + await endpoint.deployed(); + console.log("deploy mock endpoint success"); + //******* deploy endpoint finished ******** + + // deploy layerzero messager + const lzMessagerContract = await ethers.getContractFactory("LayerZeroMessager"); + const lzMessagerEth = await lzMessagerContract.deploy(dao, endpoint.address); + await lzMessagerEth.deployed(); + const lzMessagerArb = await lzMessagerContract.deploy(dao, endpoint.address); + await lzMessagerArb.deployed(); + + await lzMessagerEth.setRemoteMessager(arbChainId, arbChainId, lzMessagerArb.address); + await lzMessagerArb.setRemoteMessager(ethChainId, ethChainId, lzMessagerEth.address); + console.log("messager service deploy finished"); + + console.log("configure message service for token bridge"); + await ethBridge.setSendService(arbChainId, arbBridge.address, eth2arbSendService.address); + await ethBridge.setReceiveService(arbChainId, arbBridge.address, lzMessagerEth.address); + await arbBridge.setSendService(ethChainId, ethBridge.address, lzMessagerArb.address); + await arbBridge.setReceiveService(ethChainId, ethBridge.address, eth2arbRecvService.address); // configure - // init - // set fee receiver // register token - await eth2arbSource.initialize(dao, inbox.address); - await eth2arbSource.updateFeeReceiver(feeReceiver); - await eth2arbSource.setTokenInfo( + console.log("register token info"); + await ethBridge.setTokenInfo( + arbChainId, ethToken.address, arbToken.address, protocolFee, @@ -104,184 +167,248 @@ describe("eth->arb lnv2 positive bridge tests", () => { 18, 18 ); + await arbBridge.setTokenInfo( + ethChainId, + arbToken.address, + ethToken.address, + protocolFee, + penalty, + 18, + 18 + ); - const eth2arbTargetContract = await ethers.getContractFactory("Eth2ArbTarget"); - const eth2arbTarget = await eth2arbTargetContract.deploy(); - await eth2arbTarget.deployed(); - await eth2arbTarget.initialize(dao); - - await eth2arbSource.setRemoteBridge(eth2arbTarget.address); - await eth2arbTarget.setRemoteBridge(eth2arbSource.address); - await eth2arbTarget.setRemoteBridgeAlias(inbox.address); - console.log("deploy bridge finished"); - + console.log("provider register"); // provider - await ethToken.connect(relayer).approve(eth2arbSource.address, initTokenBalance); - await arbToken.connect(relayer).approve(eth2arbTarget.address, initTokenBalance); - // register on source chain(set provider fee) - await eth2arbSource.connect(relayer).setProviderFee( + await ethToken.connect(relayer).approve(ethBridge.address, initTokenBalance); + await arbToken.connect(relayer).approve(arbBridge.address, initTokenBalance); + // register on source chain + await arbBridge.connect(relayer).updateProviderFeeAndMargin( + ethChainId, + arbToken.address, ethToken.address, + initMargin, baseFee, liquidityFeeRate ); - await eth2arbTarget.connect(relayer).depositProviderMargin( - ethToken.address, - arbToken.address, - initMargin - ); - await eth2arbTarget.connect(relayer).depositSlashFundReserve( + + await ethBridge.connect(relayer).updateProviderFeeAndMargin( + arbChainId, ethToken.address, arbToken.address, - initSlashReserveFund + initMargin, + baseFee, + liquidityFeeRate ); - async function getCurrentTransferId(lastTransferId) { + async function getChainInfo(direction) { + if (direction === 'eth2arb') { + return { + srcChainId: ethChainId, + dstChainId: arbChainId, + srcToken: ethToken, + dstToken: arbToken, + srcBridge: ethBridge, + dstBridge: arbBridge, + extParams: relayer.address, + }; + } else { + return { + srcChainId: arbChainId, + dstChainId: ethChainId, + srcToken: arbToken, + dstToken: ethToken, + srcBridge: arbBridge, + dstBridge: ethBridge, + extParams: await arbBridge.encodeParams(0, 0, 200, relayer.address), + }; + } + } + + async function getCurrentTransferId(direction, lastTransferId) { + const chainInfo = await getChainInfo(direction); const blockTimestamp = (await ethers.provider.getBlock("latest")).timestamp; + const transferId = getTransferId( + chainInfo.srcChainId, + chainInfo.dstChainId, lastTransferId, // lastTransferId relayer.address, // provider - ethToken.address, // sourceToken - arbToken.address, // targetToken + chainInfo.srcToken.address, // sourceToken + chainInfo.dstToken.address, // targetToken user.address, // receiver - blockTimestamp, transferAmount, // amount ); // check transferId exist on source chain - const lockInfo = await eth2arbSource.lockInfos(transferId); - expect(lockInfo.isLocked).to.equal(true); + const lockInfo = await chainInfo.srcBridge.lockInfos(transferId); + expect(lockInfo.timestamp).to.equal(blockTimestamp); return transferId; } - async function transfer(lastTransferId, withdrawNonce) { - const totalFee = Number(await eth2arbSource.totalFee( + async function transfer(direction, lastTransferId, leftMargin) { + const chainInfo = await getChainInfo(direction); + const totalFee = Number(await chainInfo.srcBridge.totalFee( + chainInfo.dstChainId, relayer.address, - ethToken.address, + chainInfo.srcToken.address, + chainInfo.dstToken.address, transferAmount )); - const balanceOfUser = await ethToken.balanceOf(user.address); - const balanceOfRelayer = await ethToken.balanceOf(relayer.address); - const tx = await eth2arbSource.connect(user).transferAndLockMargin( + const balanceOfUser = await chainInfo.srcToken.balanceOf(user.address); + const balanceOfRelayer = await chainInfo.srcToken.balanceOf(relayer.address); + const tx = await chainInfo.srcBridge.connect(user).transferAndLockMargin( [ + chainInfo.dstChainId, relayer.address, - ethToken.address, + chainInfo.srcToken.address, + chainInfo.dstToken.address, lastTransferId, totalFee, - withdrawNonce + leftMargin ], transferAmount, user.address, ); - const balanceOfUserAfter = await ethToken.balanceOf(user.address); - const balanceOfRelayerAfter = await ethToken.balanceOf(relayer.address); + const balanceOfUserAfter = await chainInfo.srcToken.balanceOf(user.address); + const balanceOfRelayerAfter = await chainInfo.srcToken.balanceOf(relayer.address); expect(balanceOfUser - balanceOfUserAfter).to.equal(totalFee + transferAmount); expect(balanceOfRelayerAfter - balanceOfRelayer).to.equal(transferAmount + totalFee - protocolFee); return tx; } - async function relay(lastTransferId, transferId, timestamp) { + async function relay(direction, lastTransferId, transferId, timestamp) { + const chainInfo = await getChainInfo(direction); let blockTimestamp = timestamp; if (blockTimestamp === null) { blockTimestamp = (await ethers.provider.getBlock("latest")).timestamp; } - const balanceOfUser = await arbToken.balanceOf(user.address); - const balanceOfRelayer = await arbToken.balanceOf(relayer.address); - const relayTransaction = await eth2arbTarget.connect(relayer).transferAndReleaseMargin( + const balanceOfUser = await chainInfo.dstToken.balanceOf(user.address); + const balanceOfRelayer = await chainInfo.dstToken.balanceOf(relayer.address); + const relayTransaction = await chainInfo.dstBridge.connect(relayer).transferAndReleaseMargin( [ lastTransferId, // lastTransferId relayer.address, // provider - ethToken.address, // sourceToken - arbToken.address, // targetToken + chainInfo.srcToken.address, // sourceToken + chainInfo.dstToken.address, // targetToken transferAmount, blockTimestamp, user.address ], + chainInfo.srcChainId, transferId ); // check relay result const relayTimestamp = (await ethers.provider.getBlock("latest")).timestamp; - const fillInfo = await eth2arbTarget.fillTransfers(transferId); - expect(fillInfo.timestamp).to.equal(relayTimestamp); - expect(fillInfo.slasher).to.equal(nullAddress); - const balanceOfUserAfter = await arbToken.balanceOf(user.address); - const balanceOfRelayerAfter = await arbToken.balanceOf(relayer.address); + //const fillInfo = await chainInfo.dstBridge.fillTransfers(transferId); + //expect(fillInfo.timestamp).to.equal(relayTimestamp); + const slashInfo = await chainInfo.dstBridge.slashInfos(transferId); + expect(slashInfo.slasher).to.equal(nullAddress); + const balanceOfUserAfter = await chainInfo.dstToken.balanceOf(user.address); + const balanceOfRelayerAfter = await chainInfo.dstToken.balanceOf(relayer.address); expect(balanceOfUserAfter - balanceOfUser).to.equal(transferAmount); expect(balanceOfRelayer - balanceOfRelayerAfter).to.equal(transferAmount); return relayTransaction; } - async function slash(lastTransferId, expectedTransferId, timestamp) { + async function slash(direction, lastTransferId, expectedTransferId, timestamp) { + const chainInfo = await getChainInfo(direction); let blockTimestamp = timestamp; if (blockTimestamp === null) { blockTimestamp = (await ethers.provider.getBlock("latest")).timestamp; } - const fillInfoBefore = await eth2arbTarget.fillTransfers(expectedTransferId); - const timestampBefore = fillInfoBefore.timestamp; - const balanceOfUser = await arbToken.balanceOf(user.address); - const balanceOfSlasher = await arbToken.balanceOf(slasher.address); - const slashTransaction = await eth2arbSource.connect(slasher).slashAndRemoteRelease( + const balanceOfUser = await chainInfo.dstToken.balanceOf(user.address); + const balanceOfSlasher = await chainInfo.dstToken.balanceOf(slasher.address); + const balanceOfSlasherOnSrc = await chainInfo.srcToken.balanceOf(slasher.address); + const slashTransaction = await chainInfo.dstBridge.connect(slasher).requestSlashAndRemoteRelease( [ lastTransferId, relayer.address, - ethToken.address, - arbToken.address, + chainInfo.srcToken.address, + chainInfo.dstToken.address, transferAmount, blockTimestamp, user.address ], + chainInfo.srcChainId, expectedTransferId, - 0, - 0, - 200 + chainInfo.extParams, ); - const relayTimestamp = (await ethers.provider.getBlock("latest")).timestamp; - const fillInfo = await eth2arbTarget.fillTransfers(expectedTransferId); - const balanceOfUserAfter = await arbToken.balanceOf(user.address); - const balanceOfSlasherAfter = await arbToken.balanceOf(slasher.address); - if (timestampBefore > 0) { - expect(fillInfo.timestamp).to.equal(timestampBefore); - expect(balanceOfUserAfter - balanceOfUser).to.equal(0); - expect(balanceOfSlasherAfter - balanceOfSlasher).to.equal(penalty/5); - } else { - const totalFee = Number(await eth2arbSource.totalFee( - relayer.address, - ethToken.address, - transferAmount - )); - expect(fillInfo.timestamp).to.equal(relayTimestamp); - expect(balanceOfUserAfter - balanceOfUser).to.equal(transferAmount); - expect(balanceOfSlasherAfter - balanceOfSlasher).to.equal(penalty + totalFee); - } - expect(fillInfo.slasher).to.equal(slasher.address); + const slashInfo = await chainInfo.dstBridge.slashInfos(expectedTransferId); + const balanceOfUserAfter = await chainInfo.dstToken.balanceOf(user.address); + const balanceOfSlasherAfter = await chainInfo.dstToken.balanceOf(slasher.address); + const balanceOfSlasherAfterOnSrc = await chainInfo.srcToken.balanceOf(slasher.address); + const totalFee = Number(await chainInfo.srcBridge.totalFee( + chainInfo.dstChainId, + relayer.address, + chainInfo.srcToken.address, + chainInfo.dstToken.address, + transferAmount + )); + expect(balanceOfUserAfter - balanceOfUser).to.equal(transferAmount); + expect(balanceOfSlasher - balanceOfSlasherAfter).to.equal(transferAmount); + expect(slashInfo.slasher).to.equal(slasher.address); + expect(balanceOfSlasherAfterOnSrc - balanceOfSlasherOnSrc).to.equal(transferAmount + penalty + totalFee - protocolFee); return slashTransaction; } - // user lock - await ethToken.connect(user).approve(eth2arbSource.address, initTokenBalance); - const totalFee = Number(await eth2arbSource.totalFee( + async function withdraw(direction, lastTransferId, amount) { + const chainInfo = await getChainInfo(direction); + const providerKey = getProviderKey(chainInfo.dstChainId, relayer.address, chainInfo.srcToken.address, chainInfo.dstToken.address); + const marginBefore = (await chainInfo.srcBridge.srcProviders(providerKey)).config.margin; + + const balanceOfRelayerBefore = await chainInfo.srcToken.balanceOf(relayer.address); + const withdrawTransaction = await chainInfo.dstBridge.connect(relayer).requestWithdrawMargin( + chainInfo.srcChainId, + lastTransferId, + chainInfo.srcToken.address, + chainInfo.dstToken.address, + amount, + relayer.address + ); + const balanceOfRelayerAfter = await chainInfo.srcToken.balanceOf(relayer.address); + const marginAfter = (await chainInfo.srcBridge.srcProviders(providerKey)).config.margin; + + let successWithdrawAmount = amount; + if (marginBefore.lt(amount)) { + // if withdraw failed + successWithdrawAmount = 0; + } + expect(balanceOfRelayerAfter - balanceOfRelayerBefore).to.equal(successWithdrawAmount); + expect(marginBefore - marginAfter).to.equal(successWithdrawAmount); + return successWithdrawAmount > 0; + } + + // eth -> arb + await ethToken.connect(user).approve(ethBridge.address, initTokenBalance); + const totalFee = Number(await ethBridge.totalFee( + ethChainId, relayer.address, ethToken.address, + arbToken.address, transferAmount )); - const lockTransaction = await transfer(initTransferId, 0); + + // 1. transfer from eth to arb + const lockTransaction = await transfer('eth2arb', initTransferId, initMargin); let lockReceipt = await lockTransaction.wait(); let lockGasUsed = lockReceipt.cumulativeGasUsed; console.log("transferAndLockMargin gas used", lockGasUsed); const blockTimestamp01 = (await ethers.provider.getBlock("latest")).timestamp; - const transferId01 = await getCurrentTransferId(initTransferId); + const transferId01 = await getCurrentTransferId('eth2arb', initTransferId); - const relayTransaction = await relay(initTransferId, transferId01, null); + // 2. relay "transfer from eth to arb" + const relayTransaction = await relay('eth2arb', initTransferId, transferId01, null); let relayReceipt = await relayTransaction.wait(); let relayGasUsed = relayReceipt.cumulativeGasUsed; console.log("relay gas used", relayGasUsed); // check balance const userEthBalance = initTokenBalance - transferAmount - totalFee; - const relayerEthBalance = initTokenBalance + transferAmount + totalFee - protocolFee; + const relayerEthBalance = initTokenBalance + transferAmount + totalFee - protocolFee - initMargin ; const userArbBalance = initTokenBalance + transferAmount; - const relayerArbBalance = initTokenBalance - transferAmount - initMargin - initSlashReserveFund; + const relayerArbBalance = initTokenBalance - transferAmount - initMargin; expect(await ethToken.balanceOf(user.address)).to.equal(userEthBalance); expect(await ethToken.balanceOf(relayer.address)).to.equal(relayerEthBalance); expect(await arbToken.balanceOf(user.address)).to.equal(userArbBalance); @@ -289,48 +416,48 @@ describe("eth->arb lnv2 positive bridge tests", () => { console.log("normal lock and release test finished"); // check unique and continuous - await expect(transfer(initTransferId, 0)).to.be.revertedWith("snapshot expired:transfer"); - await expect(transfer(transferId01, 1)).to.be.revertedWith("snapshot expired:withdraw"); + await expect(transfer("eth2arb", initTransferId, initMargin)).to.be.revertedWith("snapshot expired"); + await expect(transfer("eth2arb", transferId01, initMargin + 1)).to.be.revertedWith("margin updated"); - const lockTransaction1 = await transfer(transferId01, 0) + const lockTransaction1 = await transfer("eth2arb", transferId01, initMargin) lockReceipt = await lockTransaction1.wait(); lockGasUsed = lockReceipt.cumulativeGasUsed; console.log("transferAndLockMargin 01 gas used", lockGasUsed); const blockTimestamp02 = (await ethers.provider.getBlock("latest")).timestamp; - const transferId02 = await getCurrentTransferId(transferId01); - await transfer(transferId02, 0) + const transferId02 = await getCurrentTransferId("eth2arb", transferId01); + await transfer("eth2arb", transferId02, 0) const blockTimestamp03 = (await ethers.provider.getBlock("latest")).timestamp; - const transferId03 = await getCurrentTransferId(transferId02); + const transferId03 = await getCurrentTransferId("eth2arb", transferId02); // release transfer02 failed - await expect(relay(transferId02, transferId03, null)).to.be.revertedWith("last transfer not filled"); + await expect(relay("eth2arb", transferId02, transferId03, null)).to.be.revertedWith("previous fill not exist"); // 1. slash when not timeout - await expect(slash(transferId02, transferId03, null)).to.be.revertedWith("invalid timestamp"); + await expect(slash("eth2arb", transferId02, transferId03, null)).to.be.revertedWith("slash time not expired"); await hre.network.provider.request({ method: "evm_increaseTime", params: [18001], }); - await expect(slash(transferId02, transferId03, blockTimestamp03)).to.be.revertedWith("arbitrum mock call failed"); + await expect(slash("eth2arb", transferId02, transferId03, blockTimestamp03)).to.be.revertedWith("previous fill not exist"); console.log("check continuous success"); // 2. slash when timeout, but relayed - await expect(slash(initTransferId, transferId01, blockTimestamp01)).to.be.revertedWith("arbitrum mock call failed"); + await expect(slash("eth2arb", initTransferId, transferId01, blockTimestamp01)).to.be.revertedWith("fill exist"); // relay 02 && slash 02 - await relay(transferId01, transferId02, blockTimestamp02); + await relay("eth2arb", transferId01, transferId02, blockTimestamp02); // can't relay twice - await expect(relay(transferId01, transferId02, blockTimestamp02)).to.be.revertedWith("transfer has been filled"); + await expect(relay("eth2arb", transferId01, transferId02, blockTimestamp02)).to.be.revertedWith("fill exist"); // 3. slash when timeout but relayed(timeout) - // can slash if relayed when timeout - await slash(transferId01, transferId02, blockTimestamp02); - // 4. slash when slash has finished - // can't slash twice - await expect(slash(transferId01, transferId02, blockTimestamp02)).to.be.revertedWith("arbitrum mock call failed"); + // can't slash event if relayed when timeout + await expect(slash("eth2arb", transferId01, transferId02, blockTimestamp02)).to.be.revertedWith("fill exist"); // slash 03 - // 5. slash when timeout and not relayed + // 4. slash when timeout and not relayed // can slash if not relayed when timeout - await slash(transferId02, transferId03, blockTimestamp03); + await arbToken.connect(slasher).approve(arbBridge.address, initTokenBalance); + await slash("eth2arb", transferId02, transferId03, blockTimestamp03); + expect(await withdraw('eth2arb', transferId03, 15000)).to.equal(false); + expect(await withdraw('eth2arb', transferId03, 5000)).to.equal(true); console.log("ln bridge test finished"); }); }); diff --git a/helix-contract/test/5_test_ln_layerzero.js b/helix-contract/test/5_test_ln_layerzero.js index 93eb588d..4db597d3 100644 --- a/helix-contract/test/5_test_ln_layerzero.js +++ b/helix-contract/test/5_test_ln_layerzero.js @@ -14,26 +14,43 @@ async function getBlockTimestamp() { } function getTransferId( + localChainId, + remoteChainId, lastTransferId, // lastTransferId provider, // provider sourceToken, // sourceToken targetToken, // targetToken receiver, // receiver - timestamp, amount, // amount ) { const encoded = ethers.utils.solidityPack([ + "uint256", + "uint256", "bytes32", "address", "address", "address", "address", - "uint64", "uint112", - ], [lastTransferId, provider, sourceToken, targetToken, receiver, timestamp, amount]); + ], [localChainId, remoteChainId, lastTransferId, provider, sourceToken, targetToken, receiver, amount]); return ethUtil.keccak256(encoded); } +function getProviderKey( + remoteChainId, + provider, + sourceToken, + remoteToken +) { + const encode = ethers.utils.solidityPack([ + "uint256", + "address", + "address", + "address", + ], [remoteChainId, provider, sourceToken, remoteToken]); + return ethUtil.keccak256(encode); +} + describe("eth->arb lnv2 layerzero bridge tests", () => { before(async () => { }); @@ -52,8 +69,8 @@ describe("eth->arb lnv2 layerzero bridge tests", () => { const initSlashReserveFund = 1000; const initTransferId = "0x0000000000000000000000000000000000000000000000000000000000000000"; const transferAmount = 30; - const srcChainId = 100; - const dstChainId = 200; + const ethChainId = 31337; + const arbChainId = 31337; // deploy erc20 token contract const tokenNameOnEthereum = "Darwinia Ring On Ethereum"; @@ -83,22 +100,36 @@ describe("eth->arb lnv2 layerzero bridge tests", () => { // deploy LayerZeroEndpointMock const endpointContract = await ethers.getContractFactory("LayerZeroEndpointMock"); - const endpoint = await endpointContract.deploy(srcChainId); + const endpoint = await endpointContract.deploy(ethChainId); await endpoint.deployed(); console.log("deploy mock endpoint success"); //******* deploy endpoint finished ******** - const eth2arbSourceContract = await ethers.getContractFactory("LnBridgeBaseLZ"); - const eth2arbSource = await eth2arbSourceContract.deploy(); - await eth2arbSource.deployed(); + // deploy layerzero messager + const lzMessagerContract = await ethers.getContractFactory("LayerZeroMessager"); + const lzMessagerEth = await lzMessagerContract.deploy(dao, endpoint.address); + await lzMessagerEth.deployed(); + const lzMessagerArb = await lzMessagerContract.deploy(dao, endpoint.address); + await lzMessagerArb.deployed(); + + await lzMessagerEth.setRemoteMessager(arbChainId, arbChainId, lzMessagerArb.address); + await lzMessagerArb.setRemoteMessager(ethChainId, ethChainId, lzMessagerEth.address); + + const lnDefaultBridgeContract = await ethers.getContractFactory("LnDefaultBridge"); + + const lnDefaultBridgeEth = await lnDefaultBridgeContract.deploy(); + await lnDefaultBridgeEth.deployed(); + const lnDefaultBridgeArb = await lnDefaultBridgeContract.deploy(); + await lnDefaultBridgeArb.deployed(); // configure // init // set fee receiver // register token - await eth2arbSource.initialize(dao, endpoint.address, dstChainId); - await eth2arbSource.updateFeeReceiver(feeReceiver); - await eth2arbSource.setTokenInfo( + await lnDefaultBridgeEth.initialize(dao); + await lnDefaultBridgeEth.updateFeeReceiver(feeReceiver); + await lnDefaultBridgeEth.setTokenInfo( + arbChainId, ethToken.address, arbToken.address, protocolFee, @@ -107,30 +138,43 @@ describe("eth->arb lnv2 layerzero bridge tests", () => { 18 ); - const eth2arbTargetContract = await ethers.getContractFactory("LnBridgeBaseLZ"); - const eth2arbTarget = await eth2arbTargetContract.deploy(); - await eth2arbTarget.deployed(); - await eth2arbTarget.initialize(dao, endpoint.address, srcChainId); + await lnDefaultBridgeArb.initialize(dao); + await lnDefaultBridgeArb.updateFeeReceiver(feeReceiver); + await lnDefaultBridgeArb.setTokenInfo( + arbChainId, + arbToken.address, + ethToken.address, + protocolFee, + penalty, + 18, + 18 + ); + // ******************* register token ************** - await eth2arbSource.setRemoteBridge(eth2arbTarget.address); - await eth2arbTarget.setRemoteBridge(eth2arbSource.address); + // set bridge infos + lnDefaultBridgeEth.setSendService(arbChainId, lnDefaultBridgeArb.address, lzMessagerEth.address); + lnDefaultBridgeArb.setReceiveService(ethChainId, lnDefaultBridgeEth.address, lzMessagerArb.address); console.log("deploy bridge finished"); // provider - await ethToken.connect(relayer).approve(eth2arbSource.address, initTokenBalance); - await arbToken.connect(relayer).approve(eth2arbTarget.address, initTokenBalance); + await ethToken.connect(relayer).approve(lnDefaultBridgeEth.address, initTokenBalance); + await arbToken.connect(relayer).approve(lnDefaultBridgeArb.address, initTokenBalance); // register on source chain(set provider fee) - await eth2arbSource.connect(relayer).setProviderFee( + await lnDefaultBridgeEth.connect(relayer).setProviderFee( + arbChainId, ethToken.address, + arbToken.address, baseFee, liquidityFeeRate ); - await eth2arbTarget.connect(relayer).depositProviderMargin( + await lnDefaultBridgeArb.connect(relayer).depositProviderMargin( + ethChainId, ethToken.address, arbToken.address, initMargin ); - await eth2arbTarget.connect(relayer).depositSlashFundReserve( + await lnDefaultBridgeArb.connect(relayer).depositSlashFundReserve( + ethChainId, ethToken.address, arbToken.address, initSlashReserveFund @@ -139,33 +183,38 @@ describe("eth->arb lnv2 layerzero bridge tests", () => { async function getCurrentTransferId(lastTransferId) { const blockTimestamp = (await ethers.provider.getBlock("latest")).timestamp; const transferId = getTransferId( + ethChainId, + arbChainId, lastTransferId, // lastTransferId relayer.address, // provider ethToken.address, // sourceToken arbToken.address, // targetToken user.address, // receiver - blockTimestamp, transferAmount, // amount ); // check transferId exist on source chain - const lockInfo = await eth2arbSource.lockInfos(transferId); - expect(lockInfo.isLocked).to.equal(true); + const lockInfo = await lnDefaultBridgeEth.lockInfos(transferId); + expect(lockInfo.timestamp).to.equal(blockTimestamp); return transferId; } async function transfer(lastTransferId, withdrawNonce) { - const totalFee = Number(await eth2arbSource.totalFee( + const totalFee = Number(await lnDefaultBridgeEth.totalFee( + arbChainId, relayer.address, ethToken.address, + arbToken.address, transferAmount )); const balanceOfUser = await ethToken.balanceOf(user.address); const balanceOfRelayer = await ethToken.balanceOf(relayer.address); - const tx = await eth2arbSource.connect(user).transferAndLockMargin( + const tx = await lnDefaultBridgeEth.connect(user).transferAndLockMargin( [ + arbChainId, relayer.address, ethToken.address, + arbToken.address, lastTransferId, totalFee, withdrawNonce @@ -187,7 +236,7 @@ describe("eth->arb lnv2 layerzero bridge tests", () => { } const balanceOfUser = await arbToken.balanceOf(user.address); const balanceOfRelayer = await arbToken.balanceOf(relayer.address); - const relayTransaction = await eth2arbTarget.connect(relayer).transferAndReleaseMargin( + const relayTransaction = await lnDefaultBridgeArb.connect(relayer).transferAndReleaseMargin( [ lastTransferId, // lastTransferId relayer.address, // provider @@ -197,12 +246,13 @@ describe("eth->arb lnv2 layerzero bridge tests", () => { blockTimestamp, user.address ], + ethChainId, transferId ); // check relay result const relayTimestamp = (await ethers.provider.getBlock("latest")).timestamp; - const fillInfo = await eth2arbTarget.fillTransfers(transferId); + const fillInfo = await lnDefaultBridgeArb.fillTransfers(transferId); expect(fillInfo.timestamp).to.equal(relayTimestamp); expect(fillInfo.slasher).to.equal(nullAddress); const balanceOfUserAfter = await arbToken.balanceOf(user.address); @@ -217,11 +267,11 @@ describe("eth->arb lnv2 layerzero bridge tests", () => { if (blockTimestamp === null) { blockTimestamp = (await ethers.provider.getBlock("latest")).timestamp; } - const fillInfoBefore = await eth2arbTarget.fillTransfers(expectedTransferId); + const fillInfoBefore = await lnDefaultBridgeArb.fillTransfers(expectedTransferId); const timestampBefore = fillInfoBefore.timestamp; const balanceOfUser = await arbToken.balanceOf(user.address); const balanceOfSlasher = await arbToken.balanceOf(slasher.address); - const slashTransaction = await eth2arbSource.connect(slasher).slashAndRemoteRelease( + const slashTransaction = await lnDefaultBridgeEth.connect(slasher).requestSlashAndRemoteRelease( [ lastTransferId, relayer.address, @@ -231,10 +281,12 @@ describe("eth->arb lnv2 layerzero bridge tests", () => { blockTimestamp, user.address ], - expectedTransferId + ethChainId, + expectedTransferId, + relayer.address ); const relayTimestamp = (await ethers.provider.getBlock("latest")).timestamp; - const fillInfo = await eth2arbTarget.fillTransfers(expectedTransferId); + const fillInfo = await lnDefaultBridgeArb.fillTransfers(expectedTransferId); const balanceOfUserAfter = await arbToken.balanceOf(user.address); const balanceOfSlasherAfter = await arbToken.balanceOf(slasher.address); if (timestampBefore > 0) { @@ -242,9 +294,11 @@ describe("eth->arb lnv2 layerzero bridge tests", () => { expect(balanceOfUserAfter - balanceOfUser).to.equal(0); expect(balanceOfSlasherAfter - balanceOfSlasher).to.equal(penalty/5); } else { - const totalFee = Number(await eth2arbSource.totalFee( + const totalFee = Number(await lnDefaultBridgeEth.totalFee( + arbChainId, relayer.address, ethToken.address, + arbToken.address, transferAmount )); expect(fillInfo.timestamp).to.equal(relayTimestamp); @@ -255,11 +309,37 @@ describe("eth->arb lnv2 layerzero bridge tests", () => { return slashTransaction; } + async function withdraw(amount) { + const providerKey = getProviderKey(ethChainId, relayer.address, ethToken.address, arbToken.address); + const marginBefore = (await lnDefaultBridgeArb.tgtProviders(providerKey)).margin; + const balanceOfRelayerBefore = await arbToken.balanceOf(relayer.address); + const withdrawTransaction = await lnDefaultBridgeEth.connect(relayer).requestWithdrawMargin( + ethChainId, + ethToken.address, + arbToken.address, + amount, + relayer.address + ); + const balanceOfRelayerAfter = await arbToken.balanceOf(relayer.address); + const marginAfter = (await lnDefaultBridgeArb.tgtProviders(providerKey)).margin; + + let successWithdrawAmount = amount; + if (marginBefore.lt(amount)) { + // if withdraw failed + successWithdrawAmount = 0; + } + expect(balanceOfRelayerAfter - balanceOfRelayerBefore).to.equal(successWithdrawAmount); + expect(marginBefore - marginAfter).to.equal(successWithdrawAmount); + return successWithdrawAmount > 0; + } + // user lock - await ethToken.connect(user).approve(eth2arbSource.address, initTokenBalance); - const totalFee = Number(await eth2arbSource.totalFee( + await ethToken.connect(user).approve(lnDefaultBridgeEth.address, initTokenBalance); + const totalFee = Number(await lnDefaultBridgeEth.totalFee( + arbChainId, relayer.address, ethToken.address, + arbToken.address, transferAmount )); const lockTransaction = await transfer(initTransferId, 0); @@ -318,9 +398,11 @@ describe("eth->arb lnv2 layerzero bridge tests", () => { // relay 02 && slash 02 await relay(transferId01, transferId02, blockTimestamp02); // can't relay twice + console.log("test relay twice error"); await expect(relay(transferId01, transferId02, blockTimestamp02)).to.be.revertedWith("transfer has been filled"); // 3. slash when timeout but relayed(timeout) // can slash if relayed when timeout + console.log("test can slash when relayed timeout"); await slash(transferId01, transferId02, blockTimestamp02); // 4. slash when slash has finished // can't slash twice @@ -328,8 +410,11 @@ describe("eth->arb lnv2 layerzero bridge tests", () => { // slash 03 // 5. slash when timeout and not relayed // can slash if not relayed when timeout + console.log("test slash when not relayed and timeout"); await slash(transferId02, transferId03, blockTimestamp03); + expect(await withdraw(15000)).to.equal(false); + expect(await withdraw(5000)).to.equal(true); console.log("ln bridge test finished"); }); });