diff --git a/src/Governance.sol b/src/Governance.sol index 0e5a4af..209c5d6 100644 --- a/src/Governance.sol +++ b/src/Governance.sol @@ -192,7 +192,7 @@ contract Governance is Multicall, UserProxyFactory, ReentrancyGuard, IGovernance // check if user has enough unallocated lqty require(_lqtyAmount <= lqtyStaked - userState.allocatedLQTY, "Governance: insufficient-unallocated-lqty"); - (uint256 accruedLUSD, uint256 accruedETH) = userProxy.unstake(_lqtyAmount, msg.sender, msg.sender); + (uint256 accruedLUSD, uint256 accruedETH) = userProxy.unstake(_lqtyAmount, msg.sender); emit WithdrawLQTY(msg.sender, _lqtyAmount, accruedLUSD, accruedETH); } @@ -201,7 +201,7 @@ contract Governance is Multicall, UserProxyFactory, ReentrancyGuard, IGovernance function claimFromStakingV1(address _rewardRecipient) external returns (uint256 accruedLUSD, uint256 accruedETH) { address payable userProxyAddress = payable(deriveUserProxyAddress(msg.sender)); require(userProxyAddress.code.length != 0, "Governance: user-proxy-not-deployed"); - return UserProxy(userProxyAddress).unstake(0, _rewardRecipient, _rewardRecipient); + return UserProxy(userProxyAddress).unstake(0, _rewardRecipient); } /*////////////////////////////////////////////////////////////// diff --git a/src/UserProxy.sol b/src/UserProxy.sol index 66d44a6..d6f107f 100644 --- a/src/UserProxy.sol +++ b/src/UserProxy.sol @@ -61,7 +61,7 @@ contract UserProxy is IUserProxy { } /// @inheritdoc IUserProxy - function unstake(uint256 _amount, address _lqtyRecipient, address _lusdEthRecipient) + function unstake(uint256 _amount, address _recipient) public onlyStakingV2 returns (uint256 lusdAmount, uint256 ethAmount) @@ -69,16 +69,16 @@ contract UserProxy is IUserProxy { stakingV1.unstake(_amount); uint256 lqtyAmount = lqty.balanceOf(address(this)); - if (lqtyAmount > 0) lqty.safeTransfer(_lqtyRecipient, lqtyAmount); + if (lqtyAmount > 0) lqty.safeTransfer(_recipient, lqtyAmount); lusdAmount = lusd.balanceOf(address(this)); - if (lusdAmount > 0) lusd.safeTransfer(_lusdEthRecipient, lusdAmount); + if (lusdAmount > 0) lusd.safeTransfer(_recipient, lusdAmount); ethAmount = address(this).balance; if (ethAmount > 0) { - (bool success,) = payable(_lusdEthRecipient).call{value: ethAmount}(""); + (bool success,) = payable(_recipient).call{value: ethAmount}(""); require(success, "UserProxy: eth-fail"); } - emit Unstake(_amount, _lqtyRecipient, _lusdEthRecipient, lusdAmount, ethAmount); + emit Unstake(_amount, _recipient, lusdAmount, ethAmount); } /// @inheritdoc IUserProxy diff --git a/src/interfaces/IUserProxy.sol b/src/interfaces/IUserProxy.sol index bd8c041..7a7358f 100644 --- a/src/interfaces/IUserProxy.sol +++ b/src/interfaces/IUserProxy.sol @@ -10,7 +10,7 @@ import {PermitParams} from "../utils/Types.sol"; interface IUserProxy { event Stake(uint256 amount, address lqtyFrom); event Unstake( - uint256 lqtyUnstaked, address lqtyRecipient, address lusdEthRecipient, uint256 lusdAmount, uint256 ethAmount + uint256 lqtyUnstaked, address indexed lqtyRecipient, uint256 lusdAmount, uint256 ethAmount ); /// @notice Address of the LQTY token @@ -38,11 +38,10 @@ interface IUserProxy { function stakeViaPermit(uint256 _amount, address _lqtyFrom, PermitParams calldata _permitParams) external; /// @notice Unstakes a given amount of LQTY tokens from the V1 staking contract and claims the accrued rewards /// @param _amount Amount of LQTY tokens to unstake - /// @param _lqtyRecipient Address to which the unstaked LQTY tokens should be sent - /// @param _lusdEthRecipient Address to which the unstaked LUSD and ETH rewards should be sent + /// @param _recipient Address to which the tokens should be sent /// @return lusdAmount Amount of LUSD tokens claimed /// @return ethAmount Amount of ETH claimed - function unstake(uint256 _amount, address _lqtyRecipient, address _lusdEthRecipient) + function unstake(uint256 _amount, address _recipient) external returns (uint256 lusdAmount, uint256 ethAmount); /// @notice Returns the current amount LQTY staked by a user in the V1 staking contract diff --git a/test/SafeCallWithMinGas.t.sol b/test/SafeCallWithMinGas.t.sol new file mode 100644 index 0000000..1b76df8 --- /dev/null +++ b/test/SafeCallWithMinGas.t.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import {Test} from "forge-std/Test.sol"; + +import {safeCallWithMinGas} from "src/utils/SafeCallMinGas.sol"; + +contract BasicRecipient { + bool public callWasValid; + + function validCall() external { + callWasValid = true; + } +} +contract FallbackRecipient { + bytes public received; + + fallback() external payable { + received = msg.data; + } +} + +contract SafeCallWithMinGasTests is Test { + + function test_basic_nonExistent(uint256 gas, uint256 value, bytes memory theData) public { + vm.assume(gas < 30_000_000); + // Call to non existent succeeds + address nonExistent = address(0x123123123); + assert(nonExistent.code.length == 0); + + safeCallWithMinGas(address(0x123123123), gas, value, theData); + } + + function test_basic_contractData(uint256 gas, uint256 value, bytes memory theData) public { + vm.assume(gas < 30_000_000); + vm.assume(gas > 50_000 + theData.length * 2_100); /// @audit Approximation + FallbackRecipient recipient = new FallbackRecipient(); + // Call to non existent succeeds + + vm.deal(address(this), value); + + safeCallWithMinGas(address(recipient), gas, value, theData); + assertEq(keccak256(recipient.received()), keccak256(theData), "same data"); + } + function test_basic_contractCall() public { + BasicRecipient recipient = new BasicRecipient(); + // Call to non existent succeeds + + safeCallWithMinGas(address(recipient), 35_000, 0, abi.encodeCall(BasicRecipient.validCall, ())); + assertEq(recipient.callWasValid(), true, "Call success"); + } +} \ No newline at end of file diff --git a/test/UserProxy.t.sol b/test/UserProxy.t.sol index d35a98a..789abff 100644 --- a/test/UserProxy.t.sol +++ b/test/UserProxy.t.sol @@ -111,7 +111,7 @@ contract UserProxyTest is Test { userProxy.stake(1e18, user); - (uint256 lusdAmount, uint256 ethAmount) = userProxy.unstake(0, user, user); + (uint256 lusdAmount, uint256 ethAmount) = userProxy.unstake(0, user); assertEq(lusdAmount, 0); assertEq(ethAmount, 0); @@ -123,7 +123,7 @@ contract UserProxyTest is Test { uint256 lusdBalance = uint256(vm.load(stakingV1, bytes32(uint256(4)))); vm.store(stakingV1, bytes32(uint256(4)), bytes32(abi.encodePacked(lusdBalance + 1e18))); - (lusdAmount, ethAmount) = userProxy.unstake(1e18, user, user); + (lusdAmount, ethAmount) = userProxy.unstake(1e18, user); assertEq(lusdAmount, 1e18); assertEq(ethAmount, 1e18);