Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: native restaking withdrawal checks could be bypassed #85

Merged
merged 3 commits into from
Sep 2, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/core/Bootstrap.sol
Original file line number Diff line number Diff line change
Expand Up @@ -195,8 +195,9 @@ contract Bootstrap is
whitelistTokens.push(token);
isWhitelistedToken[token] = true;

// do not deploy the vault for the virtual token address representing natively staked ETH
// deploy the corresponding vault if not deployed before
if (address(tokenToVault[token]) == address(0)) {
if (token != VIRTUAL_STAKED_ETH_ADDRESS && address(tokenToVault[token]) == address(0)) {
_deployVault(token);
}

Expand Down
1 change: 1 addition & 0 deletions src/core/ClientGatewayLzReceiver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,7 @@ abstract contract ClientGatewayLzReceiver is PausableUpgradeable, OAppReceiverUp
isWhitelistedToken[token] = true;
whitelistTokens.push(token);

// do not deploy the vault for the virtual token address representing natively staked ETH
// deploy the corresponding vault if not deployed before
if (token != VIRTUAL_STAKED_ETH_ADDRESS && address(tokenToVault[token]) == address(0)) {
_deployVault(token);
Expand Down
4 changes: 4 additions & 0 deletions src/core/LSTRestakingController.sol
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ abstract contract LSTRestakingController is
whenNotPaused
nonReentrant
{
// If we can get the vault, the token cannot be VIRTUAL_STAKED_ETH_ADDRESS, so that staker cannot bypass the
// beacon chain merkle proof check to withdraw natively staked ETH
_getVault(token);

bytes memory actionArgs =
abi.encodePacked(bytes32(bytes20(token)), bytes32(bytes20(msg.sender)), principalAmount);
bytes memory encodedRequest = abi.encode(token, msg.sender, principalAmount);
Expand Down
3 changes: 3 additions & 0 deletions src/libraries/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -207,4 +207,7 @@ library Errors {
/// @dev Vault: total principal unlock amount is larger than the total deposited amount
error VaultTotalUnlockPrincipalExceedsDeposit();

/// @dev Vault: forbid to deploy vault for the virtual token address representing natively staked ETH
error ForbidToDeployVault();

}
9 changes: 9 additions & 0 deletions src/storage/BootstrapStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import {BeaconProxyBytecode} from "../core/BeaconProxyBytecode.sol";
import {Vault} from "../core/Vault.sol";
import {IValidatorRegistry} from "../interfaces/IValidatorRegistry.sol";
import {IVault} from "../interfaces/IVault.sol";

import {Errors} from "../libraries/Errors.sol";
import {GatewayStorage} from "./GatewayStorage.sol";
import {IBeacon} from "@openzeppelin/contracts/proxy/beacon/IBeacon.sol";
import {Create2} from "@openzeppelin/contracts/utils/Create2.sol";
Expand Down Expand Up @@ -146,6 +148,9 @@ contract BootstrapStorage is GatewayStorage {
/// @dev A mapping of validator names to a boolean indicating whether the name has been used.
mapping(string name => bool used) public validatorNameInUse;

/// @dev The (virtual) address for staked ETH.
address internal constant VIRTUAL_STAKED_ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;

/* -------------------------------------------------------------------------- */
/* Events */
/* -------------------------------------------------------------------------- */
Expand Down Expand Up @@ -335,6 +340,10 @@ contract BootstrapStorage is GatewayStorage {
// array, so it would not cause collision for encodePacked
// slither-disable-next-line encode-packed-collision
function _deployVault(address underlyingToken) internal returns (IVault) {
if (underlyingToken == VIRTUAL_STAKED_ETH_ADDRESS) {
revert Errors.ForbidToDeployVault();
}

Vault vault = Vault(
Create2.deploy(
0,
Expand Down
3 changes: 0 additions & 3 deletions src/storage/ClientChainGatewayStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,6 @@ contract ClientChainGatewayStorage is BootstrapStorage {
/// @dev The length of the token address in bytes.
uint256 internal constant TOKEN_ADDRESS_BYTES_LENGTH = 32;

/// @dev The (virtual) address for staked ETH.
address internal constant VIRTUAL_STAKED_ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;

/// @dev The address of the ETHPOS deposit contract.
IETHPOSDeposit internal constant ETH_POS = IETHPOSDeposit(0x00000000219ab540356cBB839Cbe05303d7705Fa);

Expand Down
16 changes: 16 additions & 0 deletions test/foundry/unit/Bootstrap.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ contract BootstrapTest is Test {
IBeacon capsuleBeacon;
BeaconProxyBytecode beaconProxyBytecode;

address internal constant VIRTUAL_STAKED_ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
bytes constant BEACON_PROXY_BYTECODE =
hex"608060405260405161090e38038061090e83398101604081905261002291610460565b61002e82826000610035565b505061058a565b61003e83610100565b6040516001600160a01b038416907f1cf3b03a6cf19fa2baba4df148e9dcabedea7f8a5c07840e207e5c089be95d3e90600090a260008251118061007f5750805b156100fb576100f9836001600160a01b0316635c60da1b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156100c5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100e99190610520565b836102a360201b6100291760201c565b505b505050565b610113816102cf60201b6100551760201c565b6101725760405162461bcd60e51b815260206004820152602560248201527f455243313936373a206e657720626561636f6e206973206e6f74206120636f6e6044820152641d1c9858dd60da1b60648201526084015b60405180910390fd5b6101e6816001600160a01b0316635c60da1b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156101b3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101d79190610520565b6102cf60201b6100551760201c565b61024b5760405162461bcd60e51b815260206004820152603060248201527f455243313936373a20626561636f6e20696d706c656d656e746174696f6e206960448201526f1cc81b9bdd08184818dbdb9d1c9858dd60821b6064820152608401610169565b806102827fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d5060001b6102de60201b6100641760201c565b80546001600160a01b0319166001600160a01b039290921691909117905550565b60606102c883836040518060600160405280602781526020016108e7602791396102e1565b9392505050565b6001600160a01b03163b151590565b90565b6060600080856001600160a01b0316856040516102fe919061053b565b600060405180830381855af49150503d8060008114610339576040519150601f19603f3d011682016040523d82523d6000602084013e61033e565b606091505b5090925090506103508683838761035a565b9695505050505050565b606083156103c65782516103bf576001600160a01b0385163b6103bf5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610169565b50816103d0565b6103d083836103d8565b949350505050565b8151156103e85781518083602001fd5b8060405162461bcd60e51b81526004016101699190610557565b80516001600160a01b038116811461041957600080fd5b919050565b634e487b7160e01b600052604160045260246000fd5b60005b8381101561044f578181015183820152602001610437565b838111156100f95750506000910152565b6000806040838503121561047357600080fd5b61047c83610402565b60208401519092506001600160401b038082111561049957600080fd5b818501915085601f8301126104ad57600080fd5b8151818111156104bf576104bf61041e565b604051601f8201601f19908116603f011681019083821181831017156104e7576104e761041e565b8160405282815288602084870101111561050057600080fd5b610511836020830160208801610434565b80955050505050509250929050565b60006020828403121561053257600080fd5b6102c882610402565b6000825161054d818460208701610434565b9190910192915050565b6020815260008251806020840152610576816040850160208701610434565b601f01601f19169190910160400192915050565b61034e806105996000396000f3fe60806040523661001357610011610017565b005b6100115b610027610022610067565b610100565b565b606061004e83836040518060600160405280602781526020016102f260279139610124565b9392505050565b6001600160a01b03163b151590565b90565b600061009a7fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50546001600160a01b031690565b6001600160a01b0316635c60da1b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156100d7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100fb9190610249565b905090565b3660008037600080366000845af43d6000803e80801561011f573d6000f35b3d6000fd5b6060600080856001600160a01b03168560405161014191906102a2565b600060405180830381855af49150503d806000811461017c576040519150601f19603f3d011682016040523d82523d6000602084013e610181565b606091505b50915091506101928683838761019c565b9695505050505050565b6060831561020d578251610206576001600160a01b0385163b6102065760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064015b60405180910390fd5b5081610217565b610217838361021f565b949350505050565b81511561022f5781518083602001fd5b8060405162461bcd60e51b81526004016101fd91906102be565b60006020828403121561025b57600080fd5b81516001600160a01b038116811461004e57600080fd5b60005b8381101561028d578181015183820152602001610275565b8381111561029c576000848401525b50505050565b600082516102b4818460208701610272565b9190910192915050565b60208152600082518060208401526102dd816040850160208701610272565b601f01601f1916919091016040019291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220d51e81d3bc5ed20a26aeb05dce7e825c503b2061aa78628027300c8d65b9d89a64736f6c634300080c0033416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564";

Expand Down Expand Up @@ -175,6 +176,21 @@ contract BootstrapTest is Test {
vm.stopPrank();
}

// test that the vault is not deployed for the virtual token address representing natively staked ETH
function test02_VaultNotDeployedForNativeStakedETH() public {
MyToken myTokenClone = new MyToken("MyToken", "MYT", 18, addrs, 1000 * 10 ** 18);

vm.startPrank(deployer);
address[] memory addedWhitelistTokens = new address[](2);
addedWhitelistTokens[0] = address(myTokenClone);
addedWhitelistTokens[1] = VIRTUAL_STAKED_ETH_ADDRESS;
bootstrap.addWhitelistTokens(addedWhitelistTokens);
vm.stopPrank();

assertTrue(address(bootstrap.tokenToVault(address(myToken))) != address(0));
assertTrue(address(bootstrap.tokenToVault(VIRTUAL_STAKED_ETH_ADDRESS)) == address(0));
}

function test02_Deposit() public {
// Distribute MyToken to addresses
vm.startPrank(deployer);
Expand Down
59 changes: 58 additions & 1 deletion test/foundry/unit/ClientChainGateway.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ pragma solidity ^0.8.19;
import "@beacon-oracle/contracts/src/EigenLayerBeaconOracle.sol";

import "@layerzero-v2/protocol/contracts/interfaces/ILayerZeroEndpointV2.sol";

import {Origin} from "@layerzero-v2/protocol/contracts/interfaces/ILayerZeroEndpointV2.sol";
import "@layerzero-v2/protocol/contracts/libs/AddressCast.sol";
import "@layerzerolabs/lz-evm-protocol-v2/contracts/libs/GUID.sol";

Expand Down Expand Up @@ -275,7 +277,7 @@ contract Initialize is SetUp {

}

contract withdrawNonBeaconChainETHFromCapsule is SetUp {
contract WithdrawNonBeaconChainETHFromCapsule is SetUp {

using stdStorage for StdStorage;

Expand Down Expand Up @@ -349,3 +351,58 @@ contract withdrawNonBeaconChainETHFromCapsule is SetUp {
}

}

contract WithdrawalPrincipalFromExocore is SetUp {

using stdStorage for StdStorage;
using AddressCast for address;

address internal constant VIRTUAL_STAKED_ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
uint256 constant WITHDRAWAL_AMOUNT = 1 ether;

address payable user;

function setUp() public override {
super.setUp();

user = payable(players[0].addr);
vm.deal(user, 10 ether);

bytes32[] memory tokens = new bytes32[](2);
tokens[0] = bytes32(bytes20(VIRTUAL_STAKED_ETH_ADDRESS));
tokens[1] = bytes32(bytes20(address(restakeToken)));

// Simulate adding VIRTUAL_STAKED_ETH_ADDRESS to whitelist via lzReceive
bytes memory message =
abi.encodePacked(GatewayStorage.Action.REQUEST_ADD_WHITELIST_TOKENS, uint8(tokens.length), tokens);
Origin memory origin = Origin({srcEid: exocoreChainId, sender: address(exocoreGateway).toBytes32(), nonce: 1});

vm.prank(address(clientChainLzEndpoint));
clientGateway.lzReceive(origin, bytes32(0), message, address(0), bytes(""));
// assert that VIRTUAL_STAKED_ETH_ADDRESS and restake token is whitelisted
assertTrue(clientGateway.isWhitelistedToken(VIRTUAL_STAKED_ETH_ADDRESS));
assertTrue(clientGateway.isWhitelistedToken(address(restakeToken)));
}

function test_revert_withdrawVirtualStakedETH() public {
// Try to withdraw VIRTUAL_STAKED_ETH
vm.prank(user);
vm.expectRevert(BootstrapStorage.VaultNotExist.selector);
clientGateway.withdrawPrincipalFromExocore(VIRTUAL_STAKED_ETH_ADDRESS, WITHDRAWAL_AMOUNT);
}

function test_revert_withdrawNonWhitelistedToken() public {
address nonWhitelistedToken = address(0x1234);

vm.prank(players[0].addr);
vm.expectRevert("BootstrapStorage: token is not whitelisted");
clientGateway.withdrawPrincipalFromExocore(nonWhitelistedToken, WITHDRAWAL_AMOUNT);
}

function test_revert_withdrawZeroAmount() public {
vm.prank(user);
vm.expectRevert("BootstrapStorage: amount should be greater than zero");
clientGateway.withdrawPrincipalFromExocore(address(restakeToken), 0);
}

}
Loading