diff --git a/script/11_SetPeers.s.sol b/script/11_SetPeers.s.sol index d4614158..9ddce3d8 100644 --- a/script/11_SetPeers.s.sol +++ b/script/11_SetPeers.s.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.19; import {Bootstrap} from "../src/core/Bootstrap.sol"; import {ExocoreGateway} from "../src/core/ExocoreGateway.sol"; -import {GatewayStorage} from "../src/storage/GatewayStorage.sol"; +import {Action, GatewayStorage} from "../src/storage/GatewayStorage.sol"; import {BaseScript} from "./BaseScript.sol"; import "forge-std/Script.sol"; @@ -51,8 +51,7 @@ contract SetPeersAndUpgrade is BaseScript { vm.selectFork(exocore); vm.startBroadcast(exocoreValidatorSet.privateKey); - uint256 nativeFee = - exocoreGateway.quote(clientChainId, abi.encodePacked(GatewayStorage.Action.REQUEST_MARK_BOOTSTRAP, "")); + uint256 nativeFee = exocoreGateway.quote(clientChainId, abi.encodePacked(Action.REQUEST_MARK_BOOTSTRAP, "")); exocoreGateway.markBootstrap{value: nativeFee}(clientChainId); } diff --git a/script/13_DepositValidator.s.sol b/script/13_DepositValidator.s.sol index 1e3af495..580d53e4 100644 --- a/script/13_DepositValidator.s.sol +++ b/script/13_DepositValidator.s.sol @@ -5,7 +5,7 @@ import "../src/interfaces/IClientChainGateway.sol"; import "../src/interfaces/IExocoreGateway.sol"; import "../src/interfaces/IVault.sol"; -import "../src/storage/GatewayStorage.sol"; +import {Action, GatewayStorage} from "../src/storage/GatewayStorage.sol"; import "@beacon-oracle/contracts/src/EigenLayerBeaconOracle.sol"; import "@layerzero-v2/protocol/contracts/interfaces/ILayerZeroEndpointV2.sol"; import "@layerzero-v2/protocol/contracts/libs/AddressCast.sol"; @@ -74,7 +74,7 @@ contract DepositScript is BaseScript { vm.startBroadcast(depositor.privateKey); bytes memory msg_ = abi.encodePacked( - GatewayStorage.Action.REQUEST_DEPOSIT, + Action.REQUEST_DEPOSIT_LST, abi.encodePacked(bytes32(bytes20(VIRTUAL_STAKED_ETH_ADDRESS))), abi.encodePacked(bytes32(bytes20(depositor.addr))), uint256(_getEffectiveBalance(validatorContainer)) * GWEI_TO_WEI diff --git a/script/3_Setup.s.sol b/script/3_Setup.s.sol index 90e47638..8e2c5f72 100644 --- a/script/3_Setup.s.sol +++ b/script/3_Setup.s.sol @@ -1,6 +1,6 @@ pragma solidity ^0.8.19; -import {GatewayStorage} from "../src/storage/GatewayStorage.sol"; +import {Action, GatewayStorage} from "../src/storage/GatewayStorage.sol"; import "../src/interfaces/IClientChainGateway.sol"; import "../src/interfaces/IExocoreGateway.sol"; @@ -121,9 +121,7 @@ contract SetupScript is BaseScript { for (uint256 i = 0; i < whitelistTokensBytes32.length; i++) { nativeFee = exocoreGateway.quote( clientChainId, - abi.encodePacked( - GatewayStorage.Action.REQUEST_ADD_WHITELIST_TOKEN, abi.encodePacked(whitelistTokensBytes32[i]) - ) + abi.encodePacked(Action.REQUEST_ADD_WHITELIST_TOKEN, abi.encodePacked(whitelistTokensBytes32[i])) ); exocoreGateway.addWhitelistToken{value: nativeFee}( clientChainId, diff --git a/script/4_Deposit.s.sol b/script/4_Deposit.s.sol index 79fbf66b..e3c81492 100644 --- a/script/4_Deposit.s.sol +++ b/script/4_Deposit.s.sol @@ -4,7 +4,7 @@ import "../src/interfaces/IClientChainGateway.sol"; import "../src/interfaces/IExocoreGateway.sol"; import "../src/interfaces/IVault.sol"; -import "../src/storage/GatewayStorage.sol"; +import {Action, GatewayStorage} from "../src/storage/GatewayStorage.sol"; import {BaseScript} from "./BaseScript.sol"; import "@layerzero-v2/protocol/contracts/interfaces/ILayerZeroEndpointV2.sol"; @@ -57,7 +57,7 @@ contract DepositScript is BaseScript { function run() public { bytes memory msg_ = abi.encodePacked( - GatewayStorage.Action.REQUEST_DEPOSIT, + Action.REQUEST_DEPOSIT_LST, abi.encodePacked(bytes32(bytes20(address(restakeToken)))), abi.encodePacked(bytes32(bytes20(depositor.addr))), uint256(DEPOSIT_AMOUNT) diff --git a/script/5_Withdraw.s.sol b/script/5_Withdraw.s.sol index c886d0e6..6364d766 100644 --- a/script/5_Withdraw.s.sol +++ b/script/5_Withdraw.s.sol @@ -5,7 +5,7 @@ import "../src/interfaces/IClientChainGateway.sol"; import "../src/interfaces/IExocoreGateway.sol"; import "../src/interfaces/IVault.sol"; -import "../src/storage/GatewayStorage.sol"; +import {Action, GatewayStorage} from "../src/storage/GatewayStorage.sol"; import {BaseScript} from "./BaseScript.sol"; import "@layerzero-v2/protocol/contracts/interfaces/ILayerZeroEndpointV2.sol"; @@ -57,7 +57,7 @@ contract DepositScript is BaseScript { function run() public { bytes memory msg_ = abi.encodePacked( - GatewayStorage.Action.REQUEST_WITHDRAW_PRINCIPAL_FROM_EXOCORE, + Action.REQUEST_WITHDRAW_LST, abi.encodePacked(bytes32(bytes20(address(restakeToken)))), abi.encodePacked(bytes32(bytes20(depositor.addr))), uint256(WITHDRAW_AMOUNT) diff --git a/script/TestPrecompileErrorFixed.s.sol b/script/TestPrecompileErrorFixed.s.sol index 7a0660bd..742560e2 100644 --- a/script/TestPrecompileErrorFixed.s.sol +++ b/script/TestPrecompileErrorFixed.s.sol @@ -8,7 +8,7 @@ import "../src/interfaces/IVault.sol"; import "../src/interfaces/precompiles/IAssets.sol"; import "../src/interfaces/precompiles/IClaimReward.sol"; import "../src/interfaces/precompiles/IDelegation.sol"; -import "../src/storage/GatewayStorage.sol"; +import {Action, GatewayStorage} from "../src/storage/GatewayStorage.sol"; import {NonShortCircuitEndpointV2Mock} from "../test/mocks/NonShortCircuitEndpointV2Mock.sol"; import {BaseScript} from "./BaseScript.sol"; @@ -73,7 +73,7 @@ contract DepositScript is BaseScript { function run() public { bytes memory depositMsg = abi.encodePacked( - GatewayStorage.Action.REQUEST_DEPOSIT, + Action.REQUEST_DEPOSIT_LST, abi.encodePacked(bytes32(bytes20(address(restakeToken)))), abi.encodePacked(bytes32(bytes20(depositor.addr))), uint256(TEST_DEPOSIT_AMOUNT) @@ -94,7 +94,7 @@ contract DepositScript is BaseScript { vm.stopBroadcast(); bytes memory withdrawMsg = abi.encodePacked( - GatewayStorage.Action.REQUEST_WITHDRAW_PRINCIPAL_FROM_EXOCORE, + Action.REQUEST_WITHDRAW_LST, abi.encodePacked(bytes32(bytes20(address(restakeToken)))), abi.encodePacked(bytes32(bytes20(depositor.addr))), uint256(TEST_WITHDRAWAL_AMOUNT) diff --git a/script/TestPrecompileErrorFixed_Deploy.s.sol b/script/TestPrecompileErrorFixed_Deploy.s.sol index 2300e0c5..83bbaca4 100644 --- a/script/TestPrecompileErrorFixed_Deploy.s.sol +++ b/script/TestPrecompileErrorFixed_Deploy.s.sol @@ -5,7 +5,7 @@ import "../src/interfaces/IClientChainGateway.sol"; import "../src/interfaces/IExocoreGateway.sol"; import "../src/interfaces/IVault.sol"; -import "../src/storage/GatewayStorage.sol"; +import {Action, GatewayStorage} from "../src/storage/GatewayStorage.sol"; import {NonShortCircuitEndpointV2Mock} from "../test/mocks/NonShortCircuitEndpointV2Mock.sol"; import {BaseScript} from "./BaseScript.sol"; diff --git a/src/core/BaseRestakingController.sol b/src/core/BaseRestakingController.sol index a3eb11cb..a6ca276f 100644 --- a/src/core/BaseRestakingController.sol +++ b/src/core/BaseRestakingController.sol @@ -6,6 +6,7 @@ import {IExoCapsule} from "../interfaces/IExoCapsule.sol"; import {IVault} from "../interfaces/IVault.sol"; import {MessagingFee, MessagingReceipt, OAppSenderUpgradeable} from "../lzApp/OAppSenderUpgradeable.sol"; import {ClientChainGatewayStorage} from "../storage/ClientChainGatewayStorage.sol"; +import {Action} from "../storage/GatewayStorage.sol"; import {OptionsBuilder} from "@layerzero-v2/oapp/contracts/oapp/libs/OptionsBuilder.sol"; @@ -40,7 +41,7 @@ abstract contract BaseRestakingController is nonReentrant { require(recipient != address(0), "BaseRestakingController: recipient address cannot be empty or zero address"); - if (token == VIRTUAL_STAKED_ETH_ADDRESS) { + if (token == VIRTUAL_NST_ADDRESS) { IExoCapsule capsule = _getCapsule(msg.sender); capsule.withdraw(amount, payable(recipient)); } else { diff --git a/src/core/Bootstrap.sol b/src/core/Bootstrap.sol index e83e43f7..9a21543b 100644 --- a/src/core/Bootstrap.sol +++ b/src/core/Bootstrap.sol @@ -22,7 +22,9 @@ import {ITokenWhitelister} from "../interfaces/ITokenWhitelister.sol"; import {IVault} from "../interfaces/IVault.sol"; import {Errors} from "../libraries/Errors.sol"; + import {BootstrapStorage} from "../storage/BootstrapStorage.sol"; +import {Action} from "../storage/GatewayStorage.sol"; import {BootstrapLzReceiver} from "./BootstrapLzReceiver.sol"; /// @title Bootstrap @@ -213,7 +215,7 @@ contract Bootstrap is // whitelist, it means that it is missing a vault. we do not need to check for a // pre-existing vault. however, we still do ensure that the vault is not deployed // for restaking natively staked ETH. - if (token != VIRTUAL_STAKED_ETH_ADDRESS) { + if (token != VIRTUAL_NST_ADDRESS) { // setting a tvlLimit higher than the supply is permitted. // it allows for some margin for minting of the token, and lets us use // a value of type(uint256).max to indicate no limit. @@ -438,7 +440,7 @@ contract Bootstrap is /// @inheritdoc ILSTRestakingController /// @dev This is not yet supported. function withdrawRewardFromExocore(address, uint256) external payable override beforeLocked whenNotPaused { - revert NotYetSupported(); + revert Errors.NotYetSupported(); } /// @inheritdoc IBaseRestakingController diff --git a/src/core/BootstrapLzReceiver.sol b/src/core/BootstrapLzReceiver.sol index cc8240d5..7e5ae528 100644 --- a/src/core/BootstrapLzReceiver.sol +++ b/src/core/BootstrapLzReceiver.sol @@ -2,8 +2,10 @@ pragma solidity ^0.8.19; import {Errors} from "../libraries/Errors.sol"; + import {OAppReceiverUpgradeable, Origin} from "../lzApp/OAppReceiverUpgradeable.sol"; import {BootstrapStorage} from "../storage/BootstrapStorage.sol"; +import {Action} from "../storage/GatewayStorage.sol"; import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; /// @title BootstrapLzReceiver @@ -24,7 +26,7 @@ abstract contract BootstrapLzReceiver is PausableUpgradeable, OAppReceiverUpgrad /// @inheritdoc OAppReceiverUpgradeable function _lzReceive(Origin calldata _origin, bytes calldata payload) internal virtual override { if (_origin.srcEid != EXOCORE_CHAIN_ID) { - revert UnexpectedSourceChain(_origin.srcEid); + revert Errors.UnexpectedSourceChain(_origin.srcEid); } _verifyAndUpdateNonce(_origin.srcEid, _origin.sender, _origin.nonce); Action act = Action(uint8(payload[0])); @@ -33,11 +35,11 @@ abstract contract BootstrapLzReceiver is PausableUpgradeable, OAppReceiverUpgrad } bytes4 selector_ = _whiteListFunctionSelectors[act]; if (selector_ == bytes4(0)) { - revert UnsupportedRequest(act); + revert Errors.UnsupportedRequest(act); } (bool success, bytes memory reason) = address(this).call(abi.encodePacked(selector_, abi.encode(payload[1:]))); if (!success) { - revert RequestOrResponseExecuteFailed(act, _origin.nonce, reason); + revert Errors.RequestOrResponseExecuteFailed(act, _origin.nonce, reason); } } diff --git a/src/core/ClientChainGateway.sol b/src/core/ClientChainGateway.sol index 490ef110..c2c5cba8 100644 --- a/src/core/ClientChainGateway.sol +++ b/src/core/ClientChainGateway.sol @@ -15,6 +15,7 @@ import {LSTRestakingController} from "./LSTRestakingController.sol"; import {NativeRestakingController} from "./NativeRestakingController.sol"; import {Errors} from "../libraries/Errors.sol"; +import {Action} from "../storage/GatewayStorage.sol"; import {IOAppCore} from "@layerzero-v2/oapp/contracts/oapp/interfaces/IOAppCore.sol"; import {OptionsBuilder} from "@layerzero-v2/oapp/contracts/oapp/libs/OptionsBuilder.sol"; import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; diff --git a/src/core/ClientGatewayLzReceiver.sol b/src/core/ClientGatewayLzReceiver.sol index 320c376f..f244dbb4 100644 --- a/src/core/ClientGatewayLzReceiver.sol +++ b/src/core/ClientGatewayLzReceiver.sol @@ -3,8 +3,11 @@ pragma solidity ^0.8.19; import {IExoCapsule} from "../interfaces/IExoCapsule.sol"; import {IVault} from "../interfaces/IVault.sol"; + +import {ActionAttributes} from "../libraries/ActionAttributes.sol"; import {OAppReceiverUpgradeable, Origin} from "../lzApp/OAppReceiverUpgradeable.sol"; import {ClientChainGatewayStorage} from "../storage/ClientChainGatewayStorage.sol"; +import {Action} from "../storage/GatewayStorage.sol"; import {Errors} from "../libraries/Errors.sol"; import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; @@ -15,29 +18,7 @@ import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/security/ /// @dev It is abstract because it does not call the base contract's constructor. abstract contract ClientGatewayLzReceiver is PausableUpgradeable, OAppReceiverUpgradeable, ClientChainGatewayStorage { - /// @dev Thrown when the response is unsupported, that is, no hook has been registered for it. - /// @param act The action that was unsupported. - error UnsupportedResponse(Action act); - - /// @dev Thrown when the response received is unexpected, that is, the request payload for the id cannot be - /// retrieved. - /// @param nonce The nonce of the request. - error UnexpectedResponse(uint64 nonce); - - /// @dev Thrown when deposit fails on the Exocore end. - /// @param token The token address. - /// @param depositor The depositor address. - error DepositShouldNotFailOnExocore(address token, address depositor); - - /// @dev Thrown when the whitelist tokens length is invalid. - /// @param expectedLength The expected length of the request payload. - /// @param actualLength The actual length of the request payload. - error InvalidAddWhitelistTokensRequest(uint256 expectedLength, uint256 actualLength); - - /// @notice Emitted when withdrawal fails on the Exocore end. - /// @param token The token address. - /// @param withdrawer The withdrawer address. - event WithdrawFailedOnExocore(address indexed token, address indexed withdrawer); + using ActionAttributes for Action; /// @dev Ensure that the function is called only from this contract. modifier onlyCalledFromThis() { @@ -52,7 +33,7 @@ abstract contract ClientGatewayLzReceiver is PausableUpgradeable, OAppReceiverUp // slither-disable-next-line reentrancy-no-eth function _lzReceive(Origin calldata _origin, bytes calldata payload) internal virtual override whenNotPaused { if (_origin.srcEid != EXOCORE_CHAIN_ID) { - revert UnexpectedSourceChain(_origin.srcEid); + revert Errors.UnexpectedSourceChain(_origin.srcEid); } _verifyAndUpdateNonce(_origin.srcEid, _origin.sender, _origin.nonce); @@ -63,13 +44,13 @@ abstract contract ClientGatewayLzReceiver is PausableUpgradeable, OAppReceiverUp } else { bytes4 selector_ = _whiteListFunctionSelectors[act]; if (selector_ == bytes4(0)) { - revert UnsupportedRequest(act); + revert Errors.UnsupportedRequest(act); } (bool success, bytes memory reason) = address(this).call(abi.encodePacked(selector_, abi.encode(payload[1:]))); if (!success) { - revert RequestOrResponseExecuteFailed(act, _origin.nonce, reason); + revert Errors.RequestOrResponseExecuteFailed(act, _origin.nonce, reason); } } emit MessageExecuted(act, _origin.nonce); @@ -99,34 +80,28 @@ abstract contract ClientGatewayLzReceiver is PausableUpgradeable, OAppReceiverUp bool success = false; uint256 updatedBalance; - if (_expectBasicResponse(requestAct)) { + if (requestAct.expectBasicResponse()) { success = _decodeBasicResponse(response); - } else if (_expectBalanceResponse(requestAct)) { + } else if (requestAct.expectBalanceResponse()) { (address token, address staker,, uint256 amount) = _decodeCachedRequest(requestAct, cachedRequest); (success, updatedBalance) = _decodeBalanceResponse(response); - if (_isPrincipalType(requestAct)) { - // we assume deposit request must always be successful, thus we should always update balance for deposit - // request - // Notice: Action.REQUEST_DEPOSIT_THEN_DELEGATE_TO is a special operation that is not atomic, since - // deposit should always be successful while delegate could fail for some cases - if (success || _isDeposit(requestAct)) { + if (requestAct.isPrincipalType()) { + if (success || requestAct.isDeposit()) { _updatePrincipalAssetState(requestAct, token, staker, amount, updatedBalance); } } else { - // otherwise this is an operation aimed at reward since Action.REQUEST_WITHDRAW_REWARD_FROM_EXOCORE is - // the only asset operation request that deals with reward instead of principal if (success) { IVault vault = _getVault(token); vault.updateRewardBalance(staker, updatedBalance); - if (_isWithdrawal(requestAct)) { + if (requestAct.isWithdrawal()) { vault.updateWithdrawableBalance(staker, 0, amount); } } } } else { - revert UnsupportedResponse(requestAct); + revert Errors.UnsupportedResponse(requestAct); } delete _registeredRequestActions[requestId]; @@ -149,71 +124,13 @@ abstract contract ClientGatewayLzReceiver is PausableUpgradeable, OAppReceiverUp bytes memory cachedRequest = _registeredRequests[requestId]; if (cachedRequest.length == 0) { - revert UnexpectedResponse(requestId); + revert Errors.UnexpectedResponse(requestId); } Action requestAct = _registeredRequestActions[requestId]; return (requestId, requestAct, cachedRequest); } - /// @dev Checks if the action is an asset operation request. - /// @param action The action to check. - /// @return True if the action is an asset operation request, false otherwise. - function _isAssetOperationRequest(Action action) internal pure returns (bool) { - return action == Action.REQUEST_DEPOSIT || action == Action.REQUEST_WITHDRAW_PRINCIPAL_FROM_EXOCORE - || action == Action.REQUEST_WITHDRAW_REWARD_FROM_EXOCORE; - } - - /// @dev Checks if the action is a staking operation request. - /// @param action The action to check. - /// @return True if the action is a staking operation request, false otherwise. - function _isStakingOperationRequest(Action action) internal pure returns (bool) { - return action == Action.REQUEST_DELEGATE_TO || action == Action.REQUEST_UNDELEGATE_FROM - || action == Action.REQUEST_DEPOSIT_THEN_DELEGATE_TO; - } - - /// @dev Checks if the action is a basic response. - /// @param action The action to check. - /// @return True if the action is a basic response, false otherwise. - // Basic response only includes request execution status, no other informations like balance update - // and it is typically the response of a staking only operations. - function _expectBasicResponse(Action action) internal pure returns (bool) { - return action == Action.REQUEST_DELEGATE_TO || action == Action.REQUEST_UNDELEGATE_FROM; - } - - /// @dev Checks if the action is a balance response. - /// @param action The action to check. - /// @return True if the action is a balance response, false otherwise. - // Balance response includes not only request execution status, but also the balance update informations, - // so it is typically the response of an asset operation. - function _expectBalanceResponse(Action action) internal pure returns (bool) { - return action == Action.REQUEST_DEPOSIT || action == Action.REQUEST_WITHDRAW_PRINCIPAL_FROM_EXOCORE - || action == Action.REQUEST_WITHDRAW_REWARD_FROM_EXOCORE || action == Action.REQUEST_DEPOSIT_THEN_DELEGATE_TO; - } - - /// @dev Checks if the action is a principal type. - /// @param action The action to check. - /// @return True if the action is a principal type, false otherwise. - function _isPrincipalType(Action action) internal pure returns (bool) { - return action == Action.REQUEST_DEPOSIT || action == Action.REQUEST_WITHDRAW_PRINCIPAL_FROM_EXOCORE - || action == Action.REQUEST_DEPOSIT_THEN_DELEGATE_TO; - } - - /// @dev Checks if the action is a withdrawal (both principal and reward). - /// @param action The action to check. - /// @return True if the action is a withdrawal, false otherwise. - function _isWithdrawal(Action action) internal pure returns (bool) { - return action == Action.REQUEST_WITHDRAW_PRINCIPAL_FROM_EXOCORE - || action == Action.REQUEST_WITHDRAW_REWARD_FROM_EXOCORE; - } - - /// @dev Checks if the action is a deposit. - /// @param action The action to check. - /// @return True if the action is a deposit, false otherwise. - function _isDeposit(Action action) internal pure returns (bool) { - return action == Action.REQUEST_DEPOSIT || action == Action.REQUEST_DEPOSIT_THEN_DELEGATE_TO; - } - /// @dev Decodes the cached request. /// @param requestAct The request action /// @param cachedRequest The cached request against that action @@ -226,12 +143,12 @@ abstract contract ClientGatewayLzReceiver is PausableUpgradeable, OAppReceiverUp pure returns (address token, address staker, string memory operator, uint256 amount) { - if (_isAssetOperationRequest(requestAct)) { + if (requestAct.isAssetOperationRequest()) { (token, staker, amount) = abi.decode(cachedRequest, (address, address, uint256)); - } else if (_isStakingOperationRequest(requestAct)) { + } else if (requestAct.isStakingOperationRequest()) { (token, staker, operator, amount) = abi.decode(cachedRequest, (address, address, string, uint256)); } else { - revert UnsupportedRequest(requestAct); + revert Errors.UnsupportedRequest(requestAct); } return (token, staker, operator, amount); @@ -274,18 +191,18 @@ abstract contract ClientGatewayLzReceiver is PausableUpgradeable, OAppReceiverUp uint256 amount, uint256 updatedBalance ) internal { - if (token == VIRTUAL_STAKED_ETH_ADDRESS) { + if (token == VIRTUAL_NST_ADDRESS) { IExoCapsule capsule = _getCapsule(staker); capsule.updatePrincipalBalance(updatedBalance); - if (_isWithdrawal(requestAct)) { + if (requestAct.isWithdrawal()) { capsule.updateWithdrawableBalance(amount); } } else { IVault vault = _getVault(token); vault.updatePrincipalBalance(staker, updatedBalance); - if (_isWithdrawal(requestAct)) { + if (requestAct.isWithdrawal()) { vault.updateWithdrawableBalance(staker, amount, 0); } } @@ -305,7 +222,7 @@ abstract contract ClientGatewayLzReceiver is PausableUpgradeable, OAppReceiverUp // since tokens cannot be removed from the whitelist, it is not possible for a vault // to already exist. however, we should still ensure that a vault is not deployed for // restaking native staked eth. in this case, the tvlLimit is ignored. - if (token != VIRTUAL_STAKED_ETH_ADDRESS) { + if (token != VIRTUAL_NST_ADDRESS) { _deployVault(token, uint256(tvlLimit)); } emit WhitelistTokenAdded(token); diff --git a/src/core/ExocoreGateway.sol b/src/core/ExocoreGateway.sol index 553f0224..9afaac84 100644 --- a/src/core/ExocoreGateway.sol +++ b/src/core/ExocoreGateway.sol @@ -3,6 +3,9 @@ pragma solidity ^0.8.19; import {IExocoreGateway} from "../interfaces/IExocoreGateway.sol"; +import {Errors} from "../libraries/Errors.sol"; +import {Action} from "../storage/GatewayStorage.sol"; + import {ASSETS_CONTRACT, ASSETS_PRECOMPILE_ADDRESS} from "../interfaces/precompiles/IAssets.sol"; import {CLAIM_REWARD_CONTRACT, CLAIM_REWARD_PRECOMPILE_ADDRESS} from "../interfaces/precompiles/IClaimReward.sol"; import {DELEGATION_CONTRACT, DELEGATION_PRECOMPILE_ADDRESS} from "../interfaces/precompiles/IDelegation.sol"; @@ -81,7 +84,8 @@ contract ExocoreGateway is _whiteListFunctionSelectors[Action.REQUEST_CLAIM_REWARD] = this.handleRewardMessage.selector; _whiteListFunctionSelectors[Action.REQUEST_DELEGATE_TO] = this.handleDelegationMessage.selector; _whiteListFunctionSelectors[Action.REQUEST_UNDELEGATE_FROM] = this.handleDelegationMessage.selector; - _whiteListFunctionSelectors[Action.REQUEST_DEPOSIT_THEN_DELEGATE_TO] = this.handleDepositThenDelegateMessage.selector; + _whiteListFunctionSelectors[Action.REQUEST_DEPOSIT_THEN_DELEGATE_TO] = + this.handleDepositThenDelegateMessage.selector; _whiteListFunctionSelectors[Action.REQUEST_ASSOCIATE_OPERATOR] = this.handleAssociationMessage.selector; _whiteListFunctionSelectors[Action.REQUEST_DISSOCIATE_OPERATOR] = this.handleAssociationMessage.selector; } @@ -197,7 +201,7 @@ contract ExocoreGateway is clientChainId, Action.REQUEST_ADD_WHITELIST_TOKEN, abi.encodePacked(token, tvlLimit), false ); } else { - revert AddWhitelistTokenFailed(clientChainId, token); + revert Errors.AddWhitelistTokenFailed(clientChainId, token); } } @@ -215,7 +219,7 @@ contract ExocoreGateway is if (success) { emit WhitelistTokenUpdated(clientChainId, token); } else { - revert UpdateWhitelistTokenFailed(clientChainId, token); + revert Errors.UpdateWhitelistTokenFailed(clientChainId, token); } } @@ -283,7 +287,7 @@ contract ExocoreGateway is (bool success, bool updated) = ASSETS_CONTRACT.registerOrUpdateClientChain(clientChainId, addressLength, name, metaInfo, signatureType); if (!success) { - revert RegisterClientChainToExocoreFailed(clientChainId); + revert Errors.RegisterClientChainToExocoreFailed(clientChainId); } return updated; } @@ -303,13 +307,13 @@ contract ExocoreGateway is bytes calldata payload = message[1:]; bytes4 selector_ = _whiteListFunctionSelectors[act]; if (selector_ == bytes4(0)) { - revert UnsupportedRequest(act); + revert Errors.UnsupportedRequest(act); } (bool success, bytes memory responseOrReason) = address(this).call(abi.encodePacked(selector_, abi.encode(_origin.srcEid, _origin.nonce, act, payload))); if (!success) { - revert RequestExecuteFailed(act, _origin.nonce, responseOrReason); + revert Errors.RequestExecuteFailed(act, _origin.nonce, responseOrReason); } emit MessageExecuted(act, _origin.nonce); @@ -321,31 +325,36 @@ contract ExocoreGateway is /// @param lzNonce The layer zero nonce. /// @param act The action type. /// @param payload The request payload. - function handleDepositMessage(uint32 srcChainId, uint64 lzNonce, Action act, bytes calldata payload) public onlyCalledFromThis { + function handleDepositMessage(uint32 srcChainId, uint64 lzNonce, Action act, bytes calldata payload) + public + onlyCalledFromThis + { bool success; uint256 updatedBalance; - bytes calldata token = (act == Action.REQUEST_DEPOSIT_LST ? payload[:32] : bytes32(bytes20(VIRTUAL_NST_ADDRESS))); + bytes32 token = + (act == Action.REQUEST_DEPOSIT_LST ? bytes32(payload[:32]) : bytes32(bytes20(VIRTUAL_NST_ADDRESS))); bytes calldata depositor = payload[32:64]; uint256 amount = uint256(bytes32(payload[64:96])); if (act == Action.REQUEST_DEPOSIT_LST) { - (success, updatedBalance) = ASSETS_CONTRACT.depositLST(srcChainId, token, depositor, amount); + (success, updatedBalance) = + ASSETS_CONTRACT.depositLST(srcChainId, abi.encodePacked(token), depositor, amount); } else if (act == Action.REQUEST_DEPOSIT_NST) { bytes calldata validatorPubkey = payload[:32]; - (success, updatedBalance) = ASSETS_CONTRACT.depositNST(srcChainId, validatorPubkey, depositor, amount); + (success, updatedBalance) = + ASSETS_CONTRACT.depositNST(srcChainId, abi.encodePacked(validatorPubkey), depositor, amount); } else { - revert MismatchMessageHanlder(); // should never happen though + revert Errors.MismatchMessageHanlder(); // should never happen though } if (!success) { - revert DepositRequestShouldNotFail(srcChainId, lzNonce); // we should not let this happen + revert Errors.DepositRequestShouldNotFail(srcChainId, lzNonce); // we should not let this happen } - - bytes memory response = abi.encodePacked(lzNonce, success, updatedBalance); + bytes memory response = abi.encodePacked(lzNonce, success, updatedBalance); _sendInterchainMsg(srcChainId, Action.RESPOND, response, true); - emit DepositResult(true, bytes32(token), bytes32(depositor), amount); + emit DepositResult(true, token, bytes32(depositor), amount); } /// @notice Responds to a withdraw request from a client chain. @@ -360,12 +369,13 @@ contract ExocoreGateway is { bool success; uint256 updatedBalance; - bytes memory token = (act == Action.REQUEST_WITHDRAW_LST ? payload[:32] : bytes32(bytes20(VIRTUAL_NST_ADDRESS))); + bytes32 token = + (act == Action.REQUEST_WITHDRAW_LST ? bytes32(payload[:32]) : bytes32(bytes20(VIRTUAL_NST_ADDRESS))); bytes memory withdrawer = payload[32:64]; uint256 amount = uint256(bytes32(payload[64:96])); if (act == Action.REQUEST_WITHDRAW_LST) { - try ASSETS_CONTRACT.withdrawLST(srcChainId, token, withdrawer, amount) returns ( + try ASSETS_CONTRACT.withdrawLST(srcChainId, abi.encodePacked(token), withdrawer, amount) returns ( bool success_, uint256 updatedBalance_ ) { success = success_; @@ -375,8 +385,8 @@ contract ExocoreGateway is } } else if (act == Action.REQUEST_WITHDRAW_NST) { bytes calldata validatorPubkey = payload[:32]; - try ASSETS_CONTRACT.withdrawNST(srcChainId, validatorPubkey, withdrawer, amount) returns ( - bool success_, uint256 _updatedBalance + try ASSETS_CONTRACT.withdrawNST(srcChainId, abi.encodePacked(validatorPubkey), withdrawer, amount) returns ( + bool success_, uint256 updatedBalance_ ) { success = success_; updatedBalance = updatedBalance_; @@ -384,22 +394,21 @@ contract ExocoreGateway is emit ExocorePrecompileError(ASSETS_PRECOMPILE_ADDRESS, lzNonce); } } else { - revert MismatchMessageHanlder(); // should never happen though + revert Errors.MismatchMessageHanlder(); // should never happen though } bytes memory response = abi.encodePacked(lzNonce, success, updatedBalance); _sendInterchainMsg(srcChainId, Action.RESPOND, response, true); - emit WithdrawalResult(success, bytes32(token), bytes32(withdrawer), amount); + emit WithdrawalResult(success, token, bytes32(withdrawer), amount); } /// @notice Responds to a reward request from a client chain. /// @dev Can only be called from this contract via low-level call. /// @param srcChainId The source chain id. /// @param lzNonce The layer zero nonce. - /// @param act The action type. /// @param payload The request payload. - function handleRewardMessage(uint32 srcChainId, uint64 lzNonce, Action act, bytes calldata payload) + function handleRewardMessage(uint32 srcChainId, uint64 lzNonce, Action, bytes calldata payload) public onlyCalledFromThis { @@ -410,16 +419,16 @@ contract ExocoreGateway is uint256 amount = uint256(bytes32(payload[64:96])); try CLAIM_REWARD_CONTRACT.claimReward(srcChainId, token, withdrawer, amount) returns ( - bool success_, uint256 _updatedBalance + bool success_, uint256 updatedBalance_ ) { success = success_; - updatedBalance = _updatedBalance; + updatedBalance = updatedBalance_; } catch { emit ExocorePrecompileError(CLAIM_REWARD_PRECOMPILE_ADDRESS, lzNonce); } bytes memory response = abi.encodePacked(lzNonce, success, updatedBalance); - _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, success, updatedBalance), true); + _sendInterchainMsg(srcChainId, Action.RESPOND, response, true); emit ClaimRewardResult(success, bytes32(token), bytes32(withdrawer), amount); } @@ -430,7 +439,10 @@ contract ExocoreGateway is /// @param lzNonce The layer zero nonce. /// @param act The action type. /// @param payload The request payload. - function handleDelegationMessage(uint32 srcChainId, uint64 lzNonce, Action act, bytes calldata payload) public onlyCalledFromThis { + function handleDelegationMessage(uint32 srcChainId, uint64 lzNonce, Action act, bytes calldata payload) + public + onlyCalledFromThis + { bool requestQueued; bytes memory token = payload[:32]; bytes memory delegator = payload[32:64]; @@ -438,36 +450,44 @@ contract ExocoreGateway is uint256 amount = uint256(bytes32(payload[106:138])); if (act == Action.REQUEST_DELEGATE_TO) { - try DELEGATION_CONTRACT.delegate(srcChainId, lzNonce, token, delegator, operator, amount) returns (bool requestQueued_) - { + try DELEGATION_CONTRACT.delegate(srcChainId, lzNonce, token, delegator, operator, amount) returns ( + bool requestQueued_ + ) { requestQueued = requestQueued_; } catch { emit ExocorePrecompileError(DELEGATION_PRECOMPILE_ADDRESS, lzNonce); } } else if (act == Action.REQUEST_UNDELEGATE_FROM) { - try DELEGATION_CONTRACT.undelegate(srcChainId, lzNonce, token, delegator, operator, amount) returns (bool requestQueued_) - { + try DELEGATION_CONTRACT.undelegate(srcChainId, lzNonce, token, delegator, operator, amount) returns ( + bool requestQueued_ + ) { requestQueued = requestQueued_; } catch { emit ExocorePrecompileError(DELEGATION_PRECOMPILE_ADDRESS, lzNonce); } } else { - revert MismatchMessageHanlder(); // should never happen though + revert Errors.MismatchMessageHanlder(); // should never happen though } - bytes memory response = abi.encodePacked(lzNonce, success); + bytes memory response = abi.encodePacked(lzNonce, requestQueued); _sendInterchainMsg(srcChainId, Action.RESPOND, response, true); - emit DelegationRequestReceived(success, act == Action.REQUEST_DELEGATE_TO, bytes32(token), bytes32(delegator), string(operator), amount); + emit DelegationRequestReceived( + requestQueued, + act == Action.REQUEST_DELEGATE_TO, + bytes32(token), + bytes32(delegator), + string(operator), + amount + ); } /// @notice Responds to a deposit-then-delegate request from a client chain. /// @dev Can only be called from this contract via low-level call. /// @param srcChainId The source chain id. /// @param lzNonce The layer zero nonce. - /// @param act The action type. /// @param payload The request payload. - function handleDepositThenDelegateMessage(uint32 srcChainId, uint64 lzNonce, Action _, bytes calldata payload) + function handleDepositThenDelegateMessage(uint32 srcChainId, uint64 lzNonce, Action, bytes calldata payload) public onlyCalledFromThis { @@ -485,24 +505,25 @@ contract ExocoreGateway is // for example, you cannot index a bytes memory result from the requestDepositTo call, // if you were to modify it to return bytes and then process them here. - (depositSuccess, updatedBalance) = ASSETS_CONTRACT.deposit(srcChainId, token, depositor, amount); + (depositSuccess, updatedBalance) = ASSETS_CONTRACT.depositLST(srcChainId, token, depositor, amount); if (!depositSuccess) { - revert DepositRequestShouldNotFail(srcChainId, lzNonce); // we should not let this happen + revert Errors.DepositRequestShouldNotFail(srcChainId, lzNonce); // we should not let this happen } emit DepositResult(true, bytes32(token), bytes32(depositor), amount); - try DELEGATION_CONTRACT.delegate(srcChainId, lzNonce, token, depositor, operator, amount) - returns (bool requestQueued_) { + try DELEGATION_CONTRACT.delegate(srcChainId, lzNonce, token, depositor, operator, amount) returns ( + bool requestQueued_ + ) { delegateRequestQueued = requestQueued_; } catch { emit ExocorePrecompileError(DELEGATION_PRECOMPILE_ADDRESS, lzNonce); } - emit DelegationRequestReceived(delegateRequestQueued, true,bytes32(token), bytes32(depositor), string(operator), amount); + emit DelegationRequestReceived( + delegateRequestQueued, true, bytes32(token), bytes32(depositor), string(operator), amount + ); bytes memory response = abi.encodePacked(lzNonce, delegateRequestQueued, updatedBalance); - _sendInterchainMsg( - srcChainId, Action.RESPOND, response, true - ); + _sendInterchainMsg(srcChainId, Action.RESPOND, response, true); } /// @notice Handles the associating operator request, and no response would be returned. @@ -516,9 +537,9 @@ contract ExocoreGateway is onlyCalledFromThis { bool success; + bytes calldata staker = payload[:32]; if (act == Action.REQUEST_ASSOCIATE_OPERATOR) { - bytes calldata staker = payload[:32]; bytes calldata operator = payload[32:74]; try DELEGATION_CONTRACT.associateOperatorWithStaker(srcChainId, staker, operator) returns (bool success_) { @@ -527,18 +548,16 @@ contract ExocoreGateway is emit ExocorePrecompileError(DELEGATION_PRECOMPILE_ADDRESS, lzNonce); } } else if (act == Action.REQUEST_DISSOCIATE_OPERATOR) { - bytes calldata staker = payload[:32]; - try DELEGATION_CONTRACT.dissociateOperatorFromStaker(srcChainId, staker) returns (bool success_) { success = success_; } catch { emit ExocorePrecompileError(DELEGATION_PRECOMPILE_ADDRESS, lzNonce); } } else { - revert MismatchMessageHanlder(); // should never happen though + revert Errors.MismatchMessageHanlder(); // should never happen though } - emit AssociationResult(result, act == Action.REQUEST_ASSOCIATE_OPERATOR, bytes32(staker)); + emit AssociationResult(success, act == Action.REQUEST_ASSOCIATE_OPERATOR, bytes32(staker)); } /// @dev Sends an interchain message to the client chain. diff --git a/src/core/LSTRestakingController.sol b/src/core/LSTRestakingController.sol index 7e44ffdd..5de77343 100644 --- a/src/core/LSTRestakingController.sol +++ b/src/core/LSTRestakingController.sol @@ -4,6 +4,8 @@ pragma solidity ^0.8.19; import {ILSTRestakingController} from "../interfaces/ILSTRestakingController.sol"; import {IVault} from "../interfaces/IVault.sol"; + +import {Action} from "../storage/GatewayStorage.sol"; import {BaseRestakingController} from "./BaseRestakingController.sol"; import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; @@ -90,7 +92,7 @@ abstract contract LSTRestakingController is bytes memory actionArgs = abi.encodePacked(bytes32(bytes20(token)), bytes32(bytes20(msg.sender)), bytes(operator), amount); bytes memory encodedRequest = abi.encode(token, msg.sender, operator, amount); - _processRequest(Action.REQUEST_DEPOSIT_LST_THEN_DELEGATE, actionArgs, encodedRequest); + _processRequest(Action.REQUEST_DEPOSIT_THEN_DELEGATE_TO, actionArgs, encodedRequest); } } diff --git a/src/core/NativeRestakingController.sol b/src/core/NativeRestakingController.sol index d665224d..2de32731 100644 --- a/src/core/NativeRestakingController.sol +++ b/src/core/NativeRestakingController.sol @@ -5,6 +5,8 @@ import {IExoCapsule} from "../interfaces/IExoCapsule.sol"; import {INativeRestakingController} from "../interfaces/INativeRestakingController.sol"; import {BeaconChainProofs} from "../libraries/BeaconChainProofs.sol"; import {ValidatorContainer} from "../libraries/ValidatorContainer.sol"; + +import {Action} from "../storage/GatewayStorage.sol"; import {BaseRestakingController} from "./BaseRestakingController.sol"; import {Errors} from "../libraries/Errors.sol"; @@ -31,7 +33,7 @@ abstract contract NativeRestakingController is /// @dev Ensures that native restaking is enabled for this contract. modifier nativeRestakingEnabled() { - if (!isWhitelistedToken[VIRTUAL_STAKED_ETH_ADDRESS]) { + if (!isWhitelistedToken[VIRTUAL_NST_ADDRESS]) { revert Errors.NativeRestakingControllerNotWhitelisted(); } _; @@ -101,9 +103,8 @@ abstract contract NativeRestakingController is uint256 depositValue = capsule.verifyDepositProof(validatorContainer, proof); bytes32 validatorPubkey = validatorContainer.getPubkey(); - bytes memory actionArgs = - abi.encodePacked(validatorPubkey, bytes32(bytes20(msg.sender)), depositValue); - bytes memory encodedRequest = abi.encode(validatorPubkey, msg.sender, depositValue); + bytes memory actionArgs = abi.encodePacked(validatorPubkey, bytes32(bytes20(msg.sender)), depositValue); + bytes memory encodedRequest = abi.encode(VIRTUAL_NST_ADDRESS, msg.sender, depositValue); _processRequest(Action.REQUEST_DEPOSIT_NST, actionArgs, encodedRequest); } @@ -124,10 +125,8 @@ abstract contract NativeRestakingController is if (!partialWithdrawal) { // request full withdraw bytes32 validatorPubkey = validatorContainer.getPubkey(); - bytes memory actionArgs = abi.encodePacked( - validatorPubkey, bytes32(bytes20(msg.sender)), withdrawalAmount - ); - bytes memory encodedRequest = abi.encode(validatorPubkey, msg.sender, withdrawalAmount); + bytes memory actionArgs = abi.encodePacked(validatorPubkey, bytes32(bytes20(msg.sender)), withdrawalAmount); + bytes memory encodedRequest = abi.encode(VIRTUAL_NST_ADDRESS, msg.sender, withdrawalAmount); _processRequest(Action.REQUEST_WITHDRAW_NST, actionArgs, encodedRequest); } diff --git a/src/interfaces/precompiles/IAssets.sol b/src/interfaces/precompiles/IAssets.sol index 1df8ced8..ff33a78e 100644 --- a/src/interfaces/precompiles/IAssets.sol +++ b/src/interfaces/precompiles/IAssets.sol @@ -23,9 +23,12 @@ interface IAssets { /// @param assetsAddress The client chain asset address /// @param stakerAddress The staker address /// @param opAmount The amount to deposit - function depositLST(uint32 clientChainID, bytes memory assetsAddress, bytes memory stakerAddress, uint256 opAmount) - external - returns (bool success, uint256 latestAssetState); + function depositLST( + uint32 clientChainID, + bytes calldata assetsAddress, + bytes calldata stakerAddress, + uint256 opAmount + ) external returns (bool success, uint256 latestAssetState); /// TRANSACTIONS /// @dev deposit the client chain assets, native staking tokens, for the staker, @@ -37,9 +40,12 @@ interface IAssets { /// @param validatorPubkey The validator's pubkey /// @param stakerAddress The staker address /// @param opAmount The amount to deposit - function depositNST(uint32 clientChainID, bytes memory validatorPubkey, bytes memory stakerAddress, uint256 opAmount) - external - returns (bool success, uint256 latestAssetState); + function depositNST( + uint32 clientChainID, + bytes calldata validatorPubkey, + bytes calldata stakerAddress, + uint256 opAmount + ) external returns (bool success, uint256 latestAssetState); /// @dev withdraw LST To the staker, that will change the state in withdraw module /// Note that this address cannot be a module account. @@ -51,8 +57,8 @@ interface IAssets { /// @param opAmount The withdraw amount function withdrawLST( uint32 clientChainID, - bytes memory assetsAddress, - bytes memory withdrawAddress, + bytes calldata assetsAddress, + bytes calldata withdrawAddress, uint256 opAmount ) external returns (bool success, uint256 latestAssetState); @@ -66,8 +72,8 @@ interface IAssets { /// @param opAmount The withdraw amount function withdrawNST( uint32 clientChainID, - bytes memory validatorPubkey, - bytes memory withdrawAddress, + bytes calldata validatorPubkey, + bytes calldata withdrawAddress, uint256 opAmount ) external returns (bool success, uint256 latestAssetState); diff --git a/src/interfaces/precompiles/IClaimReward.sol b/src/interfaces/precompiles/IClaimReward.sol index 5fe063df..49e30623 100644 --- a/src/interfaces/precompiles/IClaimReward.sol +++ b/src/interfaces/precompiles/IClaimReward.sol @@ -25,8 +25,8 @@ interface IClaimReward { /// @param opAmount The reward amount function claimReward( uint32 clientChainLzId, - bytes memory assetsAddress, - bytes memory withdrawRewardAddress, + bytes calldata assetsAddress, + bytes calldata withdrawRewardAddress, uint256 opAmount ) external returns (bool success, uint256 latestAssetState); diff --git a/src/interfaces/precompiles/IDelegation.sol b/src/interfaces/precompiles/IDelegation.sol index 8a09b116..8e75eae1 100644 --- a/src/interfaces/precompiles/IDelegation.sol +++ b/src/interfaces/precompiles/IDelegation.sol @@ -25,12 +25,12 @@ interface IDelegation { /// @param stakerAddress The staker address /// @param operatorAddr The operator address that wants to be delegated to /// @param opAmount The delegation amount - function delegateToThroughClientChain( + function delegate( uint32 clientChainID, uint64 lzNonce, - bytes memory assetsAddress, - bytes memory stakerAddress, - bytes memory operatorAddr, + bytes calldata assetsAddress, + bytes calldata stakerAddress, + bytes calldata operatorAddr, uint256 opAmount ) external returns (bool success); @@ -46,12 +46,12 @@ interface IDelegation { /// @param stakerAddress The staker address /// @param operatorAddr The operator address that wants to unDelegate from /// @param opAmount The Undelegation amount - function undelegateFromThroughClientChain( + function undelegate( uint32 clientChainID, uint64 lzNonce, - bytes memory assetsAddress, - bytes memory stakerAddress, - bytes memory operatorAddr, + bytes calldata assetsAddress, + bytes calldata stakerAddress, + bytes calldata operatorAddr, uint256 opAmount ) external returns (bool success); @@ -63,7 +63,7 @@ interface IDelegation { // by layerZero /// @param staker is the EVM address of the staker /// @param operator is the address that is to be marked as the owner. - function associateOperatorWithStaker(uint32 clientChainID, bytes memory staker, bytes memory operator) + function associateOperatorWithStaker(uint32 clientChainID, bytes calldata staker, bytes calldata operator) external returns (bool success); @@ -73,6 +73,8 @@ interface IDelegation { // It might be allocated by Exocore when the client chain isn't supported // by layerZero /// @param staker is the EVM address to remove the marking from. - function dissociateOperatorFromStaker(uint32 clientChainID, bytes memory staker) external returns (bool success); + function dissociateOperatorFromStaker(uint32 clientChainID, bytes calldata staker) + external + returns (bool success); } diff --git a/src/libraries/ActionAttributes.sol b/src/libraries/ActionAttributes.sol new file mode 100644 index 00000000..b8a8a2be --- /dev/null +++ b/src/libraries/ActionAttributes.sol @@ -0,0 +1,65 @@ +pragma solidity ^0.8.19; + +import {Action} from "../storage/GatewayStorage.sol"; + +library ActionAttributes { + + // Bitmaps for operation types + uint256 internal constant ASSET_OPERATION = 1 << 0; + uint256 internal constant STAKING_OPERATION = 1 << 1; + uint256 internal constant PRINCIPAL_TYPE = 1 << 2; + uint256 internal constant REWARD_TYPE = 1 << 3; + uint256 internal constant WITHDRAWAL = 1 << 4; + uint256 internal constant DEPOSIT = 1 << 5; + uint256 internal constant BASIC_RESPONSE = 1 << 6; + uint256 internal constant BALANCE_RESPONSE = 1 << 7; + + function getAttributes(Action action) internal pure returns (uint256) { + if (action == Action.REQUEST_DEPOSIT_LST || action == Action.REQUEST_DEPOSIT_NST) { + return ASSET_OPERATION | PRINCIPAL_TYPE | DEPOSIT | BALANCE_RESPONSE; + } else if (action == Action.REQUEST_WITHDRAW_LST || action == Action.REQUEST_WITHDRAW_NST) { + return ASSET_OPERATION | PRINCIPAL_TYPE | WITHDRAWAL | BALANCE_RESPONSE; + } else if (action == Action.REQUEST_CLAIM_REWARD) { + return ASSET_OPERATION | REWARD_TYPE | WITHDRAWAL | BALANCE_RESPONSE; + } else if (action == Action.REQUEST_DELEGATE_TO || action == Action.REQUEST_UNDELEGATE_FROM) { + return STAKING_OPERATION | BASIC_RESPONSE; + } else if (action == Action.REQUEST_DEPOSIT_THEN_DELEGATE_TO) { + return ASSET_OPERATION | STAKING_OPERATION | PRINCIPAL_TYPE | DEPOSIT | BALANCE_RESPONSE; + } else { + return 0; + } + } + + function isAssetOperationRequest(Action action) internal pure returns (bool) { + return (getAttributes(action) & ASSET_OPERATION) != 0; + } + + function isStakingOperationRequest(Action action) internal pure returns (bool) { + return (getAttributes(action) & STAKING_OPERATION) != 0; + } + + function expectBasicResponse(Action action) internal pure returns (bool) { + return (getAttributes(action) & BASIC_RESPONSE) != 0; + } + + function expectBalanceResponse(Action action) internal pure returns (bool) { + return (getAttributes(action) & BALANCE_RESPONSE) != 0; + } + + function isPrincipalType(Action action) internal pure returns (bool) { + return (getAttributes(action) & PRINCIPAL_TYPE) != 0; + } + + function isRewardType(Action action) internal pure returns (bool) { + return (getAttributes(action) & REWARD_TYPE) != 0; + } + + function isWithdrawal(Action action) internal pure returns (bool) { + return (getAttributes(action) & WITHDRAWAL) != 0; + } + + function isDeposit(Action action) internal pure returns (bool) { + return (getAttributes(action) & DEPOSIT) != 0; + } + +} diff --git a/src/libraries/Errors.sol b/src/libraries/Errors.sol index 8665a832..d065806b 100644 --- a/src/libraries/Errors.sol +++ b/src/libraries/Errors.sol @@ -123,7 +123,7 @@ library Errors { /// @dev Bootstrap: validator name length is zero error BootstrapValidatorNameLengthZero(); - /// @dev Indicates an operation failed because the specified vault does not exist. + /// @dev Indicates an operation failed because the specified vault does not exist. error VaultNotExist(); /// @dev Indicates that an operation which is not yet supported is requested. @@ -166,6 +166,30 @@ library Errors { /// @dev ClientChainLzReceiver: could only be called from this contract itself with low level call error ClientGatewayLzReceiverOnlyCalledFromThis(); + /// @dev Thrown when the response is unsupported, that is, no hook has been registered for it. + /// @param act The action that was unsupported. + error UnsupportedResponse(Action act); + + /// @dev Thrown when the response received is unexpected, that is, the request payload for the id cannot be + /// retrieved. + /// @param nonce The nonce of the request. + error UnexpectedResponse(uint64 nonce); + + /// @dev Thrown when deposit fails on the Exocore end. + /// @param token The token address. + /// @param depositor The depositor address. + error DepositShouldNotFailOnExocore(address token, address depositor); + + /// @dev Thrown when the whitelist tokens length is invalid. + /// @param expectedLength The expected length of the request payload. + /// @param actualLength The actual length of the request payload. + error InvalidAddWhitelistTokensRequest(uint256 expectedLength, uint256 actualLength); + + /// @notice Emitted when withdrawal fails on the Exocore end. + /// @param token The token address. + /// @param withdrawer The withdrawer address. + event WithdrawFailedOnExocore(address indexed token, address indexed withdrawer); + /////////////////////////////// // CustomProxyAdmin Errors // /////////////////////////////// diff --git a/src/storage/ClientChainGatewayStorage.sol b/src/storage/ClientChainGatewayStorage.sol index c0aff261..9298643d 100644 --- a/src/storage/ClientChainGatewayStorage.sol +++ b/src/storage/ClientChainGatewayStorage.sol @@ -3,9 +3,10 @@ pragma solidity ^0.8.19; import {IETHPOSDeposit} from "../interfaces/IETHPOSDeposit.sol"; import {IExoCapsule} from "../interfaces/IExoCapsule.sol"; + +import {Errors} from "../libraries/Errors.sol"; import {BootstrapStorage} from "../storage/BootstrapStorage.sol"; import {Action} from "../storage/GatewayStorage.sol"; -import {Errors} from "../libraries/Errors.sol"; import {IBeacon} from "@openzeppelin/contracts/proxy/beacon/IBeacon.sol"; diff --git a/src/storage/ExocoreGatewayStorage.sol b/src/storage/ExocoreGatewayStorage.sol index 4d46fe90..d725aa8c 100644 --- a/src/storage/ExocoreGatewayStorage.sol +++ b/src/storage/ExocoreGatewayStorage.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import {Action, GatewayStorage} from "./GatewayStorage.sol"; import {Errors} from "../libraries/Errors.sol"; +import {Action, GatewayStorage} from "./GatewayStorage.sol"; /// @title ExocoreGatewayStorage /// @notice Storage used by the ExocoreGateway contract. @@ -95,19 +95,23 @@ contract ExocoreGatewayStorage is GatewayStorage { /// @param token The address of the token. /// @param withdrawer The address of the withdrawer. /// @param amount The amount of the token withdrawn. - event WithdrawalResult( - bool indexed success, bytes32 indexed token, bytes32 indexed withdrawer, uint256 amount - ); + event WithdrawalResult(bool indexed success, bytes32 indexed token, bytes32 indexed withdrawer, uint256 amount); /// @notice Emitted upon delegation. - /// @param accepted Whether the delegation request was accepted, true if it is accepted and being queued, false if it is rejected. + /// @param accepted Whether the delegation request was accepted, true if it is accepted and being queued, false if + /// it is rejected. /// @param isDelegate Whether the delegation request is a delegate request or an undelegate request. /// @param token The address of the token. /// @param delegator The address of the delegator. /// @param operator The Exo account address of the operator. /// @param amount The amount of the token delegated. event DelegationRequestReceived( - bool indexed accepted, bool indexed isDelegate, bytes32 indexed token, bytes32 delegator, string operator, uint256 amount + bool indexed accepted, + bool indexed isDelegate, + bytes32 indexed token, + bytes32 delegator, + string operator, + uint256 amount ); /// @notice Emitted upon handling associating operator request diff --git a/src/storage/GatewayStorage.sol b/src/storage/GatewayStorage.sol index 3e08fd48..3a7a8c21 100644 --- a/src/storage/GatewayStorage.sol +++ b/src/storage/GatewayStorage.sol @@ -47,12 +47,6 @@ contract GatewayStorage { /// @param nativeFee The native fee paid for the message. event MessageSent(Action indexed act, bytes32 packetId, uint64 nonce, uint256 nativeFee); - /// @notice Thrown when the request length is invalid. - /// @param act The action that failed. - /// @param expectedLength The expected length of the request. - /// @param actualLength The actual length of the request. - error InvalidRequestLength(Action act, uint256 expectedLength, uint256 actualLength); - /// @notice Ensures the provided address is a valid exo Bech32 encoded address. /// @param addressToValidate The address to check. modifier isValidBech32Address(string calldata addressToValidate) { diff --git a/test/foundry/Delegation.t.sol b/test/foundry/Delegation.t.sol index 6a274345..5e3d34b2 100644 --- a/test/foundry/Delegation.t.sol +++ b/test/foundry/Delegation.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.19; import "../../src/core/ExocoreGateway.sol"; import "../../src/interfaces/precompiles/IDelegation.sol"; -import "../../src/storage/GatewayStorage.sol"; +import {Action, GatewayStorage} from "../../src/storage/GatewayStorage.sol"; import "../mocks/DelegationMock.sol"; import "./ExocoreDeployer.t.sol"; @@ -25,11 +25,13 @@ contract DelegateTest is ExocoreDeployer { string operatorAddress; - event DelegateResult( - bool indexed success, bytes32 indexed token, bytes32 indexed delegator, string operator, uint256 amount - ); - event UndelegateResult( - bool indexed success, bytes32 indexed token, bytes32 indexed undelegator, string operator, uint256 amount + event DelegationRequestReceived( + bool indexed accepted, + bool indexed isDelegate, + bytes32 indexed token, + bytes32 delegator, + string operator, + uint256 amount ); event DelegateRequestProcessed( uint32 clientChainLzId, @@ -90,7 +92,7 @@ contract DelegateTest is ExocoreDeployer { /// estimate the messaging fee that would be charged from user bytes memory delegateRequestPayload = abi.encodePacked( - GatewayStorage.Action.REQUEST_DELEGATE_TO, + Action.REQUEST_DELEGATE_TO, abi.encodePacked(bytes32(bytes20(address(restakeToken)))), abi.encodePacked(bytes32(bytes20(delegator.addr))), bytes(operatorAddress), @@ -112,7 +114,7 @@ contract DelegateTest is ExocoreDeployer { /// clientGateway should emit MessageSent event vm.expectEmit(true, true, true, true, address(clientGateway)); emit MessageSent( - GatewayStorage.Action.REQUEST_DELEGATE_TO, requestId, outboundNonces[clientChainId]++, requestNativeFee + Action.REQUEST_DELEGATE_TO, requestId, outboundNonces[clientChainId]++, requestNativeFee ); /// delegator call clientGateway to send delegation request @@ -124,7 +126,7 @@ contract DelegateTest is ExocoreDeployer { // endpoint bytes memory delegateResponsePayload = - abi.encodePacked(GatewayStorage.Action.RESPOND, outboundNonces[clientChainId] - 1, true); + abi.encodePacked(Action.RESPOND, outboundNonces[clientChainId] - 1, true); uint256 responseNativeFee = exocoreGateway.quote(clientChainId, delegateResponsePayload); bytes32 responseId = generateUID(outboundNonces[exocoreChainId], false); @@ -151,11 +153,12 @@ contract DelegateTest is ExocoreDeployer { /// exocoreGateway should emit MessageSent event after finishing sending response vm.expectEmit(true, true, true, true, address(exocoreGateway)); - emit MessageSent(GatewayStorage.Action.RESPOND, responseId, outboundNonces[exocoreChainId]++, responseNativeFee); + emit MessageSent(Action.RESPOND, responseId, outboundNonces[exocoreChainId]++, responseNativeFee); /// exocoreGateway contract should emit DelegateResult event vm.expectEmit(true, true, true, true, address(exocoreGateway)); - emit DelegateResult( + emit DelegationRequestReceived( + true, true, bytes32(bytes20(address(restakeToken))), bytes32(bytes20(delegator.addr)), @@ -189,12 +192,12 @@ contract DelegateTest is ExocoreDeployer { /// event vm.expectEmit(true, true, true, true, address(clientGateway)); emit RequestFinished( - GatewayStorage.Action.REQUEST_DELEGATE_TO, + Action.REQUEST_DELEGATE_TO, outboundNonces[clientChainId] - 1, // request id true ); vm.expectEmit(address(clientGateway)); - emit MessageExecuted(GatewayStorage.Action.RESPOND, inboundNonces[clientChainId]++); + emit MessageExecuted(Action.RESPOND, inboundNonces[clientChainId]++); /// relayer should watch the response message and relay it back to client chain vm.startPrank(relayer.addr); @@ -219,7 +222,7 @@ contract DelegateTest is ExocoreDeployer { /// estimate the messaging fee that would be charged from user bytes memory undelegateRequestPayload = abi.encodePacked( - GatewayStorage.Action.REQUEST_UNDELEGATE_FROM, + Action.REQUEST_UNDELEGATE_FROM, abi.encodePacked(bytes32(bytes20(address(restakeToken)))), abi.encodePacked(bytes32(bytes20(delegator.addr))), bytes(operatorAddress), @@ -241,7 +244,7 @@ contract DelegateTest is ExocoreDeployer { /// clientGateway should emit MessageSent event vm.expectEmit(true, true, true, true, address(clientGateway)); emit MessageSent( - GatewayStorage.Action.REQUEST_UNDELEGATE_FROM, requestId, outboundNonces[clientChainId]++, requestNativeFee + Action.REQUEST_UNDELEGATE_FROM, requestId, outboundNonces[clientChainId]++, requestNativeFee ); /// delegator call clientGateway to send undelegation request @@ -253,7 +256,7 @@ contract DelegateTest is ExocoreDeployer { // endpoint bytes memory undelegateResponsePayload = - abi.encodePacked(GatewayStorage.Action.RESPOND, outboundNonces[clientChainId] - 1, true); + abi.encodePacked(Action.RESPOND, outboundNonces[clientChainId] - 1, true); uint256 responseNativeFee = exocoreGateway.quote(clientChainId, undelegateResponsePayload); bytes32 responseId = generateUID(outboundNonces[exocoreChainId], false); @@ -280,12 +283,13 @@ contract DelegateTest is ExocoreDeployer { /// exocoreGateway should emit MessageSent event after finishing sending response vm.expectEmit(true, true, true, true, address(exocoreGateway)); - emit MessageSent(GatewayStorage.Action.RESPOND, responseId, outboundNonces[exocoreChainId]++, responseNativeFee); + emit MessageSent(Action.RESPOND, responseId, outboundNonces[exocoreChainId]++, responseNativeFee); /// exocoreGateway contract should emit UndelegateResult event vm.expectEmit(true, true, true, true, address(exocoreGateway)); - emit UndelegateResult( + emit DelegationRequestReceived( true, + false, bytes32(bytes20(address(restakeToken))), bytes32(bytes20(delegator.addr)), operatorAddress, diff --git a/test/foundry/DepositThenDelegateTo.t.sol b/test/foundry/DepositThenDelegateTo.t.sol index 92f2dc6f..34640c31 100644 --- a/test/foundry/DepositThenDelegateTo.t.sol +++ b/test/foundry/DepositThenDelegateTo.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.19; import "../../src/core/ExocoreGateway.sol"; import "../../src/interfaces/precompiles/IDelegation.sol"; -import "../../src/storage/GatewayStorage.sol"; +import {Action, GatewayStorage} from "../../src/storage/GatewayStorage.sol"; import "../mocks/AssetsMock.sol"; import "../mocks/DelegationMock.sol"; @@ -23,8 +23,13 @@ contract DepositThenDelegateToTest is ExocoreDeployer { // ExocoreGateway emits these two events after handling the request event DepositResult(bool indexed success, bytes32 indexed token, bytes32 indexed depositor, uint256 amount); - event DelegateResult( - bool indexed success, bytes32 indexed token, bytes32 indexed delegator, string operator, uint256 amount + event DelegationRequestReceived( + bool indexed accepted, + bool indexed isDelegate, + bytes32 indexed token, + bytes32 delegator, + string operator, + uint256 amount ); // emitted by the mock delegation contract @@ -103,7 +108,7 @@ contract DepositThenDelegateToTest is ExocoreDeployer { uint256 beforeBalanceVault = restakeToken.balanceOf(address(vault)); requestPayload = abi.encodePacked( - GatewayStorage.Action.REQUEST_DEPOSIT_THEN_DELEGATE_TO, + Action.REQUEST_DEPOSIT_THEN_DELEGATE_TO, abi.encodePacked(bytes32(bytes20(address(restakeToken)))), abi.encodePacked(bytes32(bytes20(delegator))), bytes(operatorAddress), @@ -125,12 +130,16 @@ contract DepositThenDelegateToTest is ExocoreDeployer { ); vm.expectEmit(address(clientGateway)); +<<<<<<< HEAD emit MessageSent( GatewayStorage.Action.REQUEST_DEPOSIT_THEN_DELEGATE_TO, requestId, outboundNonces[clientChainId]++, requestNativeFee ); +======= + emit MessageSent(Action.REQUEST_DEPOSIT_THEN_DELEGATE_TO, requestId, lzNonce, requestNativeFee); +>>>>>>> 68fba84 (feat: use ActionAttributes lib) vm.startPrank(delegator); clientGateway.depositThenDelegateTo{value: requestNativeFee}( @@ -175,6 +184,16 @@ contract DepositThenDelegateToTest is ExocoreDeployer { delegateAmount ); + vm.expectEmit(address(exocoreGateway)); + emit DelegationRequestReceived( + true, + true, + bytes32(bytes20(address(restakeToken))), + bytes32(bytes20(delegator)), + operatorAddress, + delegateAmount + ); + vm.expectEmit(true, true, true, true, address(exocoreLzEndpoint)); // nothing indexed here emit NewPacket( diff --git a/test/foundry/DepositWithdrawPrinciple.t.sol b/test/foundry/DepositWithdrawPrinciple.t.sol index 625a44ae..e06699f7 100644 --- a/test/foundry/DepositWithdrawPrinciple.t.sol +++ b/test/foundry/DepositWithdrawPrinciple.t.sol @@ -6,7 +6,7 @@ import "../../src/core/ExocoreGateway.sol"; import {IExoCapsule} from "../../src/interfaces/IExoCapsule.sol"; import {ILSTRestakingController} from "../../src/interfaces/ILSTRestakingController.sol"; -import "../../src/storage/GatewayStorage.sol"; +import {Action, GatewayStorage} from "../../src/storage/GatewayStorage.sol"; import "./ExocoreDeployer.t.sol"; import "forge-std/Test.sol"; @@ -20,9 +20,7 @@ contract DepositWithdrawPrincipalTest is ExocoreDeployer { using stdStorage for StdStorage; event DepositResult(bool indexed success, bytes32 indexed token, bytes32 indexed depositor, uint256 amount); - event WithdrawPrincipalResult( - bool indexed success, bytes32 indexed token, bytes32 indexed withdrawer, uint256 amount - ); + event WithdrawalResult(bool indexed success, bytes32 indexed token, bytes32 indexed withdrawer, uint256 amount); event Transfer(address indexed from, address indexed to, uint256 amount); event CapsuleCreated(address owner, address capsule); event StakedWithCapsule(address staker, address capsule); @@ -78,7 +76,7 @@ contract DepositWithdrawPrincipalTest is ExocoreDeployer { // estimate l0 relay fee that the user should pay bytes memory depositRequestPayload = abi.encodePacked( - GatewayStorage.Action.REQUEST_DEPOSIT, + Action.REQUEST_DEPOSIT_LST, bytes32(bytes20(address(restakeToken))), bytes32(bytes20(depositor.addr)), depositAmount @@ -171,7 +169,7 @@ contract DepositWithdrawPrincipalTest is ExocoreDeployer { // estimate l0 relay fee that the user should pay bytes memory withdrawRequestPayload = abi.encodePacked( - GatewayStorage.Action.REQUEST_WITHDRAW_PRINCIPAL_FROM_EXOCORE, + Action.REQUEST_WITHDRAW_LST, bytes32(bytes20(address(restakeToken))), bytes32(bytes20(withdrawer.addr)), withdrawAmount @@ -227,7 +225,7 @@ contract DepositWithdrawPrincipalTest is ExocoreDeployer { withdrawResponseNativeFee ); vm.expectEmit(true, true, true, true, address(exocoreGateway)); - emit WithdrawPrincipalResult( + emit WithdrawalResult( true, bytes32(bytes20(address(restakeToken))), bytes32(bytes20(withdrawer.addr)), withdrawAmount ); vm.expectEmit(address(exocoreGateway)); @@ -330,10 +328,7 @@ contract DepositWithdrawPrincipalTest is ExocoreDeployer { } bytes memory depositRequestPayload = abi.encodePacked( - GatewayStorage.Action.REQUEST_DEPOSIT, - bytes32(bytes20(VIRTUAL_STAKED_ETH_ADDRESS)), - bytes32(bytes20(depositor.addr)), - depositAmount + Action.REQUEST_DEPOSIT_NST, _getPubkey(validatorContainer), bytes32(bytes20(depositor.addr)), depositAmount ); uint256 depositRequestNativeFee = clientGateway.quote(depositRequestPayload); bytes32 depositRequestId = generateUID(outboundNonces[clientChainId], true); @@ -505,8 +500,8 @@ contract DepositWithdrawPrincipalTest is ExocoreDeployer { withdrawalAmount = withdrawalAmountGwei * GWEI_TO_WEI; } bytes memory withdrawRequestPayload = abi.encodePacked( - GatewayStorage.Action.REQUEST_WITHDRAW_PRINCIPAL_FROM_EXOCORE, - bytes32(bytes20(VIRTUAL_STAKED_ETH_ADDRESS)), + Action.REQUEST_WITHDRAW_NST, + _getPubkey(validatorContainer), bytes32(bytes20(withdrawer.addr)), withdrawalAmount ); @@ -565,7 +560,7 @@ contract DepositWithdrawPrincipalTest is ExocoreDeployer { // exocore gateway should emit WithdrawPrincipalResult event vm.expectEmit(true, true, true, true, address(exocoreGateway)); - emit WithdrawPrincipalResult( + emit WithdrawalResult( true, bytes32(bytes20(VIRTUAL_STAKED_ETH_ADDRESS)), bytes32(bytes20(withdrawer.addr)), withdrawalAmount ); diff --git a/test/foundry/ExocoreDeployer.t.sol b/test/foundry/ExocoreDeployer.t.sol index 68814178..ac725d2f 100644 --- a/test/foundry/ExocoreDeployer.t.sol +++ b/test/foundry/ExocoreDeployer.t.sol @@ -19,7 +19,7 @@ import "../../src/core/ClientChainGateway.sol"; import "../../src/core/ExoCapsule.sol"; import "../../src/core/ExocoreGateway.sol"; import {Vault} from "../../src/core/Vault.sol"; -import "src/storage/GatewayStorage.sol"; +import {Action, GatewayStorage} from "../../src/storage/GatewayStorage.sol"; import {IVault} from "../../src/interfaces/IVault.sol"; @@ -108,7 +108,7 @@ contract ExocoreDeployer is Test { address addr; } - event MessageSent(GatewayStorage.Action indexed act, bytes32 packetId, uint64 nonce, uint256 nativeFee); + event MessageSent(Action indexed act, bytes32 packetId, uint64 nonce, uint256 nativeFee); event NewPacket(uint32, address, bytes32, uint64, bytes); event WhitelistTokenAdded(address _token); event VaultCreated(address underlyingToken, address vault); @@ -199,7 +199,7 @@ contract ExocoreDeployer is Test { ); vm.expectEmit(address(exocoreGateway)); emit MessageSent( - GatewayStorage.Action.REQUEST_ADD_WHITELIST_TOKEN, + Action.REQUEST_ADD_WHITELIST_TOKEN, requestIds[i], uint64(i) + 1, // nonce nativeFee diff --git a/test/foundry/WithdrawReward.t.sol b/test/foundry/WithdrawReward.t.sol index 48487b24..2c32860d 100644 --- a/test/foundry/WithdrawReward.t.sol +++ b/test/foundry/WithdrawReward.t.sol @@ -1,7 +1,7 @@ pragma solidity ^0.8.19; import "../../src/core/ExocoreGateway.sol"; -import "../../src/storage/GatewayStorage.sol"; +import {Action, GatewayStorage} from "../../src/storage/GatewayStorage.sol"; import "./ExocoreDeployer.t.sol"; import "@layerzero-v2/protocol/contracts/libs/AddressCast.sol"; @@ -13,7 +13,7 @@ contract WithdrawRewardTest is ExocoreDeployer { using AddressCast for address; - event WithdrawRewardResult(bool indexed success, bytes32 indexed token, bytes32 indexed withdrawer, uint256 amount); + event ClaimRewardResult(bool indexed success, bytes32 indexed token, bytes32 indexed withdrawer, uint256 amount); event Transfer(address indexed from, address indexed to, uint256 amount); uint256 constant DEFAULT_ENDPOINT_CALL_GAS_LIMIT = 200_000; @@ -36,7 +36,7 @@ contract WithdrawRewardTest is ExocoreDeployer { // estimate l0 relay fee that the user should pay bytes memory withdrawRequestPayload = abi.encodePacked( - GatewayStorage.Action.REQUEST_WITHDRAW_REWARD_FROM_EXOCORE, + Action.REQUEST_CLAIM_REWARD, bytes32(bytes20(address(restakeToken))), bytes32(bytes20(withdrawer.addr)), withdrawAmount @@ -54,12 +54,16 @@ contract WithdrawRewardTest is ExocoreDeployer { ); // client chain gateway should emit MessageSent event vm.expectEmit(true, true, true, true, address(clientGateway)); +<<<<<<< HEAD emit MessageSent( GatewayStorage.Action.REQUEST_WITHDRAW_REWARD_FROM_EXOCORE, requestId, outboundNonces[clientChainId]++, requestNativeFee ); +======= + emit MessageSent(Action.REQUEST_CLAIM_REWARD, requestId, withdrawRequestNonce, requestNativeFee); +>>>>>>> 68fba84 (feat: use ActionAttributes lib) vm.startPrank(withdrawer.addr); clientGateway.withdrawRewardFromExocore{value: requestNativeFee}(address(restakeToken), withdrawAmount); @@ -70,7 +74,11 @@ contract WithdrawRewardTest is ExocoreDeployer { // exocore gateway should return response message to exocore network layerzero endpoint bytes memory withdrawResponsePayload = +<<<<<<< HEAD abi.encodePacked(GatewayStorage.Action.RESPOND, outboundNonces[clientChainId] - 1, true, uint256(1234)); +======= + abi.encodePacked(Action.RESPOND, withdrawRequestNonce, true, uint256(1234)); +>>>>>>> 68fba84 (feat: use ActionAttributes lib) uint256 responseNativeFee = exocoreGateway.quote(clientChainId, withdrawResponsePayload); bytes32 responseId = generateUID(outboundNonces[exocoreChainId], false); @@ -88,7 +96,7 @@ contract WithdrawRewardTest is ExocoreDeployer { // exocore gateway should emit WithdrawRewardResult event vm.expectEmit(true, true, true, true, address(exocoreGateway)); - emit WithdrawRewardResult( + emit ClaimRewardResult( true, bytes32(bytes20(address(restakeToken))), bytes32(bytes20(withdrawer.addr)), withdrawAmount ); diff --git a/test/foundry/unit/Bootstrap.t.sol b/test/foundry/unit/Bootstrap.t.sol index 40be9673..9db4f327 100644 --- a/test/foundry/unit/Bootstrap.t.sol +++ b/test/foundry/unit/Bootstrap.t.sol @@ -14,7 +14,7 @@ import {MyToken} from "./MyToken.sol"; import {IVault} from "src/interfaces/IVault.sol"; import {Origin} from "src/lzApp/OAppReceiverUpgradeable.sol"; import {BootstrapStorage} from "src/storage/BootstrapStorage.sol"; -import {GatewayStorage} from "src/storage/GatewayStorage.sol"; +import {Action, GatewayStorage} from "src/storage/GatewayStorage.sol"; import "@layerzerolabs/lz-evm-protocol-v2/contracts/libs/GUID.sol"; import "src/libraries/Errors.sol"; @@ -1004,7 +1004,7 @@ contract BootstrapTest is Test { Origin(exocoreChainId, bytes32(bytes20(undeployedExocoreGateway)), nonce), address(bootstrap), generateUID(nonce), - abi.encodePacked(GatewayStorage.Action.REQUEST_MARK_BOOTSTRAP, ""), + abi.encodePacked(Action.REQUEST_MARK_BOOTSTRAP, ""), bytes("") ); vm.stopPrank(); diff --git a/test/foundry/unit/ClientChainGateway.t.sol b/test/foundry/unit/ClientChainGateway.t.sol index 386eb7b8..168ba2c6 100644 --- a/test/foundry/unit/ClientChainGateway.t.sol +++ b/test/foundry/unit/ClientChainGateway.t.sol @@ -25,12 +25,13 @@ import "src/storage/ClientChainGatewayStorage.sol"; import "src/core/ExoCapsule.sol"; import "src/core/ExocoreGateway.sol"; import {Vault} from "src/core/Vault.sol"; -import "src/storage/GatewayStorage.sol"; +import {Action, GatewayStorage} from "src/storage/GatewayStorage.sol"; import {NonShortCircuitEndpointV2Mock} from "../../mocks/NonShortCircuitEndpointV2Mock.sol"; import "src/interfaces/IExoCapsule.sol"; import "src/interfaces/IVault.sol"; +import {Errors} from "src/libraries/Errors.sol"; import "src/utils/BeaconProxyBytecode.sol"; contract SetUp is Test { @@ -67,7 +68,7 @@ contract SetUp is Test { event Paused(address account); event Unpaused(address account); - event MessageSent(GatewayStorage.Action indexed act, bytes32 packetId, uint64 nonce, uint256 nativeFee); + event MessageSent(Action indexed act, bytes32 packetId, uint64 nonce, uint256 nativeFee); function setUp() public virtual { players.push(Player({privateKey: uint256(0x1), addr: vm.addr(uint256(0x1))})); @@ -331,7 +332,7 @@ contract WithdrawNonBeaconChainETHFromCapsule is SetUp { address payable userWithoutCapsule = payable(address(0x123)); vm.prank(userWithoutCapsule); - vm.expectRevert(ClientChainGatewayStorage.CapsuleNotExist.selector); + vm.expectRevert(Errors.CapsuleNotExist.selector); clientGateway.withdrawNonBeaconChainETHFromCapsule(userWithoutCapsule, withdrawAmount); } @@ -394,7 +395,7 @@ contract WithdrawalPrincipalFromExocore is SetUp { function test_revert_withdrawVirtualStakedETH() public { // Try to withdraw VIRTUAL_STAKED_ETH vm.prank(user); - vm.expectRevert(BootstrapStorage.VaultNotExist.selector); + vm.expectRevert(Errors.VaultNotExist.selector); clientGateway.withdrawPrincipalFromExocore(VIRTUAL_STAKED_ETH_ADDRESS, WITHDRAWAL_AMOUNT); } diff --git a/test/foundry/unit/ExocoreGateway.t.sol b/test/foundry/unit/ExocoreGateway.t.sol index 80d88006..375f97c9 100644 --- a/test/foundry/unit/ExocoreGateway.t.sol +++ b/test/foundry/unit/ExocoreGateway.t.sol @@ -24,7 +24,7 @@ import "src/core/ExocoreGateway.sol"; import {Vault} from "src/core/Vault.sol"; import {ExocoreGatewayStorage} from "src/storage/ExocoreGatewayStorage.sol"; -import {GatewayStorage} from "src/storage/GatewayStorage.sol"; +import {Action, GatewayStorage} from "src/storage/GatewayStorage.sol"; contract SetUp is Test { @@ -52,7 +52,7 @@ contract SetUp is Test { event Paused(address account); event Unpaused(address account); event ExocorePrecompileError(address indexed precompile, uint64 nonce); - event MessageSent(GatewayStorage.Action indexed act, bytes32 packetId, uint64 nonce, uint256 nativeFee); + event MessageSent(Action indexed act, bytes32 packetId, uint64 nonce, uint256 nativeFee); function setUp() public virtual { players.push(Player({privateKey: uint256(0x1), addr: vm.addr(uint256(0x1))})); @@ -183,8 +183,7 @@ contract LzReceive is SetUp { uint256 constant WITHDRAWAL_AMOUNT = 123; string operator = "exo13hasr43vvq8v44xpzh0l6yuym4kca98f87j7ac"; - event AssociateOperatorResult(bool indexed success, bytes32 indexed staker, bytes operator); - event DissociateOperatorResult(bool indexed success, bytes32 indexed staker); + event AssociationResult(bool indexed success, bool indexed isAssociate, bytes32 indexed staker); function setUp() public override { super.setUp(); @@ -206,7 +205,7 @@ contract LzReceive is SetUp { abi.encodePacked(bytes32(bytes20(withdrawer.addr))), uint256(WITHDRAWAL_AMOUNT) ); - bytes memory msg_ = abi.encodePacked(GatewayStorage.Action.REQUEST_WITHDRAW_PRINCIPAL_FROM_EXOCORE, payload); + bytes memory msg_ = abi.encodePacked(Action.REQUEST_WITHDRAW_LST, payload); vm.expectEmit(true, true, true, true, address(exocoreGateway)); emit ExocorePrecompileError(ASSETS_PRECOMPILE_ADDRESS, uint64(1)); @@ -225,10 +224,10 @@ contract LzReceive is SetUp { Player memory staker = players[0]; bytes memory payload = abi.encodePacked(abi.encodePacked(bytes32(bytes20(staker.addr))), bytes(operator)); - bytes memory msg_ = abi.encodePacked(GatewayStorage.Action.REQUEST_ASSOCIATE_OPERATOR, payload); + bytes memory msg_ = abi.encodePacked(Action.REQUEST_ASSOCIATE_OPERATOR, payload); vm.expectEmit(true, true, true, true, address(exocoreGateway)); - emit AssociateOperatorResult(true, bytes32(bytes20(staker.addr)), bytes(operator)); + emit AssociationResult(true, true, bytes32(bytes20(staker.addr))); vm.prank(address(exocoreLzEndpoint)); exocoreGateway.lzReceive( @@ -247,10 +246,10 @@ contract LzReceive is SetUp { string memory anotherOperator = "exo13hasr43vvq8v44xpzh0l6yuym4kca98f811111"; bytes memory payload = abi.encodePacked(abi.encodePacked(bytes32(bytes20(staker.addr))), bytes(anotherOperator)); - bytes memory msg_ = abi.encodePacked(GatewayStorage.Action.REQUEST_ASSOCIATE_OPERATOR, payload); + bytes memory msg_ = abi.encodePacked(Action.REQUEST_ASSOCIATE_OPERATOR, payload); vm.expectEmit(true, true, true, true, address(exocoreGateway)); - emit AssociateOperatorResult(false, bytes32(bytes20(staker.addr)), bytes(anotherOperator)); + emit AssociationResult(false, true, bytes32(bytes20(staker.addr))); vm.prank(address(exocoreLzEndpoint)); exocoreGateway.lzReceive( @@ -275,7 +274,7 @@ contract LzReceive is SetUp { uint32 anotherChainId = 123; bytes memory payload = abi.encodePacked(abi.encodePacked(bytes32(bytes20(staker.addr))), bytes(anotherOperator)); - bytes memory msg_ = abi.encodePacked(GatewayStorage.Action.REQUEST_ASSOCIATE_OPERATOR, payload); + bytes memory msg_ = abi.encodePacked(Action.REQUEST_ASSOCIATE_OPERATOR, payload); vm.startPrank(exocoreValidatorSet.addr); exocoreGateway.registerOrUpdateClientChain( @@ -289,7 +288,7 @@ contract LzReceive is SetUp { vm.stopPrank(); vm.expectEmit(true, true, true, true, address(exocoreGateway)); - emit AssociateOperatorResult(true, bytes32(bytes20(staker.addr)), bytes(anotherOperator)); + emit AssociationResult(true, true, bytes32(bytes20(staker.addr))); vm.prank(address(exocoreLzEndpoint)); exocoreGateway.lzReceive( @@ -312,10 +311,10 @@ contract LzReceive is SetUp { Player memory staker = players[0]; bytes memory payload = abi.encodePacked(abi.encodePacked(bytes32(bytes20(staker.addr)))); - bytes memory msg_ = abi.encodePacked(GatewayStorage.Action.REQUEST_DISSOCIATE_OPERATOR, payload); + bytes memory msg_ = abi.encodePacked(Action.REQUEST_DISSOCIATE_OPERATOR, payload); vm.expectEmit(true, true, true, true, address(exocoreGateway)); - emit DissociateOperatorResult(true, bytes32(bytes20(staker.addr))); + emit AssociationResult(true, false, bytes32(bytes20(staker.addr))); vm.prank(address(exocoreLzEndpoint)); exocoreGateway.lzReceive( @@ -331,10 +330,10 @@ contract LzReceive is SetUp { Player memory staker = players[0]; bytes memory payload = abi.encodePacked(abi.encodePacked(bytes32(bytes20(staker.addr)))); - bytes memory msg_ = abi.encodePacked(GatewayStorage.Action.REQUEST_DISSOCIATE_OPERATOR, payload); + bytes memory msg_ = abi.encodePacked(Action.REQUEST_DISSOCIATE_OPERATOR, payload); vm.expectEmit(true, true, true, true, address(exocoreGateway)); - emit DissociateOperatorResult(false, bytes32(bytes20(staker.addr))); + emit AssociationResult(false, false, bytes32(bytes20(staker.addr))); vm.prank(address(exocoreLzEndpoint)); exocoreGateway.lzReceive( @@ -774,8 +773,7 @@ contract MarkBootstrap is SetUp { function setUp() public virtual override { super.setUp(); - nativeFee = - exocoreGateway.quote(clientChainId, abi.encodePacked(GatewayStorage.Action.REQUEST_MARK_BOOTSTRAP, "")); + nativeFee = exocoreGateway.quote(clientChainId, abi.encodePacked(Action.REQUEST_MARK_BOOTSTRAP, "")); } function test_Success() public { diff --git a/test/mocks/AssetsMock.sol b/test/mocks/AssetsMock.sol index 0b0c60b8..d3b7e1f8 100644 --- a/test/mocks/AssetsMock.sol +++ b/test/mocks/AssetsMock.sol @@ -7,6 +7,7 @@ contract AssetsMock is IAssets { address constant VIRTUAL_STAKED_ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; mapping(uint32 => mapping(bytes => mapping(bytes => uint256))) public principalBalances; + mapping(bytes => mapping(bytes => bool)) public inValidatorSet; uint32[] internal chainIds; mapping(uint32 chainId => bool registered) public isRegisteredChain; @@ -17,25 +18,40 @@ contract AssetsMock is IAssets { chainIds.push(clientChainId); } - function depositTo(uint32 clientChainLzId, bytes memory assetsAddress, bytes memory stakerAddress, uint256 opAmount) - external - returns (bool success, uint256 latestAssetState) - { + function depositLST( + uint32 clientChainLzId, + bytes calldata assetsAddress, + bytes calldata stakerAddress, + uint256 opAmount + ) external returns (bool success, uint256 latestAssetState) { require(assetsAddress.length == 32, "invalid asset address"); require(stakerAddress.length == 32, "invalid staker address"); - if (bytes32(assetsAddress) != bytes32(bytes20(VIRTUAL_STAKED_ETH_ADDRESS))) { - require(isRegisteredToken[clientChainLzId][assetsAddress], "the token is not registered before"); - } + require(bytes32(assetsAddress) != bytes32(bytes20(VIRTUAL_STAKED_ETH_ADDRESS)), "only support LST"); + require(isRegisteredToken[clientChainLzId][assetsAddress], "the token is not registered before"); principalBalances[clientChainLzId][assetsAddress][stakerAddress] += opAmount; return (true, principalBalances[clientChainLzId][assetsAddress][stakerAddress]); } - function withdrawPrincipal( + function depositNST( + uint32 clientChainLzId, + bytes calldata validatorPubkey, + bytes calldata stakerAddress, + uint256 opAmount + ) external returns (bool success, uint256 latestAssetState) { + require(stakerAddress.length == 32, "invalid staker address"); + + bytes memory nstAddress = abi.encodePacked(bytes32(bytes20(VIRTUAL_STAKED_ETH_ADDRESS))); + principalBalances[clientChainLzId][nstAddress][stakerAddress] += opAmount; + inValidatorSet[stakerAddress][validatorPubkey] = true; + return (true, principalBalances[clientChainLzId][nstAddress][stakerAddress]); + } + + function withdrawLST( uint32 clientChainLzId, - bytes memory assetsAddress, - bytes memory withdrawer, + bytes calldata assetsAddress, + bytes calldata withdrawer, uint256 opAmount ) external returns (bool success, uint256 latestAssetState) { require(assetsAddress.length == 32, "invalid asset address"); @@ -51,6 +67,21 @@ contract AssetsMock is IAssets { return (true, principalBalances[clientChainLzId][assetsAddress][withdrawer]); } + function withdrawNST( + uint32 clientChainLzId, + bytes calldata validatorPubkey, + bytes calldata withdrawer, + uint256 opAmount + ) external returns (bool success, uint256 latestAssetState) { + require(withdrawer.length == 32, "invalid staker address"); + + bytes memory nstAddress = abi.encodePacked(bytes32(bytes20(VIRTUAL_STAKED_ETH_ADDRESS))); + require(opAmount <= principalBalances[clientChainLzId][nstAddress][withdrawer], "withdraw amount overflow"); + principalBalances[clientChainLzId][nstAddress][withdrawer] -= opAmount; + inValidatorSet[withdrawer][validatorPubkey] = false; + return (true, principalBalances[clientChainLzId][nstAddress][withdrawer]); + } + function getClientChains() external view returns (bool, uint32[] memory) { return (true, chainIds); } diff --git a/test/mocks/ClaimRewardMock.sol b/test/mocks/ClaimRewardMock.sol index 04055919..4f7c94d2 100644 --- a/test/mocks/ClaimRewardMock.sol +++ b/test/mocks/ClaimRewardMock.sol @@ -4,10 +4,12 @@ import {IClaimReward} from "../../src/interfaces/precompiles/IClaimReward.sol"; contract ClaimRewardMock is IClaimReward { - function claimReward(uint32 clientChainLzId, bytes memory assetsAddress, bytes memory withdrawer, uint256 opAmount) - external - returns (bool success, uint256 latestAssetState) - { + function claimReward( + uint32 clientChainLzId, + bytes calldata assetsAddress, + bytes calldata withdrawer, + uint256 opAmount + ) external returns (bool success, uint256 latestAssetState) { require(assetsAddress.length == 32, "invalid asset address"); require(withdrawer.length == 32, "invalid withdrawer address"); return (true, uint256(1234)); diff --git a/test/mocks/DelegationMock.sol b/test/mocks/DelegationMock.sol index 75f3b324..6ac85c3a 100644 --- a/test/mocks/DelegationMock.sol +++ b/test/mocks/DelegationMock.sol @@ -27,12 +27,12 @@ contract DelegationMock is IDelegation { uint256 opAmount ); - function delegateToThroughClientChain( + function delegate( uint32 clientChainLzId, uint64 lzNonce, - bytes memory assetsAddress, - bytes memory stakerAddress, - bytes memory operatorAddr, + bytes calldata assetsAddress, + bytes calldata stakerAddress, + bytes calldata operatorAddr, uint256 opAmount ) external returns (bool success) { if (!AssetsMock(ASSETS_PRECOMPILE_ADDRESS).isRegisteredChain(clientChainLzId)) { @@ -49,12 +49,12 @@ contract DelegationMock is IDelegation { return true; } - function undelegateFromThroughClientChain( + function undelegate( uint32 clientChainLzId, uint64 lzNonce, - bytes memory assetsAddress, - bytes memory stakerAddress, - bytes memory operatorAddr, + bytes calldata assetsAddress, + bytes calldata stakerAddress, + bytes calldata operatorAddr, uint256 opAmount ) external returns (bool success) { if (!AssetsMock(ASSETS_PRECOMPILE_ADDRESS).isRegisteredChain(clientChainLzId)) { @@ -74,7 +74,7 @@ contract DelegationMock is IDelegation { return true; } - function associateOperatorWithStaker(uint32 clientChainId, bytes memory staker, bytes memory operator) + function associateOperatorWithStaker(uint32 clientChainId, bytes calldata staker, bytes calldata operator) external returns (bool success) { @@ -89,7 +89,10 @@ contract DelegationMock is IDelegation { return true; } - function dissociateOperatorFromStaker(uint32 clientChainId, bytes memory staker) external returns (bool success) { + function dissociateOperatorFromStaker(uint32 clientChainId, bytes calldata staker) + external + returns (bool success) + { if (!AssetsMock(ASSETS_PRECOMPILE_ADDRESS).isRegisteredChain(clientChainId)) { return false; } @@ -102,7 +105,7 @@ contract DelegationMock is IDelegation { return true; } - function getDelegateAmount(address delegator, string memory operator, uint32 clientChainLzId, address token) + function getDelegateAmount(address delegator, string calldata operator, uint32 clientChainLzId, address token) public view returns (uint256) @@ -110,7 +113,7 @@ contract DelegationMock is IDelegation { return delegateTo[_addressToBytes(delegator)][bytes(operator)][clientChainLzId][_addressToBytes(token)]; } - function getAssociatedOperator(uint32 clientChainId, bytes memory staker) + function getAssociatedOperator(uint32 clientChainId, bytes calldata staker) external view returns (bytes memory operator) diff --git a/test/mocks/ExocoreGatewayMock.sol b/test/mocks/ExocoreGatewayMock.sol index 2b02ae30..4ccacca5 100644 --- a/test/mocks/ExocoreGatewayMock.sol +++ b/test/mocks/ExocoreGatewayMock.sol @@ -1,6 +1,7 @@ pragma solidity ^0.8.19; import {IExocoreGateway} from "src/interfaces/IExocoreGateway.sol"; +import {Action} from "src/storage/GatewayStorage.sol"; import {IAssets} from "src/interfaces/precompiles/IAssets.sol"; import {IClaimReward} from "src/interfaces/precompiles/IClaimReward.sol"; @@ -77,36 +78,54 @@ contract ExocoreGatewayMock is receive() external payable {} + /// @notice Initializes the ExocoreGateway contract. + /// @param owner_ The address of the contract owner. function initialize(address owner_) external initializer { - require(owner_ != address(0), "ExocoreGateway: owner can not be zero address"); + if (owner_ == address(0)) { + revert Errors.ZeroAddress(); + } _initializeWhitelistFunctionSelectors(); _transferOwnership(owner_); __OAppCore_init_unchained(owner_); __Pausable_init_unchained(); + __ReentrancyGuard_init_unchained(); } + /// @dev Initializes the whitelist function selectors. function _initializeWhitelistFunctionSelectors() private { - _whiteListFunctionSelectors[Action.REQUEST_DEPOSIT] = this.requestDeposit.selector; - _whiteListFunctionSelectors[Action.REQUEST_DELEGATE_TO] = this.requestDelegateTo.selector; - _whiteListFunctionSelectors[Action.REQUEST_UNDELEGATE_FROM] = this.requestUndelegateFrom.selector; - _whiteListFunctionSelectors[Action.REQUEST_WITHDRAW_PRINCIPAL_FROM_EXOCORE] = - this.requestWithdrawPrincipal.selector; - _whiteListFunctionSelectors[Action.REQUEST_WITHDRAW_REWARD_FROM_EXOCORE] = this.requestWithdrawReward.selector; + _whiteListFunctionSelectors[Action.REQUEST_DEPOSIT_LST] = this.handleDepositMessage.selector; + _whiteListFunctionSelectors[Action.REQUEST_DEPOSIT_NST] = this.handleDepositMessage.selector; + _whiteListFunctionSelectors[Action.REQUEST_WITHDRAW_LST] = this.handleWithdrawalMessage.selector; + _whiteListFunctionSelectors[Action.REQUEST_WITHDRAW_NST] = this.handleWithdrawalMessage.selector; + _whiteListFunctionSelectors[Action.REQUEST_CLAIM_REWARD] = this.handleRewardMessage.selector; + _whiteListFunctionSelectors[Action.REQUEST_DELEGATE_TO] = this.handleDelegationMessage.selector; + _whiteListFunctionSelectors[Action.REQUEST_UNDELEGATE_FROM] = this.handleDelegationMessage.selector; + _whiteListFunctionSelectors[Action.REQUEST_DEPOSIT_THEN_DELEGATE_TO] = + this.handleDepositThenDelegateMessage.selector; + _whiteListFunctionSelectors[Action.REQUEST_ASSOCIATE_OPERATOR] = this.handleAssociationMessage.selector; + _whiteListFunctionSelectors[Action.REQUEST_DISSOCIATE_OPERATOR] = this.handleAssociationMessage.selector; } + /// @notice Pauses the contract. function pause() external onlyOwner { _pause(); } + /// @notice Unpauses the contract. function unpause() external onlyOwner { _unpause(); } + /// @notice Sends a request to mark the bootstrap on a chain. + /// @param chainIndex The index of the chain. + /// @dev This function is useful if the bootstrap failed on a chain and needs to be retried. function markBootstrap(uint32 chainIndex) public payable whenNotPaused nonReentrant { _markBootstrap(chainIndex); } + /// @dev Internal function to mark the bootstrap on a chain. + /// @param chainIndex The index of the chain. function _markBootstrap(uint32 chainIndex) internal { // we don't track that a request was sent to a chain to allow for retrials // if the transaction fails on the destination chain @@ -114,23 +133,7 @@ contract ExocoreGatewayMock is emit BootstrapRequestSent(chainIndex); } - /** - * @notice Register the `cientChainId` and othe meta data to Exocore native module or update clien chain's meta data - * according to the `clinetChainId`. - * And set trusted remote peer to enable layerzero messaging or other bridge messaging. - * @param clientChainId The endpoint ID for client chain. - * @param peer The trusted remote contract address to be associated with the corresponding endpoint or some - * authorized signer that would be trusted for - * sending messages from/to source chain to/from this contract - * @param addressLength The bytes length of address type on that client chain - * @param name The name of client chain - * @param metaInfo The arbitrary metadata for client chain - * @param signatureType The cryptographic signature type that client chain supports - * - * @dev Only the owner/admin of the OApp can call this function. - * @dev Indicates that the peer is trusted to send LayerZero messages to this OApp. - * @dev Peer is a bytes32 to accommodate non-evm chains. - */ + /// @inheritdoc IExocoreGateway function registerOrUpdateClientChain( uint32 clientChainId, bytes32 peer, @@ -139,14 +142,15 @@ contract ExocoreGatewayMock is string calldata metaInfo, string calldata signatureType ) public onlyOwner whenNotPaused { - require(clientChainId != uint32(0), "ExocoreGateway: client chain id cannot be zero or empty"); - require(peer != bytes32(0), "ExocoreGateway: peer address cannot be zero or empty"); - require(addressLength != 0, "ExocoreGateway: address length cannot be zero or empty"); - require(bytes(name).length != 0, "ExocoreGateway: name cannot be empty"); - require(bytes(metaInfo).length != 0, "ExocoreGateway: meta data cannot be empty"); - // signature type could be left as empty for current implementation + if ( + clientChainId == uint32(0) || peer == bytes32(0) || addressLength == 0 || bytes(name).length == 0 + || bytes(metaInfo).length == 0 + ) { + revert Errors.ZeroValue(); + } bool updated = _registerOrUpdateClientChain(clientChainId, addressLength, name, metaInfo, signatureType); + // the peer is always set, regardless of `updated` super.setPeer(clientChainId, peer); if (updated) { @@ -156,16 +160,33 @@ contract ExocoreGatewayMock is } } + /// @notice Sets a peer on the destination chain for this contract. + /// @dev This is the LayerZero peer. This function is here for the modifiers + /// as well as checking the registration of the client chain id. + /// @param clientChainId The id of the client chain. + /// @param clientChainGateway The address of the peer as bytes32. function setPeer(uint32 clientChainId, bytes32 clientChainGateway) public override(IOAppCore, OAppCoreUpgradeable) onlyOwner whenNotPaused { + // This check, for the registration of the client chain id, is done here and + // nowhere else. Elsewhere, the precompile is responsible for the checks. + // The precompile is not called here at all, and hence, such a check must be + // performed manually. _validateClientChainIdRegistered(clientChainId); super.setPeer(clientChainId, clientChainGateway); } + /// @inheritdoc IExocoreGateway + /// @notice Tokens can only be normal reward-bearing LST tokens like wstETH, rETH, jitoSol... + /// And they are not intended to be: 1) rebasing tokens like stETH, since we assume staker's + /// balance would not change if nothing is done after deposit, 2) fee-on-transfer tokens, since we + /// assume Vault would account for the amount that staker transfers to it. + /// @notice If we want to activate client chain's native restaking, we should add the corresponding virtual + /// token address to the whitelist, bytes32(bytes20(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE)) for Ethereum + /// native restaking for example. function addWhitelistToken( uint32 clientChainId, bytes32 token, @@ -175,9 +196,6 @@ contract ExocoreGatewayMock is string calldata oracleInfo, uint128 tvlLimit ) external payable onlyOwner whenNotPaused nonReentrant { - if (msg.value == 0) { - revert Errors.ZeroValue(); - } require(clientChainId != 0, "ExocoreGateway: client chain id cannot be zero"); require(token != bytes32(0), "ExocoreGateway: token cannot be zero address"); require(bytes(name).length != 0, "ExocoreGateway: name cannot be empty"); @@ -200,7 +218,7 @@ contract ExocoreGatewayMock is clientChainId, Action.REQUEST_ADD_WHITELIST_TOKEN, abi.encodePacked(token, tvlLimit), false ); } else { - revert AddWhitelistTokenFailed(clientChainId, token); + revert Errors.AddWhitelistTokenFailed(clientChainId, token); } } @@ -217,10 +235,48 @@ contract ExocoreGatewayMock is if (success) { emit WhitelistTokenUpdated(clientChainId, token); } else { - revert UpdateWhitelistTokenFailed(clientChainId, token); + revert Errors.UpdateWhitelistTokenFailed(clientChainId, token); + } + } + + /** + * @notice Associate an Exocore operator with an EVM staker(msg.sender), and this would count staker's delegation + * as operator's self-delegation when staker delegates to operator. + * @param clientChainId The id of client chain + * @param operator The Exocore operator address + * @dev one staker(chainId+stakerAddress) can only associate one operator, while one operator might be associated + * with multiple stakers + */ + function associateOperatorWithEVMStaker(uint32 clientChainId, string calldata operator) + external + whenNotPaused + isValidBech32Address(operator) + { + bytes memory staker = abi.encodePacked(bytes32(bytes20(msg.sender))); + bool success = DELEGATION_CONTRACT.associateOperatorWithStaker(clientChainId, staker, bytes(operator)); + if (!success) { + revert Errors.AssociateOperatorFailed(clientChainId, msg.sender, operator); } } + /** + * @notice Dissociate an Exocore operator from an EVM staker(msg.sender), and this requires that the staker has + * already been associated to operator. + * @param clientChainId The id of client chain + */ + function dissociateOperatorFromEVMStaker(uint32 clientChainId) external whenNotPaused { + bytes memory staker = abi.encodePacked(bytes32(bytes20(msg.sender))); + bool success = DELEGATION_CONTRACT.dissociateOperatorFromStaker(clientChainId, staker); + if (!success) { + revert Errors.DissociateOperatorFailed(clientChainId, msg.sender); + } + } + + /// @dev Validates that the client chain id is registered. + /// @dev This is designed to be called only in the cases wherein the precompile isn't used. + /// @dev In all other situations, it is the responsibility of the precompile to perform such + /// checks. + /// @param clientChainId The client chain id. function _validateClientChainIdRegistered(uint32 clientChainId) internal view { (bool success, bool isRegistered) = ASSETS_CONTRACT.isRegisteredClientChain(clientChainId); if (!success) { @@ -231,6 +287,12 @@ contract ExocoreGatewayMock is } } + /// @dev The internal version of registerOrUpdateClientChain. + /// @param clientChainId The client chain id. + /// @param addressLength The length of the address type on the client chain. + /// @param name The name of the client chain. + /// @param metaInfo The arbitrary metadata for the client chain. + /// @param signatureType The signature type supported by the client chain. function _registerOrUpdateClientChain( uint32 clientChainId, uint8 addressLength, @@ -241,13 +303,13 @@ contract ExocoreGatewayMock is (bool success, bool updated) = ASSETS_CONTRACT.registerOrUpdateClientChain(clientChainId, addressLength, name, metaInfo, signatureType); if (!success) { - revert RegisterClientChainToExocoreFailed(clientChainId); + revert Errors.RegisterClientChainToExocoreFailed(clientChainId); } return updated; } /// @inheritdoc OAppReceiverUpgradeable - function _lzReceive(Origin calldata _origin, bytes calldata payload) + function _lzReceive(Origin calldata _origin, bytes calldata message) internal virtual override @@ -255,127 +317,201 @@ contract ExocoreGatewayMock is nonReentrant { _verifyAndUpdateNonce(_origin.srcEid, _origin.sender, _origin.nonce); + _validateMessageLength(message); - Action act = Action(uint8(payload[0])); + Action act = Action(uint8(message[0])); + bytes calldata payload = message[1:]; bytes4 selector_ = _whiteListFunctionSelectors[act]; if (selector_ == bytes4(0)) { - revert UnsupportedRequest(act); + revert Errors.UnsupportedRequest(act); } (bool success, bytes memory responseOrReason) = - address(this).call(abi.encodePacked(selector_, abi.encode(_origin.srcEid, _origin.nonce, payload[1:]))); + address(this).call(abi.encodePacked(selector_, abi.encode(_origin.srcEid, _origin.nonce, act, payload))); if (!success) { - revert RequestExecuteFailed(act, _origin.nonce, responseOrReason); + revert Errors.RequestExecuteFailed(act, _origin.nonce, responseOrReason); } emit MessageExecuted(act, _origin.nonce); } - function requestDeposit(uint32 srcChainId, uint64 lzNonce, bytes calldata payload) public onlyCalledFromThis { - _validatePayloadLength(payload, DEPOSIT_REQUEST_LENGTH, Action.REQUEST_DEPOSIT); - - bytes memory token = payload[:32]; - bytes memory depositor = payload[32:64]; + /// @notice Responds to a deposit request from a client chain. + /// @dev Can only be called from this contract via low-level call. + /// @param srcChainId The source chain id. + /// @param lzNonce The layer zero nonce. + /// @param act The action type. + /// @param payload The request payload. + function handleDepositMessage(uint32 srcChainId, uint64 lzNonce, Action act, bytes calldata payload) + public + onlyCalledFromThis + { + bool success; + uint256 updatedBalance; + bytes32 token = + (act == Action.REQUEST_DEPOSIT_LST ? bytes32(payload[:32]) : bytes32(bytes20(VIRTUAL_NST_ADDRESS))); + bytes calldata depositor = payload[32:64]; uint256 amount = uint256(bytes32(payload[64:96])); - (bool success, uint256 updatedBalance) = ASSETS_CONTRACT.depositTo(srcChainId, token, depositor, amount); + if (act == Action.REQUEST_DEPOSIT_LST) { + (success, updatedBalance) = + ASSETS_CONTRACT.depositLST(srcChainId, abi.encodePacked(token), depositor, amount); + } else if (act == Action.REQUEST_DEPOSIT_NST) { + bytes calldata validatorPubkey = payload[:32]; + (success, updatedBalance) = + ASSETS_CONTRACT.depositNST(srcChainId, abi.encodePacked(validatorPubkey), depositor, amount); + } else { + revert Errors.MismatchMessageHanlder(); // should never happen though + } + if (!success) { - revert DepositRequestShouldNotFail(srcChainId, lzNonce); + revert Errors.DepositRequestShouldNotFail(srcChainId, lzNonce); // we should not let this happen } - _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, success, updatedBalance), true); + bytes memory response = abi.encodePacked(lzNonce, success, updatedBalance); + + _sendInterchainMsg(srcChainId, Action.RESPOND, response, true); + + emit DepositResult(true, token, bytes32(depositor), amount); } - function requestWithdrawPrincipal(uint32 srcChainId, uint64 lzNonce, bytes calldata payload) + /// @notice Responds to a withdraw request from a client chain. + /// @dev Can only be called from this contract via low-level call. + /// @param srcChainId The source chain id. + /// @param lzNonce The layer zero nonce. + /// @param act The action type. + /// @param payload The request payload. + function handleWithdrawalMessage(uint32 srcChainId, uint64 lzNonce, Action act, bytes calldata payload) public onlyCalledFromThis { - _validatePayloadLength( - payload, WITHDRAW_PRINCIPAL_REQUEST_LENGTH, Action.REQUEST_WITHDRAW_PRINCIPAL_FROM_EXOCORE - ); - - bytes memory token = payload[:32]; + bool success; + uint256 updatedBalance; + bytes32 token = + (act == Action.REQUEST_WITHDRAW_LST ? bytes32(payload[:32]) : bytes32(bytes20(VIRTUAL_NST_ADDRESS))); bytes memory withdrawer = payload[32:64]; uint256 amount = uint256(bytes32(payload[64:96])); - try ASSETS_CONTRACT.withdrawPrincipal(srcChainId, token, withdrawer, amount) returns ( - bool success, uint256 updatedBalance - ) { - _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, success, updatedBalance), true); - } catch { - emit ExocorePrecompileError(ASSETS_PRECOMPILE_ADDRESS, lzNonce); - - _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, false, uint256(0)), true); + if (act == Action.REQUEST_WITHDRAW_LST) { + try ASSETS_CONTRACT.withdrawLST(srcChainId, abi.encodePacked(token), withdrawer, amount) returns ( + bool success_, uint256 updatedBalance_ + ) { + success = success_; + updatedBalance = updatedBalance_; + } catch { + emit ExocorePrecompileError(ASSETS_PRECOMPILE_ADDRESS, lzNonce); + } + } else if (act == Action.REQUEST_WITHDRAW_NST) { + bytes calldata validatorPubkey = payload[:32]; + try ASSETS_CONTRACT.withdrawNST(srcChainId, abi.encodePacked(validatorPubkey), withdrawer, amount) returns ( + bool success_, uint256 updatedBalance_ + ) { + success = success_; + updatedBalance = updatedBalance_; + } catch { + emit ExocorePrecompileError(ASSETS_PRECOMPILE_ADDRESS, lzNonce); + } + } else { + revert Errors.MismatchMessageHanlder(); // should never happen though } + + bytes memory response = abi.encodePacked(lzNonce, success, updatedBalance); + _sendInterchainMsg(srcChainId, Action.RESPOND, response, true); + + emit WithdrawalResult(success, token, bytes32(withdrawer), amount); } - function requestWithdrawReward(uint32 srcChainId, uint64 lzNonce, bytes calldata payload) + /// @notice Responds to a reward request from a client chain. + /// @dev Can only be called from this contract via low-level call. + /// @param srcChainId The source chain id. + /// @param lzNonce The layer zero nonce. + /// @param act The action type. + /// @param payload The request payload. + function handleRewardMessage(uint32 srcChainId, uint64 lzNonce, Action act, bytes calldata payload) public onlyCalledFromThis { - _validatePayloadLength(payload, CLAIM_REWARD_REQUEST_LENGTH, Action.REQUEST_WITHDRAW_REWARD_FROM_EXOCORE); - - bytes memory token = payload[:32]; - bytes memory withdrawer = payload[32:64]; + bool success; + uint256 updatedBalance; + bytes calldata token = payload[:32]; + bytes calldata withdrawer = payload[32:64]; uint256 amount = uint256(bytes32(payload[64:96])); try CLAIM_REWARD_CONTRACT.claimReward(srcChainId, token, withdrawer, amount) returns ( - bool success, uint256 updatedBalance + bool success_, uint256 updatedBalance_ ) { - _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, success, updatedBalance), true); + success = success_; + updatedBalance = updatedBalance_; } catch { emit ExocorePrecompileError(CLAIM_REWARD_PRECOMPILE_ADDRESS, lzNonce); - - _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, false, uint256(0)), true); } - } - - function requestDelegateTo(uint32 srcChainId, uint64 lzNonce, bytes calldata payload) public onlyCalledFromThis { - _validatePayloadLength(payload, DELEGATE_REQUEST_LENGTH, Action.REQUEST_DELEGATE_TO); - bytes memory token = payload[:32]; - bytes memory delegator = payload[32:64]; - bytes memory operator = payload[64:106]; - uint256 amount = uint256(bytes32(payload[106:138])); - - try DELEGATION_CONTRACT.delegateToThroughClientChain(srcChainId, lzNonce, token, delegator, operator, amount) - returns (bool success) { - _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, success), true); - } catch { - emit ExocorePrecompileError(DELEGATION_PRECOMPILE_ADDRESS, lzNonce); + bytes memory response = abi.encodePacked(lzNonce, success, updatedBalance); + _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, success, updatedBalance), true); - _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, false), true); - } + emit ClaimRewardResult(success, bytes32(token), bytes32(withdrawer), amount); } - function requestUndelegateFrom(uint32 srcChainId, uint64 lzNonce, bytes calldata payload) + /// @notice Responds to a delegate request from a client chain. + /// @dev Can only be called from this contract via low-level call. + /// @param srcChainId The source chain id. + /// @param lzNonce The layer zero nonce. + /// @param act The action type. + /// @param payload The request payload. + function handleDelegationMessage(uint32 srcChainId, uint64 lzNonce, Action act, bytes calldata payload) public onlyCalledFromThis { - _validatePayloadLength(payload, UNDELEGATE_REQUEST_LENGTH, Action.REQUEST_UNDELEGATE_FROM); - + bool requestQueued; bytes memory token = payload[:32]; bytes memory delegator = payload[32:64]; bytes memory operator = payload[64:106]; uint256 amount = uint256(bytes32(payload[106:138])); - try DELEGATION_CONTRACT.undelegateFromThroughClientChain( - srcChainId, lzNonce, token, delegator, operator, amount - ) returns (bool success) { - _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, success), true); - } catch { - emit ExocorePrecompileError(DELEGATION_PRECOMPILE_ADDRESS, lzNonce); - - _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, false), true); + if (act == Action.REQUEST_DELEGATE_TO) { + try DELEGATION_CONTRACT.delegate(srcChainId, lzNonce, token, delegator, operator, amount) returns ( + bool requestQueued_ + ) { + requestQueued = requestQueued_; + } catch { + emit ExocorePrecompileError(DELEGATION_PRECOMPILE_ADDRESS, lzNonce); + } + } else if (act == Action.REQUEST_UNDELEGATE_FROM) { + try DELEGATION_CONTRACT.undelegate(srcChainId, lzNonce, token, delegator, operator, amount) returns ( + bool requestQueued_ + ) { + requestQueued = requestQueued_; + } catch { + emit ExocorePrecompileError(DELEGATION_PRECOMPILE_ADDRESS, lzNonce); + } + } else { + revert Errors.MismatchMessageHanlder(); // should never happen though } + + bytes memory response = abi.encodePacked(lzNonce, requestQueued); + _sendInterchainMsg(srcChainId, Action.RESPOND, response, true); + + emit DelegationRequestReceived( + requestQueued, + act == Action.REQUEST_DELEGATE_TO, + bytes32(token), + bytes32(delegator), + string(operator), + amount + ); } - function requestDepositThenDelegateTo(uint32 srcChainId, uint64 lzNonce, bytes calldata payload) + /// @notice Responds to a deposit-then-delegate request from a client chain. + /// @dev Can only be called from this contract via low-level call. + /// @param srcChainId The source chain id. + /// @param lzNonce The layer zero nonce. + /// @param payload The request payload. + function handleDepositThenDelegateMessage(uint32 srcChainId, uint64 lzNonce, Action, bytes calldata payload) public onlyCalledFromThis { - _validatePayloadLength(payload, DEPOSIT_THEN_DELEGATE_REQUEST_LENGTH, Action.REQUEST_DEPOSIT_THEN_DELEGATE_TO); - + bool depositSuccess; + bool delegateRequestQueued; + uint256 updatedBalance; bytes memory token = payload[:32]; bytes memory depositor = payload[32:64]; bytes memory operator = payload[64:106]; @@ -387,21 +523,67 @@ contract ExocoreGatewayMock is // for example, you cannot index a bytes memory result from the requestDepositTo call, // if you were to modify it to return bytes and then process them here. - (bool success, uint256 updatedBalance) = ASSETS_CONTRACT.depositTo(srcChainId, token, depositor, amount); - if (!success) { - revert DepositRequestShouldNotFail(srcChainId, lzNonce); + (depositSuccess, updatedBalance) = ASSETS_CONTRACT.depositLST(srcChainId, token, depositor, amount); + if (!depositSuccess) { + revert Errors.DepositRequestShouldNotFail(srcChainId, lzNonce); // we should not let this happen } - try DELEGATION_CONTRACT.delegateToThroughClientChain(srcChainId, lzNonce, token, depositor, operator, amount) - returns (bool delegateSuccess) { - _sendInterchainMsg( - srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, delegateSuccess, updatedBalance), true - ); + emit DepositResult(true, bytes32(token), bytes32(depositor), amount); + + try DELEGATION_CONTRACT.delegate(srcChainId, lzNonce, token, depositor, operator, amount) returns ( + bool requestQueued_ + ) { + delegateRequestQueued = requestQueued_; } catch { emit ExocorePrecompileError(DELEGATION_PRECOMPILE_ADDRESS, lzNonce); - _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, false, updatedBalance), true); } + emit DelegationRequestReceived( + delegateRequestQueued, true, bytes32(token), bytes32(depositor), string(operator), amount + ); + + bytes memory response = abi.encodePacked(lzNonce, delegateRequestQueued, updatedBalance); + _sendInterchainMsg(srcChainId, Action.RESPOND, response, true); + } + + /// @notice Handles the associating operator request, and no response would be returned. + /// @dev Can only be called from this contract via low-level call. + /// @param srcChainId The source chain id. + /// @param lzNonce The layer zero nonce. + /// @param act The action type. + /// @param payload The request payload. + function handleAssociationMessage(uint32 srcChainId, uint64 lzNonce, Action act, bytes calldata payload) + public + onlyCalledFromThis + { + bool success; + bytes calldata staker = payload[:32]; + + if (act == Action.REQUEST_ASSOCIATE_OPERATOR) { + bytes calldata operator = payload[32:74]; + + try DELEGATION_CONTRACT.associateOperatorWithStaker(srcChainId, staker, operator) returns (bool success_) { + success = success_; + } catch { + emit ExocorePrecompileError(DELEGATION_PRECOMPILE_ADDRESS, lzNonce); + } + } else if (act == Action.REQUEST_DISSOCIATE_OPERATOR) { + try DELEGATION_CONTRACT.dissociateOperatorFromStaker(srcChainId, staker) returns (bool success_) { + success = success_; + } catch { + emit ExocorePrecompileError(DELEGATION_PRECOMPILE_ADDRESS, lzNonce); + } + } else { + revert Errors.MismatchMessageHanlder(); // should never happen though + } + + emit AssociationResult(success, act == Action.REQUEST_ASSOCIATE_OPERATOR, bytes32(staker)); } + /// @dev Sends an interchain message to the client chain. + /// @param srcChainId The chain id of the source chain, from which a message was received, and to which a response + /// is being sent. + /// @param act The action to be performed. + /// @param actionArgs The arguments for the action. + /// @param payByApp If the source for the transaction funds is this contract. function _sendInterchainMsg(uint32 srcChainId, Action act, bytes memory actionArgs, bool payByApp) internal whenNotPaused @@ -412,12 +594,14 @@ contract ExocoreGatewayMock is ).addExecutorOrderedExecutionOption(); MessagingFee memory fee = _quote(srcChainId, payload, options, false); + address refundAddress = payByApp ? address(this) : msg.sender; MessagingReceipt memory receipt = - _lzSend(srcChainId, payload, options, MessagingFee(fee.nativeFee, 0), msg.sender, payByApp); + _lzSend(srcChainId, payload, options, MessagingFee(fee.nativeFee, 0), refundAddress, payByApp); emit MessageSent(act, receipt.guid, receipt.nonce, receipt.fee.nativeFee); } - function quote(uint32 srcChainid, bytes memory _message) public view returns (uint256 nativeFee) { + /// @inheritdoc IExocoreGateway + function quote(uint32 srcChainid, bytes calldata _message) public view returns (uint256 nativeFee) { bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption( DESTINATION_GAS_LIMIT, DESTINATION_MSG_VALUE ).addExecutorOrderedExecutionOption(); @@ -425,6 +609,7 @@ contract ExocoreGatewayMock is return fee.nativeFee; } + /// @inheritdoc OAppReceiverUpgradeable function nextNonce(uint32 srcEid, bytes32 sender) public view diff --git a/test/mocks/PrecompileCallerMock.sol b/test/mocks/PrecompileCallerMock.sol index 9df5b4d5..7a793445 100644 --- a/test/mocks/PrecompileCallerMock.sol +++ b/test/mocks/PrecompileCallerMock.sol @@ -12,7 +12,7 @@ contract PrecompileCallerMock { function deposit(uint256 amount) public { (bool success, bytes memory response) = ASSETS_PRECOMPILE_ADDRESS.call{gas: 216_147}( abi.encodeWithSelector( - ASSETS_CONTRACT.depositTo.selector, + ASSETS_CONTRACT.depositLST.selector, uint32(101), abi.encodePacked(bytes32(bytes20(address(0xdAC17F958D2ee523a2206206994597C13D831ec7)))), abi.encodePacked(bytes32(bytes20(address(0x2)))),