Platform: Code4rena
Start Date: 24/03/2023
Pot Size: $49,200 USDC
Total HM: 20
Participants: 246
Period: 6 days
Judge: Picodes
Total Solo HM: 1
Id: 226
League: ETH
Rank: 204/246
Findings: 1
Award: $11.13
🌟 Selected for report: 0
🚀 Solo Findings: 0
11.1318 USDC - $11.13
https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/SafEth.sol#L63-L101 https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/SafEth.sol#L108-L129
Stake function does not check for zero derivativeCount
User funds can be locked in the contract with no way to withdraw
setUp
function create new safEth
contract and reth
, sfrxEth
, wstEth
derivatives.test_normal_behevior
shows how contract works in normal situation.test_user_lock_poc
shows how contract works when derivatives was not yet added.NOTE: Deploy local node to have initial balance for USER
s: anvil -f <MAINNET RPC>
NOTE: Run test with forge test -f http://127.0.0.1:8545 -vvv
// SPDX-License-Identifier: MIT pragma solidity ^0.8.13; import "../../contracts/SafEth/SafEth.sol"; import "../../contracts/SafEth/derivatives/Reth.sol"; import "../../contracts/SafEth/derivatives/SfrxEth.sol"; import "../../contracts/SafEth/derivatives/WstEth.sol"; import "forge-std/Test.sol"; import "forge-std/console2.sol"; import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; contract SafEthAudit is Test { // @note Anvil addresses uint256 private constant MAX_BALANCE = 1e22; address private constant USER = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266; address private constant USER2 = 0x70997970C51812dc3A010C7d01b50e0d17dc79C8; address private constant USER3 = 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC; string private constant NAME = "AuditSafEth"; string private constant SYMBOL = "ASE"; SafEth safEth; Reth reth; SfrxEth sfrxEth; WstEth wstEth; uint256 private immutable minAmount; uint256 private immutable maxAmount; constructor() { SafEth _safEth = new SafEth(); safEth = SafEth( payable( address( new ERC1967Proxy( address(_safEth), abi.encodeWithSelector( SafEth.initialize.selector, NAME, SYMBOL ) ) ) ) ); minAmount = safEth.minAmount(); maxAmount = safEth.maxAmount(); Reth _reth = new Reth(); reth = Reth( payable( address( new ERC1967Proxy( address(_reth), abi.encodeWithSelector( Reth.initialize.selector, address(safEth) ) ) ) ) ); SfrxEth _sfrxEth = new SfrxEth(); sfrxEth = SfrxEth( payable( address( new ERC1967Proxy( address(_sfrxEth), abi.encodeWithSelector( SfrxEth.initialize.selector, address(safEth) ) ) ) ) ); WstEth _wstEth = new WstEth(); wstEth = WstEth( payable( address( new ERC1967Proxy( address(_wstEth), abi.encodeWithSelector( WstEth.initialize.selector, address(safEth) ) ) ) ) ); } function test_user_lock_poc() public { // @note currently there is not derivatives added safEth.derivativeCount(); console2.log("User ETH before:", USER.balance / 1e18); stake(USER, safEth.maxAmount(), false, false); console2.log("User ETH after stake:", USER.balance / 1e18); console2.log("SafEth ETH after stake:", address(safEth).balance / 1e18); // @note user can't withdraw his ETH because the is zero ETH in zero derivatives // @note The is no function to return user his locked ETH unstacke(USER, safEth.maxAmount(), false, true); console2.log("User ETH after stake:", USER.balance / 1e18); console2.log("SafEth ETH after stake:", address(safEth).balance / 1e18); add_derivatives(); unstacke(USER, safEth.maxAmount(), false, true); // @note Even after adding derivatives funds still locked console2.log("User ETH after stake:", USER.balance / 1e18); console2.log("SafEth ETH after stake:", address(safEth).balance / 1e18); } function stake( address _user, uint256 _amount, bool require_bound, bool will_revert ) public { if (require_bound) { if (maxAmount > MAX_BALANCE) { _amount = bound(_amount, minAmount, MAX_BALANCE); } else { _amount = bound(_amount, minAmount, maxAmount); } } vm.startPrank(_user); if (will_revert) vm.expectRevert(); safEth.stake{value: _amount}(); vm.stopPrank(); } function unstacke( address _user, uint256 _safEthAmount, bool require_bound, bool will_revert ) public { if (require_bound) { _safEthAmount = bound(_safEthAmount, 0, safEth.balanceOf(_user)); } vm.startPrank(_user); if (will_revert) vm.expectRevert(); safEth.unstake(_safEthAmount); vm.stopPrank(); } function add_derivatives() public { safEth.addDerivative(address(reth), 200); safEth.addDerivative(address(sfrxEth), 400); safEth.addDerivative(address(wstEth), 300); } function test_normal_behevior() public { add_derivatives(); safEth.derivativeCount(); console2.log("User ETH before:", USER.balance / 1e18); stake(USER, safEth.maxAmount(), false, false); console2.log("User ETH after stake:", USER.balance / 1e18); console2.log("SafEth ETH after stake:", address(safEth).balance / 1e18); unstacke(USER, safEth.maxAmount(), false, false); console2.log("User ETH after stake:", USER.balance / 1e18); console2.log("SafEth ETH after stake:", address(safEth).balance / 1e18); } }
Manual review, foundry for testing
diff --git a/contracts/SafEth/SafEth.sol b/contracts/SafEth/SafEth.sol index ebadb4b..7502ddb 100644 --- a/contracts/SafEth/SafEth.sol +++ b/contracts/SafEth/SafEth.sol @@ -64,6 +64,7 @@ contract SafEth is require(pauseStaking == false, "staking is paused"); require(msg.value >= minAmount, "amount too low"); require(msg.value <= maxAmount, "amount too high"); + require(derivativeCount != 0, "there is no derivative in contract"); uint256 underlyingValue = 0;
#0 - c4-pre-sort
2023-04-04T19:33:09Z
0xSorryNotSorry marked the issue as duplicate of #363
#1 - c4-judge
2023-04-21T16:29:01Z
Picodes changed the severity to 2 (Med Risk)
#2 - c4-judge
2023-04-21T16:31:50Z
Picodes marked the issue as satisfactory