Skip to content

Commit

Permalink
Merge main
Browse files Browse the repository at this point in the history
  • Loading branch information
MaxMustermann2 committed Dec 3, 2024
2 parents 923c420 + e01d9e0 commit 3332c96
Show file tree
Hide file tree
Showing 11 changed files with 96 additions and 83 deletions.
78 changes: 39 additions & 39 deletions src/core/ExoCapsule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand All @@ -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.
Expand Down Expand Up @@ -163,20 +163,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())) {
Expand All @@ -194,7 +194,7 @@ contract ExoCapsule is ReentrancyGuardUpgradeable, ExoCapsuleStorage, IExoCapsul
depositAmount = depositAmountGwei * GWEI_TO_WEI;
}

_capsuleValidatorsByIndex[proof.validatorIndex] = validatorPubkey;
_capsuleValidatorsByIndex[proof.validatorIndex] = validatorPubkeyHash;
}

/// @inheritdoc IExoCapsule
Expand All @@ -204,24 +204,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(getSlotsPerEpoch());
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) {
Expand All @@ -235,13 +235,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);
Expand Down Expand Up @@ -313,14 +313,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;
Expand Down Expand Up @@ -367,7 +367,7 @@ contract ExoCapsule is ReentrancyGuardUpgradeable, ExoCapsuleStorage, IExoCapsul
proof.stateRootProof
);
if (!valid) {
revert InvalidValidatorContainer(validatorContainer.getPubkey());
revert InvalidValidatorContainer(validatorContainer.getPubkeyHash());
}
}

Expand Down
16 changes: 10 additions & 6 deletions src/core/ExocoreGateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
7 changes: 3 additions & 4 deletions src/core/NativeRestakingController.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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(""));
Expand All @@ -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
Expand Down
8 changes: 4 additions & 4 deletions src/interfaces/precompiles/IAssets.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand Down
14 changes: 10 additions & 4 deletions src/libraries/ActionAttributes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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);
}

}
4 changes: 2 additions & 2 deletions src/libraries/ValidatorContainer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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];
}

Expand Down
8 changes: 4 additions & 4 deletions src/storage/ExoCapsuleStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,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
Expand Down
Loading

0 comments on commit 3332c96

Please sign in to comment.