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

Lido Split Audit Fix #87

Merged
merged 6 commits into from
Sep 28, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion lib/solady
Submodule solady updated 142 files
62 changes: 38 additions & 24 deletions src/lido/LidoSplit.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,52 +15,51 @@ interface IwSTETH {
/// stETH token to wstETH token because stETH is a rebasing token
/// @dev Wraps stETH to wstETH and transfers to defined SplitWallet address
contract LidoSplit is Clone {

error Invalid_Address();

/// -----------------------------------------------------------------------
/// libraries
/// -----------------------------------------------------------------------
using SafeTransferLib for ERC20;
using SafeTransferLib for address;

address internal constant ETH_ADDRESS = address(0);

/// -----------------------------------------------------------------------
/// storage - cwia offsets
/// -----------------------------------------------------------------------

// stETH (address, 20 bytes),
// 0; first item
uint256 internal constant ST_ETH_ADDRESS_OFFSET = 0;
// wstETH (address, 20 bytees)
// 20 = st_eth_offset(0) + st_eth_address_size(address, 20 bytes)
uint256 internal constant WST_ETH_ADDRESS_OFFSET = 20;
// splitWallet (adress, 20 bytes)
// 40 = wst_eth_offset(20) + wst_eth_size(address, 20 bytes)
uint256 internal constant SPLIT_WALLET_ADDRESS_OFFSET = 40;
// 0; first item
uint256 internal constant SPLIT_WALLET_ADDRESS_OFFSET = 0;

constructor() {}

/// -----------------------------------------------------------------------
/// storage
/// -----------------------------------------------------------------------

/// @notice stETH token
ERC20 public immutable stETH;

/// @notice wstETH token
ERC20 public immutable wstETH;

constructor(ERC20 _stETH, ERC20 _wstETH) {
stETH = _stETH;
wstETH = _wstETH;
}

/// Address of split wallet to send funds to to
/// @dev equivalent to address public immutable splitWallet
function splitWallet() public pure returns (address) {
return _getArgAddress(SPLIT_WALLET_ADDRESS_OFFSET);
}

/// Address of stETH token
/// @dev equivalent to address public immutable stETHAddress
function stETHAddress() public pure returns (address) {
return _getArgAddress(ST_ETH_ADDRESS_OFFSET);
}

/// Address of wstETH token
/// @dev equivalent to address public immutable wstETHAddress
function wstETHAddress() public pure returns (address) {
return _getArgAddress(WST_ETH_ADDRESS_OFFSET);
}

/// Wraps the current stETH token balance to wstETH
/// transfers the wstETH balance to splitWallet for distribution
/// @return amount Amount of wstETH transferred to splitWallet
function distribute() external returns (uint256 amount) {
ERC20 stETH = ERC20(stETHAddress());
ERC20 wstETH = ERC20(wstETHAddress());

// get current balance
uint256 balance = stETH.balanceOf(address(this));
// approve the wstETH
Expand All @@ -70,4 +69,19 @@ contract LidoSplit is Clone {
// transfer to split wallet
ERC20(wstETH).safeTransfer(splitWallet(), amount);
}

/// @notice Rescue stuck ETH
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// @notice Rescue stuck ETH
/// @notice Rescue stuck ETH or ERC20 by pushing to the reward address

I was going to ask should this method have a modifier to protect it, but then realised it didn't matter because the funds can only go one place.

/// Uses token == address(0) to represent ETH
/// @return balance Amount of ETH rescued
function rescueFunds(address token) external returns (uint256 balance) {
if (token == address(stETH)) revert Invalid_Address();

if (token == ETH_ADDRESS) {
balance = address(this).balance;
if (balance > 0) splitWallet().safeTransferETH(balance);
} else {
balance = ERC20(token).balanceOf(address(this));
if (balance > 0) ERC20(token).transfer(splitWallet(), balance);
}
}
}
12 changes: 2 additions & 10 deletions src/lido/LidoSplitFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,11 @@ contract LidoSplitFactory {
/// storage
/// -----------------------------------------------------------------------

/// @notice stETH token address
ERC20 public immutable stETH;

/// @notice wstETH token address
ERC20 public immutable wstETH;

/// @dev lido split implementation
LidoSplit public immutable lidoSplitImpl;

constructor(ERC20 _stETH, ERC20 _wstETH) {
stETH = _stETH;
wstETH = _wstETH;
lidoSplitImpl = new LidoSplit();
lidoSplitImpl = new LidoSplit(_stETH, _wstETH);
}

/// Creates a wrapper for splitWallet that transforms stETH token into
Expand All @@ -55,7 +47,7 @@ contract LidoSplitFactory {
function createSplit(address splitWallet) external returns (address lidoSplit) {
if (splitWallet == address(0)) revert Invalid_Wallet();

lidoSplit = address(lidoSplitImpl).clone(abi.encodePacked(stETH, wstETH, splitWallet));
lidoSplit = address(lidoSplitImpl).clone(abi.encodePacked(splitWallet));

emit CreateLidoSplit(lidoSplit);
}
Expand Down
6 changes: 3 additions & 3 deletions src/test/lido/LIdoSplitFactory.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ contract LidoSplitFactoryTest is LidoSplitTestHelper, Test {
vm.createSelectFork(getChain("mainnet").rpcUrl, mainnetBlock);

lidoSplitFactory = new LidoSplitFactory(
ERC20(STETH_MAINNET_ADDRESS),
ERC20(WSTETH_MAINNET_ADDRESS)
);
ERC20(STETH_MAINNET_ADDRESS),
ERC20(WSTETH_MAINNET_ADDRESS)
);

demoSplit = makeAddr("demoSplit");
}
Expand Down
40 changes: 34 additions & 6 deletions src/test/lido/LidoSplit.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@ import "forge-std/Test.sol";
import {LidoSplitFactory, LidoSplit} from "src/lido/LidoSplitFactory.sol";
import {ERC20} from "solmate/tokens/ERC20.sol";
import {LidoSplitTestHelper} from "./LidoSplitTestHelper.sol";
import { MockERC20 } from "src/test/utils/mocks/MockERC20.sol";


contract LidoSplitTest is LidoSplitTestHelper, Test {
LidoSplitFactory internal lidoSplitFactory;
LidoSplit internal lidoSplit;

address demoSplit;

MockERC20 mERC20;

function setUp() public {
uint256 mainnetBlock = 17_421_005;
vm.createSelectFork(getChain("mainnet").rpcUrl, mainnetBlock);
Expand All @@ -24,12 +28,40 @@ contract LidoSplitTest is LidoSplitTestHelper, Test {
demoSplit = makeAddr("demoSplit");

lidoSplit = LidoSplit(lidoSplitFactory.createSplit(demoSplit));

mERC20 = new MockERC20("Test Token", "TOK", 18);
mERC20.mint(type(uint256).max);
}

function test_CloneArgsIsCorrect() public {
assertEq(lidoSplit.splitWallet(), demoSplit, "invalid address");
assertEq(lidoSplit.stETHAddress(), STETH_MAINNET_ADDRESS, "invalid stETH address");
assertEq(lidoSplit.wstETHAddress(), WSTETH_MAINNET_ADDRESS, "invalid wstETH address");
assertEq(address(lidoSplit.stETH()), STETH_MAINNET_ADDRESS, "invalid stETH address");
assertEq(address(lidoSplit.wstETH()), WSTETH_MAINNET_ADDRESS, "invalid wstETH address");
}

function test_CanRescueFunds() public {
// rescue ETH
uint256 amountOfEther = 1 ether;
deal(address(lidoSplit), amountOfEther);

uint256 balance = lidoSplit.rescueFunds(address(0));
assertEq(balance, amountOfEther, "balance not rescued");
assertEq(address(lidoSplit).balance, 0, "balance is not zero");
assertEq(address(lidoSplit.splitWallet()).balance, amountOfEther, "rescue not successful");

// rescue tokens
mERC20.transfer(address(lidoSplit), amountOfEther);
uint256 tokenBalance = lidoSplit.rescueFunds(address(mERC20));
assertEq(tokenBalance, amountOfEther, "token - balance not rescued");
assertEq(mERC20.balanceOf(address(lidoSplit)), 0, "token - balance is not zero");
assertEq(mERC20.balanceOf(lidoSplit.splitWallet()), amountOfEther, "token - rescue not successful");
}

function testCannot_RescueLidoTokens() public {
vm.expectRevert(
LidoSplit.Invalid_Address.selector
);
lidoSplit.rescueFunds(address(STETH_MAINNET_ADDRESS));
}

function test_CanDistribute() public {
Expand All @@ -46,10 +78,6 @@ contract LidoSplitTest is LidoSplitTestHelper, Test {

uint256 afterBalance = ERC20(WSTETH_MAINNET_ADDRESS).balanceOf(demoSplit);

console.log("checking");
console.log(afterBalance);
console.log(prevBalance);

assertGe(afterBalance, prevBalance, "after balance greater");
}
}
Loading