From e01d9e0528f1d59ac73d30f69c0bcc3755929e5a Mon Sep 17 00:00:00 2001 From: Max <82761650+MaxMustermann2@users.noreply.github.com> Date: Tue, 3 Dec 2024 09:40:07 +0530 Subject: [PATCH] fix: use the validatorID instead of pubkey (#124) * fix: use the validatorID instead of pubkey On an Ethereum-based client chain, a validator container (generated as part of the proof) does not have the raw public key stored within it. Rather, it is the hash of the public key. This hash, therefore, cannot be used to query the beacon chain API. This PR proposes an alternative solution of using the `validatorIndex`, that is, the position of the validator amongst all of the validators, be used instead. Note that, according to the beacon spec, this number does not change even as more validators enter or leave the network. The `validatorIndex` is passed to the ExocoreGateway as a unique identifier for it to send to the precompile, which refers to it as the `validatorID`. For other chains, where a validator public key may be directly available, the `validatorID` will be the public key instead of the index. On Ethereum, the public key is 48 bytes (though we pass a uint256 index which is 32 bytes). On Solana, the public key is 32 bytes and on Sui it is 96 bytes. To handle the varying lengths, the public key has been moved to the end of the payload, such that, it is the remainder of the payload after parsing the staker and the amount. BREAKING CHANGE Since the PR modifies the format of the data sent over the wire, it is not compatible with prior deployments of ClientChainGateway and ExocoreGateway. If care is taken to ensure that there are no affected messages currently in-flight and the implementations are updated in tandem, it will be sufficient. * chore: forge fmt * fix(precompile): change param name to ValidatorID * fix: rename pubkey to pubkeyHash In all functions, accept the call to IETH_POS.stake, the pubkey is not used directly. Instead it is the hashed public key. --- src/core/ExoCapsule.sol | 78 ++++++++++----------- src/core/ExocoreGateway.sol | 16 +++-- src/core/NativeRestakingController.sol | 7 +- src/interfaces/precompiles/IAssets.sol | 8 +-- src/libraries/ActionAttributes.sol | 14 ++-- src/libraries/ValidatorContainer.sol | 4 +- src/storage/ExoCapsuleStorage.sol | 8 +-- src/storage/ExocoreGatewayStorage.sol | 12 ++-- test/foundry/DepositWithdrawPrinciple.t.sol | 12 ++-- test/mocks/AssetsMock.sol | 8 +-- test/mocks/ExocoreGatewayMock.sol | 12 ++-- 11 files changed, 96 insertions(+), 83 deletions(-) diff --git a/src/core/ExoCapsule.sol b/src/core/ExoCapsule.sol index 5e364d1f..a809a00a 100644 --- a/src/core/ExoCapsule.sol +++ b/src/core/ExoCapsule.sol @@ -35,21 +35,21 @@ contract ExoCapsule is ReentrancyGuardUpgradeable, ExoCapsuleStorage, IExoCapsul event WithdrawalSuccess(address owner, address recipient, uint256 amount); /// @notice Emitted when a partial withdrawal claim is successfully redeemed - /// @param pubkey The validator's BLS12-381 public key. + /// @param pubkeyHash The validator's BLS12-381 public key hash. /// @param withdrawalEpoch The epoch at which the withdrawal was made. /// @param recipient The address of the recipient of the withdrawal. /// @param partialWithdrawalAmountGwei The amount of the partial withdrawal in Gwei. event PartialWithdrawalRedeemed( - bytes32 pubkey, uint256 withdrawalEpoch, address indexed recipient, uint64 partialWithdrawalAmountGwei + bytes32 pubkeyHash, uint256 withdrawalEpoch, address indexed recipient, uint64 partialWithdrawalAmountGwei ); /// @notice Emitted when an ETH validator is prove to have fully withdrawn from the beacon chain - /// @param pubkey The validator's BLS12-381 public key. + /// @param pubkeyHash The validator's BLS12-381 public key hash. /// @param withdrawalEpoch The epoch at which the withdrawal was made. /// @param recipient The address of the recipient of the withdrawal. /// @param withdrawalAmountGwei The amount of the withdrawal in Gwei. event FullWithdrawalRedeemed( - bytes32 pubkey, uint64 withdrawalEpoch, address indexed recipient, uint64 withdrawalAmountGwei + bytes32 pubkeyHash, uint64 withdrawalEpoch, address indexed recipient, uint64 withdrawalAmountGwei ); /// @notice Emitted when capsuleOwner enables restaking @@ -66,34 +66,34 @@ contract ExoCapsule is ReentrancyGuardUpgradeable, ExoCapsuleStorage, IExoCapsul event NonBeaconChainETHWithdrawn(address indexed recipient, uint256 amountWithdrawn); /// @dev Thrown when the validator container is invalid. - /// @param pubkey The validator's BLS12-381 public key. - error InvalidValidatorContainer(bytes32 pubkey); + /// @param pubkeyHash The validator's BLS12-381 public key hash. + error InvalidValidatorContainer(bytes32 pubkeyHash); /// @dev Thrown when the withdrawal container is invalid. /// @param validatorIndex The validator index. error InvalidWithdrawalContainer(uint64 validatorIndex); /// @dev Thrown when a validator is double deposited. - /// @param pubkey The validator's BLS12-381 public key. - error DoubleDepositedValidator(bytes32 pubkey); + /// @param pubkeyHash The validator's BLS12-381 public key hash. + error DoubleDepositedValidator(bytes32 pubkeyHash); /// @dev Thrown when a validator container is stale. - /// @param pubkey The validator's BLS12-381 public key. + /// @param pubkeyHash The validator's BLS12-381 public key hash. /// @param timestamp The timestamp of the validator proof. - error StaleValidatorContainer(bytes32 pubkey, uint256 timestamp); + error StaleValidatorContainer(bytes32 pubkeyHash, uint256 timestamp); /// @dev Thrown when a withdrawal has already been proven. - /// @param pubkey The validator's BLS12-381 public key. + /// @param pubkeyHash The validator's BLS12-381 public key hash. /// @param withdrawalIndex The index of the withdrawal. - error WithdrawalAlreadyProven(bytes32 pubkey, uint256 withdrawalIndex); + error WithdrawalAlreadyProven(bytes32 pubkeyHash, uint256 withdrawalIndex); /// @dev Thrown when a validator container is unregistered. - /// @param pubkey The validator's BLS12-381 public key. - error UnregisteredValidator(bytes32 pubkey); + /// @param pubkeyHash The validator's BLS12-381 public key hash. + error UnregisteredValidator(bytes32 pubkeyHash); /// @dev Thrown when a validator container is unregistered or withdrawn. - /// @param pubkey The validator's BLS12-381 public key. - error UnregisteredOrWithdrawnValidatorContainer(bytes32 pubkey); + /// @param pubkeyHash The validator's BLS12-381 public key hash. + error UnregisteredOrWithdrawnValidatorContainer(bytes32 pubkeyHash); /// @dev Thrown when the validator and withdrawal state roots do not match. /// @param validatorStateRoot The state root of the validator container. @@ -115,8 +115,8 @@ contract ExoCapsule is ReentrancyGuardUpgradeable, ExoCapsuleStorage, IExoCapsul error WithdrawalCredentialsNotMatch(); /// @dev Thrown when the validator container is inactive. - /// @param pubkey The validator's BLS12-381 public key. - error InactiveValidatorContainer(bytes32 pubkey); + /// @param pubkeyHash The validator's BLS12-381 public key hash. + error InactiveValidatorContainer(bytes32 pubkeyHash); /// @dev Thrown when the caller of a message is not the gateway /// @param gateway The address of the gateway. @@ -162,20 +162,20 @@ contract ExoCapsule is ReentrancyGuardUpgradeable, ExoCapsuleStorage, IExoCapsul bytes32[] calldata validatorContainer, BeaconChainProofs.ValidatorContainerProof calldata proof ) external onlyGateway returns (uint256 depositAmount) { - bytes32 validatorPubkey = validatorContainer.getPubkey(); + bytes32 validatorPubkeyHash = validatorContainer.getPubkeyHash(); bytes32 withdrawalCredentials = validatorContainer.getWithdrawalCredentials(); - Validator storage validator = _capsuleValidators[validatorPubkey]; + Validator storage validator = _capsuleValidators[validatorPubkeyHash]; if (!validatorContainer.verifyValidatorContainerBasic()) { - revert InvalidValidatorContainer(validatorPubkey); + revert InvalidValidatorContainer(validatorPubkeyHash); } if (validator.status != VALIDATOR_STATUS.UNREGISTERED) { - revert DoubleDepositedValidator(validatorPubkey); + revert DoubleDepositedValidator(validatorPubkeyHash); } if (_isStaleProof(proof.beaconBlockTimestamp)) { - revert StaleValidatorContainer(validatorPubkey, proof.beaconBlockTimestamp); + revert StaleValidatorContainer(validatorPubkeyHash, proof.beaconBlockTimestamp); } if (withdrawalCredentials != bytes32(capsuleWithdrawalCredentials())) { @@ -193,7 +193,7 @@ contract ExoCapsule is ReentrancyGuardUpgradeable, ExoCapsuleStorage, IExoCapsul depositAmount = depositAmountGwei * GWEI_TO_WEI; } - _capsuleValidatorsByIndex[proof.validatorIndex] = validatorPubkey; + _capsuleValidatorsByIndex[proof.validatorIndex] = validatorPubkeyHash; } /// @inheritdoc IExoCapsule @@ -203,24 +203,24 @@ contract ExoCapsule is ReentrancyGuardUpgradeable, ExoCapsuleStorage, IExoCapsul bytes32[] calldata withdrawalContainer, BeaconChainProofs.WithdrawalProof calldata withdrawalProof ) external onlyGateway returns (bool partialWithdrawal, uint256 withdrawalAmount) { - bytes32 validatorPubkey = validatorContainer.getPubkey(); - Validator storage validator = _capsuleValidators[validatorPubkey]; + bytes32 validatorPubkeyHash = validatorContainer.getPubkeyHash(); + Validator storage validator = _capsuleValidators[validatorPubkeyHash]; uint64 withdrawalEpoch = withdrawalProof.slotRoot.getWithdrawalEpoch(); partialWithdrawal = withdrawalEpoch < validatorContainer.getWithdrawableEpoch(); uint256 withdrawalId = uint256(withdrawalContainer.getWithdrawalIndex()); if (!validatorContainer.verifyValidatorContainerBasic()) { - revert InvalidValidatorContainer(validatorPubkey); + revert InvalidValidatorContainer(validatorPubkeyHash); } if (validator.status == VALIDATOR_STATUS.UNREGISTERED) { - revert UnregisteredOrWithdrawnValidatorContainer(validatorPubkey); + revert UnregisteredOrWithdrawnValidatorContainer(validatorPubkeyHash); } - if (provenWithdrawal[validatorPubkey][withdrawalId]) { - revert WithdrawalAlreadyProven(validatorPubkey, withdrawalId); + if (provenWithdrawal[validatorPubkeyHash][withdrawalId]) { + revert WithdrawalAlreadyProven(validatorPubkeyHash, withdrawalId); } - provenWithdrawal[validatorPubkey][withdrawalId] = true; + provenWithdrawal[validatorPubkeyHash][withdrawalId] = true; // Validate if validator and withdrawal proof state roots are the same if (validatorProof.stateRoot != withdrawalProof.stateRoot) { @@ -234,13 +234,13 @@ contract ExoCapsule is ReentrancyGuardUpgradeable, ExoCapsuleStorage, IExoCapsul if (partialWithdrawal) { // Immediately send ETH without sending request to Exocore side - emit PartialWithdrawalRedeemed(validatorPubkey, withdrawalEpoch, capsuleOwner, withdrawalAmountGwei); + emit PartialWithdrawalRedeemed(validatorPubkeyHash, withdrawalEpoch, capsuleOwner, withdrawalAmountGwei); _sendETH(capsuleOwner, withdrawalAmountGwei * GWEI_TO_WEI); } else { // Full withdrawal validator.status = VALIDATOR_STATUS.WITHDRAWN; // If over MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR = 32 * 1e9, then send remaining amount immediately - emit FullWithdrawalRedeemed(validatorPubkey, withdrawalEpoch, capsuleOwner, withdrawalAmountGwei); + emit FullWithdrawalRedeemed(validatorPubkeyHash, withdrawalEpoch, capsuleOwner, withdrawalAmountGwei); if (withdrawalAmountGwei > MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR) { uint256 amountToSend = (withdrawalAmountGwei - MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR) * GWEI_TO_WEI; _sendETH(capsuleOwner, amountToSend); @@ -312,14 +312,14 @@ contract ExoCapsule is ReentrancyGuardUpgradeable, ExoCapsuleStorage, IExoCapsul return root; } - /// @notice Gets the registered validator by pubkey. + /// @notice Gets the registered validator by pubkeyHash. /// @dev The validator status must be registered. Reverts if not. - /// @param pubkey The validator's BLS12-381 public key. + /// @param pubkeyHash The validator's BLS12-381 public key hash. /// @return The validator object, as defined in the `ExoCapsuleStorage`. - function getRegisteredValidatorByPubkey(bytes32 pubkey) public view returns (Validator memory) { - Validator memory validator = _capsuleValidators[pubkey]; + function getRegisteredValidatorByPubkey(bytes32 pubkeyHash) public view returns (Validator memory) { + Validator memory validator = _capsuleValidators[pubkeyHash]; if (validator.status == VALIDATOR_STATUS.UNREGISTERED) { - revert UnregisteredValidator(pubkey); + revert UnregisteredValidator(pubkeyHash); } return validator; @@ -366,7 +366,7 @@ contract ExoCapsule is ReentrancyGuardUpgradeable, ExoCapsuleStorage, IExoCapsul proof.stateRootProof ); if (!valid) { - revert InvalidValidatorContainer(validatorContainer.getPubkey()); + revert InvalidValidatorContainer(validatorContainer.getPubkeyHash()); } } diff --git a/src/core/ExocoreGateway.sol b/src/core/ExocoreGateway.sol index 99058203..d839ffe7 100644 --- a/src/core/ExocoreGateway.sol +++ b/src/core/ExocoreGateway.sol @@ -374,21 +374,25 @@ contract ExocoreGateway is onlyCalledFromThis returns (bytes memory response) { - bytes calldata validatorPubkey = payload[:32]; - bytes calldata staker = payload[32:64]; - uint256 amount = uint256(bytes32(payload[64:96])); + bytes calldata staker = payload[:32]; + uint256 amount = uint256(bytes32(payload[32:64])); + // the length of the validatorID is not known. it depends on the chain. + // for Ethereum, it is the validatorIndex uint256 as bytes so it becomes 32. its value may be 0. + // for Solana, the pubkey is 32 bytes long but for Sui it is 96 bytes long. + // these chains do not have the concept of validatorIndex, so the raw key must be used. + bytes calldata validatorID = payload[64:]; bool isDeposit = act == Action.REQUEST_DEPOSIT_NST; bool success; if (isDeposit) { - (success,) = ASSETS_CONTRACT.depositNST(srcChainId, validatorPubkey, staker, amount); + (success,) = ASSETS_CONTRACT.depositNST(srcChainId, validatorID, staker, amount); } else { - (success,) = ASSETS_CONTRACT.withdrawNST(srcChainId, validatorPubkey, staker, amount); + (success,) = ASSETS_CONTRACT.withdrawNST(srcChainId, validatorID, staker, amount); } if (isDeposit && !success) { revert Errors.DepositRequestShouldNotFail(srcChainId, lzNonce); // we should not let this happen } - emit NSTTransfer(isDeposit, success, bytes32(validatorPubkey), bytes32(staker), amount); + emit NSTTransfer(isDeposit, success, validatorID, bytes32(staker), amount); response = isDeposit ? bytes("") : abi.encodePacked(lzNonce, success); } diff --git a/src/core/NativeRestakingController.sol b/src/core/NativeRestakingController.sol index 16fd28aa..d9f35674 100644 --- a/src/core/NativeRestakingController.sol +++ b/src/core/NativeRestakingController.sol @@ -94,8 +94,7 @@ abstract contract NativeRestakingController is IExoCapsule capsule = _getCapsule(msg.sender); uint256 depositValue = capsule.verifyDepositProof(validatorContainer, proof); - bytes32 validatorPubkey = validatorContainer.getPubkey(); - bytes memory actionArgs = abi.encodePacked(validatorPubkey, bytes32(bytes20(msg.sender)), depositValue); + bytes memory actionArgs = abi.encodePacked(bytes32(bytes20(msg.sender)), depositValue, proof.validatorIndex); // deposit NST is a must-succeed action, so we don't need to check the response _processRequest(Action.REQUEST_DEPOSIT_NST, actionArgs, bytes("")); @@ -117,8 +116,8 @@ abstract contract NativeRestakingController is capsule.verifyWithdrawalProof(validatorContainer, validatorProof, withdrawalContainer, withdrawalProof); if (!partialWithdrawal) { // request full withdraw - bytes32 validatorPubkey = validatorContainer.getPubkey(); - bytes memory actionArgs = abi.encodePacked(validatorPubkey, bytes32(bytes20(msg.sender)), withdrawalAmount); + bytes memory actionArgs = + abi.encodePacked(bytes32(bytes20(msg.sender)), withdrawalAmount, validatorProof.validatorIndex); bytes memory encodedRequest = abi.encode(VIRTUAL_NST_ADDRESS, msg.sender, withdrawalAmount); // a full withdrawal needs response from Exocore, so we don't pass empty bytes diff --git a/src/interfaces/precompiles/IAssets.sol b/src/interfaces/precompiles/IAssets.sol index ff33a78e..f1f11c37 100644 --- a/src/interfaces/precompiles/IAssets.sol +++ b/src/interfaces/precompiles/IAssets.sol @@ -37,12 +37,12 @@ interface IAssets { /// @param clientChainID is the layerZero chainID if it is supported. // It might be allocated by Exocore when the client chain isn't supported // by layerZero - /// @param validatorPubkey The validator's pubkey + /// @param validatorID The validator's identifier: index (uint256 as bytes32) or pubkey. /// @param stakerAddress The staker address /// @param opAmount The amount to deposit function depositNST( uint32 clientChainID, - bytes calldata validatorPubkey, + bytes calldata validatorID, bytes calldata stakerAddress, uint256 opAmount ) external returns (bool success, uint256 latestAssetState); @@ -67,12 +67,12 @@ interface IAssets { /// @param clientChainID is the layerZero chainID if it is supported. // It might be allocated by Exocore when the client chain isn't supported // by layerZero - /// @param validatorPubkey The validator's pubkey + /// @param validatorID The validator's identifier: index (uint256 as bytes32) or pubkey. /// @param withdrawAddress The withdraw address /// @param opAmount The withdraw amount function withdrawNST( uint32 clientChainID, - bytes calldata validatorPubkey, + bytes calldata validatorID, bytes calldata withdrawAddress, uint256 opAmount ) external returns (bool success, uint256 latestAssetState); diff --git a/src/libraries/ActionAttributes.sol b/src/libraries/ActionAttributes.sol index 7fa19f69..ecbf3a4c 100644 --- a/src/libraries/ActionAttributes.sol +++ b/src/libraries/ActionAttributes.sol @@ -20,6 +20,7 @@ library ActionAttributes { uint256 internal constant MESSAGE_LENGTH_MASK = 0xFF; // 8 bits for message length uint256 internal constant MESSAGE_LENGTH_SHIFT = 8; + uint256 internal constant MIN_LENGTH_FLAG = 1 << 16; // Flag at the 16th bit function getAttributes(Action action) internal pure returns (uint256) { uint256 attributes = 0; @@ -29,13 +30,15 @@ library ActionAttributes { attributes = LST | PRINCIPAL; messageLength = ASSET_OPERATION_LENGTH; } else if (action == Action.REQUEST_DEPOSIT_NST) { - attributes = NST | PRINCIPAL; + // we assume that a validatorID is at least 32 bytes, however, it is up for review. + attributes = NST | PRINCIPAL | MIN_LENGTH_FLAG; messageLength = ASSET_OPERATION_LENGTH; } else if (action == Action.REQUEST_WITHDRAW_LST) { attributes = LST | PRINCIPAL | WITHDRAWAL; messageLength = ASSET_OPERATION_LENGTH; } else if (action == Action.REQUEST_WITHDRAW_NST) { - attributes = NST | PRINCIPAL | WITHDRAWAL; + // we assume that a validatorID is at least 32 bytes, however, it is up for review. + attributes = NST | PRINCIPAL | WITHDRAWAL | MIN_LENGTH_FLAG; messageLength = ASSET_OPERATION_LENGTH; } else if (action == Action.REQUEST_CLAIM_REWARD) { attributes = REWARD | WITHDRAWAL; @@ -80,8 +83,11 @@ library ActionAttributes { return (getAttributes(action) & REWARD) != 0; } - function getMessageLength(Action action) internal pure returns (uint256) { - return (getAttributes(action) >> MESSAGE_LENGTH_SHIFT) & MESSAGE_LENGTH_MASK; + function getMessageLength(Action action) internal pure returns (bool, uint256) { + uint256 attributes = getAttributes(action); + uint256 length = (attributes >> MESSAGE_LENGTH_SHIFT) & MESSAGE_LENGTH_MASK; + bool isMinLength = (attributes & MIN_LENGTH_FLAG) != 0; + return (isMinLength, length); } } diff --git a/src/libraries/ValidatorContainer.sol b/src/libraries/ValidatorContainer.sol index 813dcf76..3f9092c4 100644 --- a/src/libraries/ValidatorContainer.sol +++ b/src/libraries/ValidatorContainer.sol @@ -5,7 +5,7 @@ import {Endian} from "../libraries/Endian.sol"; /** * class Validator(Container): - * pubkey: BLSPubkey + * pubkeyHash: The validator's BLS12-381 public key hash. * withdrawal_credentials: Bytes32 # Commitment to pubkey for withdrawals * effective_balance: Gwei # Balance at stake * slashed: boolean @@ -26,7 +26,7 @@ library ValidatorContainer { return validatorContainer.length == VALID_LENGTH; } - function getPubkey(bytes32[] calldata validatorContainer) internal pure returns (bytes32) { + function getPubkeyHash(bytes32[] calldata validatorContainer) internal pure returns (bytes32) { return validatorContainer[0]; } diff --git a/src/storage/ExoCapsuleStorage.sol b/src/storage/ExoCapsuleStorage.sol index fa4cdaa3..153fa3e8 100644 --- a/src/storage/ExoCapsuleStorage.sol +++ b/src/storage/ExoCapsuleStorage.sol @@ -64,11 +64,11 @@ contract ExoCapsuleStorage { /// @notice The address of the Beacon Chain Oracle contract. IBeaconChainOracle public beaconOracle; - /// @dev Mapping of validator pubkey to their corresponding struct. - mapping(bytes32 pubkey => Validator validator) internal _capsuleValidators; + /// @dev Mapping of validator pubkey hash to their corresponding struct. + mapping(bytes32 pubkeyHash => Validator validator) internal _capsuleValidators; - /// @dev Mapping of validator index to their corresponding pubkey. - mapping(uint256 index => bytes32 pubkey) internal _capsuleValidatorsByIndex; + /// @dev Mapping of validator index to their corresponding pubkey hash. + mapping(uint256 index => bytes32 pubkeyHash) internal _capsuleValidatorsByIndex; /// @notice This is a mapping of validatorPubkeyHash to withdrawal index to whether or not they have proven a /// withdrawal diff --git a/src/storage/ExocoreGatewayStorage.sol b/src/storage/ExocoreGatewayStorage.sol index 614c9c72..ecb62adc 100644 --- a/src/storage/ExocoreGatewayStorage.sol +++ b/src/storage/ExocoreGatewayStorage.sol @@ -82,11 +82,11 @@ contract ExocoreGatewayStorage is GatewayStorage { /// @notice Emitted when a NST transfer happens. /// @param isDeposit Whether the transfer is a deposit or a withdraw. /// @param success Whether the transfer was successful. - /// @param validatorPubkey The validator public key. + /// @param validatorID The validator ID (index or pubkey). /// @param staker The address that makes the transfer. /// @param amount The amount of the token transferred. event NSTTransfer( - bool isDeposit, bool indexed success, bytes32 indexed validatorPubkey, bytes32 indexed staker, uint256 amount + bool isDeposit, bool indexed success, bytes indexed validatorID, bytes32 indexed staker, uint256 amount ); /// @notice Emitted upon receiving a delegation request. @@ -128,13 +128,17 @@ contract ExocoreGatewayStorage is GatewayStorage { revert Errors.InvalidMessageLength(); } Action action = Action(uint8(message[0])); - uint256 expectedLength = action.getMessageLength(); + (bool isMinLength, uint256 expectedLength) = action.getMessageLength(); if (expectedLength == 0) { revert Errors.UnsupportedRequest(action); } - if (message.length != expectedLength) { + if (isMinLength) { + if (message.length < expectedLength) { + revert Errors.InvalidMessageLength(); + } + } else if (message.length != expectedLength) { revert Errors.InvalidMessageLength(); } } diff --git a/test/foundry/DepositWithdrawPrinciple.t.sol b/test/foundry/DepositWithdrawPrinciple.t.sol index bc1447fe..4278005a 100644 --- a/test/foundry/DepositWithdrawPrinciple.t.sol +++ b/test/foundry/DepositWithdrawPrinciple.t.sol @@ -24,7 +24,7 @@ contract DepositWithdrawPrincipalTest is ExocoreDeployer { bool isDeposit, bool indexed success, bytes32 indexed token, bytes32 indexed account, uint256 amount ); event NSTTransfer( - bool isDeposit, bool indexed success, bytes32 indexed token, bytes32 indexed account, uint256 amount + bool isDeposit, bool indexed success, bytes indexed validatorID, bytes32 indexed account, uint256 amount ); event Transfer(address indexed from, address indexed to, uint256 amount); event CapsuleCreated(address indexed owner, address indexed capsule); @@ -312,7 +312,7 @@ contract DepositWithdrawPrincipalTest is ExocoreDeployer { } bytes memory depositRequestPayload = abi.encodePacked( - Action.REQUEST_DEPOSIT_NST, _getPubkey(validatorContainer), bytes32(bytes20(depositor.addr)), depositAmount + Action.REQUEST_DEPOSIT_NST, bytes32(bytes20(depositor.addr)), depositAmount, validatorProof.validatorIndex ); uint256 depositRequestNativeFee = clientGateway.quote(depositRequestPayload); bytes32 depositRequestId = generateUID(outboundNonces[clientChainId], true); @@ -344,7 +344,7 @@ contract DepositWithdrawPrincipalTest is ExocoreDeployer { emit NSTTransfer( true, // isDeposit true, // success - bytes32(_getPubkey(validatorContainer)), + abi.encodePacked(bytes32(validatorProof.validatorIndex)), bytes32(bytes20(depositor.addr)), depositAmount ); @@ -442,9 +442,9 @@ contract DepositWithdrawPrincipalTest is ExocoreDeployer { } bytes memory withdrawRequestPayload = abi.encodePacked( Action.REQUEST_WITHDRAW_NST, - _getPubkey(validatorContainer), bytes32(bytes20(withdrawer.addr)), - withdrawalAmount + withdrawalAmount, + validatorProof.validatorIndex ); uint256 withdrawRequestNativeFee = clientGateway.quote(withdrawRequestPayload); bytes32 withdrawRequestId = generateUID(outboundNonces[clientChainId], true); @@ -481,7 +481,7 @@ contract DepositWithdrawPrincipalTest is ExocoreDeployer { emit NSTTransfer( false, // isDeposit (false for withdrawal) true, // success - bytes32(_getPubkey(validatorContainer)), + abi.encodePacked(bytes32(validatorProof.validatorIndex)), bytes32(bytes20(withdrawer.addr)), withdrawalAmount ); diff --git a/test/mocks/AssetsMock.sol b/test/mocks/AssetsMock.sol index dd0f92cf..6f041606 100644 --- a/test/mocks/AssetsMock.sol +++ b/test/mocks/AssetsMock.sol @@ -36,7 +36,7 @@ contract AssetsMock is IAssets { function depositNST( uint32 clientChainLzId, - bytes calldata validatorPubkey, + bytes calldata validatorID, bytes calldata stakerAddress, uint256 opAmount ) external returns (bool success, uint256 latestAssetState) { @@ -44,7 +44,7 @@ contract AssetsMock is IAssets { bytes memory nstAddress = abi.encodePacked(bytes32(bytes20(VIRTUAL_STAKED_ETH_ADDRESS))); principalBalances[clientChainLzId][nstAddress][stakerAddress] += opAmount; - inValidatorSet[stakerAddress][validatorPubkey] = true; + inValidatorSet[stakerAddress][validatorID] = true; return (true, principalBalances[clientChainLzId][nstAddress][stakerAddress]); } @@ -74,7 +74,7 @@ contract AssetsMock is IAssets { function withdrawNST( uint32 clientChainLzId, - bytes calldata validatorPubkey, + bytes calldata validatorID, bytes calldata withdrawer, uint256 opAmount ) external returns (bool success, uint256 latestAssetState) { @@ -85,7 +85,7 @@ contract AssetsMock is IAssets { return (false, 0); } principalBalances[clientChainLzId][nstAddress][withdrawer] -= opAmount; - inValidatorSet[withdrawer][validatorPubkey] = false; + inValidatorSet[withdrawer][validatorID] = false; return (true, principalBalances[clientChainLzId][nstAddress][withdrawer]); } diff --git a/test/mocks/ExocoreGatewayMock.sol b/test/mocks/ExocoreGatewayMock.sol index 23c91b4f..6e1b15ba 100644 --- a/test/mocks/ExocoreGatewayMock.sol +++ b/test/mocks/ExocoreGatewayMock.sol @@ -388,21 +388,21 @@ contract ExocoreGatewayMock is onlyCalledFromThis returns (bytes memory response) { - bytes calldata validatorPubkey = payload[:32]; - bytes calldata staker = payload[32:64]; - uint256 amount = uint256(bytes32(payload[64:96])); + bytes calldata staker = payload[:32]; + uint256 amount = uint256(bytes32(payload[32:64])); + bytes calldata validatorID = payload[64:]; bool isDeposit = act == Action.REQUEST_DEPOSIT_NST; bool success; if (isDeposit) { - (success,) = ASSETS_CONTRACT.depositNST(srcChainId, validatorPubkey, staker, amount); + (success,) = ASSETS_CONTRACT.depositNST(srcChainId, validatorID, staker, amount); } else { - (success,) = ASSETS_CONTRACT.withdrawNST(srcChainId, validatorPubkey, staker, amount); + (success,) = ASSETS_CONTRACT.withdrawNST(srcChainId, validatorID, staker, amount); } if (isDeposit && !success) { revert Errors.DepositRequestShouldNotFail(srcChainId, lzNonce); // we should not let this happen } - emit NSTTransfer(isDeposit, success, bytes32(validatorPubkey), bytes32(staker), amount); + emit NSTTransfer(isDeposit, success, validatorID, bytes32(staker), amount); response = isDeposit ? bytes("") : abi.encodePacked(lzNonce, success); }