diff --git a/src/Governance.sol b/src/Governance.sol index fb200cc..c28bf56 100644 --- a/src/Governance.sol +++ b/src/Governance.sol @@ -188,8 +188,6 @@ contract Governance is Multicall, UserProxyFactory, ReentrancyGuard, Ownable, IG ); userStates[msg.sender] = userState; - emit DepositLQTY(msg.sender, _lqtyAmount); - return userProxy; } @@ -200,7 +198,11 @@ contract Governance is Multicall, UserProxyFactory, ReentrancyGuard, Ownable, IG function depositLQTY(uint88 _lqtyAmount, bool _doSendRewards, address _recipient) public nonReentrant { UserProxy userProxy = _updateUserTimestamp(_lqtyAmount); - userProxy.stake(_lqtyAmount, msg.sender, _doSendRewards, _recipient); + + (uint256 lusdReceived, uint256 lusdSent, uint256 ethReceived, uint256 ethSent) = + userProxy.stake(_lqtyAmount, msg.sender, _doSendRewards, _recipient); + + emit DepositLQTY(msg.sender, _recipient, _lqtyAmount, lusdReceived, lusdSent, ethReceived, ethSent); } /// @inheritdoc IGovernance @@ -215,7 +217,11 @@ contract Governance is Multicall, UserProxyFactory, ReentrancyGuard, Ownable, IG address _recipient ) public nonReentrant { UserProxy userProxy = _updateUserTimestamp(_lqtyAmount); - userProxy.stakeViaPermit(_lqtyAmount, msg.sender, _permitParams, _doSendRewards, _recipient); + + (uint256 lusdReceived, uint256 lusdSent, uint256 ethReceived, uint256 ethSent) = + userProxy.stakeViaPermit(_lqtyAmount, msg.sender, _permitParams, _doSendRewards, _recipient); + + emit DepositLQTY(msg.sender, _recipient, _lqtyAmount, lusdReceived, lusdSent, ethReceived, ethSent); } /// @inheritdoc IGovernance @@ -231,18 +237,34 @@ contract Governance is Multicall, UserProxyFactory, ReentrancyGuard, Ownable, IG UserProxy userProxy = UserProxy(payable(deriveUserProxyAddress(msg.sender))); require(address(userProxy).code.length != 0, "Governance: user-proxy-not-deployed"); - uint88 lqtyStaked = uint88(stakingV1.stakes(address(userProxy))); + ( + uint256 lqtyReceived, + uint256 lqtySent, + uint256 lusdReceived, + uint256 lusdSent, + uint256 ethReceived, + uint256 ethSent + ) = userProxy.unstake(_lqtyAmount, _doSendRewards, _recipient); - (uint256 accruedLUSD, uint256 accruedETH) = userProxy.unstake(_lqtyAmount, _doSendRewards, _recipient); - - emit WithdrawLQTY(msg.sender, _lqtyAmount, accruedLUSD, accruedETH); + emit WithdrawLQTY(msg.sender, _recipient, lqtyReceived, lqtySent, lusdReceived, lusdSent, ethReceived, ethSent); } /// @inheritdoc IGovernance - function claimFromStakingV1(address _rewardRecipient) external returns (uint256 accruedLUSD, uint256 accruedETH) { + function claimFromStakingV1(address _rewardRecipient) external returns (uint256 lusdSent, uint256 ethSent) { address payable userProxyAddress = payable(deriveUserProxyAddress(msg.sender)); require(userProxyAddress.code.length != 0, "Governance: user-proxy-not-deployed"); - return UserProxy(userProxyAddress).unstake(0, true, _rewardRecipient); + + uint256 lqtyReceived; + uint256 lqtySent; + uint256 lusdReceived; + uint256 ethReceived; + + (lqtyReceived, lqtySent, lusdReceived, lusdSent, ethReceived, ethSent) = + UserProxy(userProxyAddress).unstake(0, true, _rewardRecipient); + + emit WithdrawLQTY( + msg.sender, _rewardRecipient, lqtyReceived, lqtySent, lusdReceived, lusdSent, ethReceived, ethSent + ); } /*////////////////////////////////////////////////////////////// diff --git a/src/UserProxy.sol b/src/UserProxy.sol index dc801f3..b8d0367 100644 --- a/src/UserProxy.sol +++ b/src/UserProxy.sol @@ -35,18 +35,26 @@ contract UserProxy is IUserProxy { function stake(uint256 _amount, address _lqtyFrom, bool _doSendRewards, address _recipient) public onlyStakingV2 - returns (uint256 lusdAmount, uint256 ethAmount) + returns ( + uint256 lusdReceived, + uint256 lusdSent, + uint256 ethReceived, + uint256 ethSent + ) { uint256 initialLUSDAmount = lusd.balanceOf(address(this)); uint256 initialETHAmount = address(this).balance; lqty.transferFrom(_lqtyFrom, address(this), _amount); stakingV1.stake(_amount); - emit Stake(_amount, _lqtyFrom); - if (_doSendRewards) { - (lusdAmount, ethAmount) = _sendRewards(_recipient, initialLUSDAmount, initialETHAmount); - } + uint256 lusdAmount = lusd.balanceOf(address(this)); + uint256 ethAmount = address(this).balance; + + lusdReceived = lusdAmount - initialLUSDAmount; + ethReceived = ethAmount - initialETHAmount; + + if (_doSendRewards) (lusdSent, ethSent) = _sendRewards(_recipient, lusdAmount, ethAmount); } /// @inheritdoc IUserProxy @@ -56,12 +64,18 @@ contract UserProxy is IUserProxy { PermitParams calldata _permitParams, bool _doSendRewards, address _recipient - ) external onlyStakingV2 returns (uint256 lusdAmount, uint256 ethAmount) { + ) + external + onlyStakingV2 + returns ( + uint256 lusdReceived, + uint256 lusdSent, + uint256 ethReceived, + uint256 ethSent + ) + { require(_lqtyFrom == _permitParams.owner, "UserProxy: owner-not-sender"); - uint256 initialLUSDAmount = lusd.balanceOf(address(this)); - uint256 initialETHAmount = address(this).balance; - try IERC20Permit(address(lqty)).permit( _permitParams.owner, _permitParams.spender, @@ -71,18 +85,22 @@ contract UserProxy is IUserProxy { _permitParams.r, _permitParams.s ) {} catch {} - stake(_amount, _lqtyFrom, _doSendRewards, _recipient); - if (_doSendRewards) { - (lusdAmount, ethAmount) = _sendRewards(_recipient, initialLUSDAmount, initialETHAmount); - } + return stake(_amount, _lqtyFrom, _doSendRewards, _recipient); } /// @inheritdoc IUserProxy function unstake(uint256 _amount, bool _doSendRewards, address _recipient) external onlyStakingV2 - returns (uint256 lusdAmount, uint256 ethAmount) + returns ( + uint256 lqtyReceived, + uint256 lqtySent, + uint256 lusdReceived, + uint256 lusdSent, + uint256 ethReceived, + uint256 ethSent + ) { uint256 initialLQTYAmount = lqty.balanceOf(address(this)); uint256 initialLUSDAmount = lusd.balanceOf(address(this)); @@ -90,31 +108,29 @@ contract UserProxy is IUserProxy { stakingV1.unstake(_amount); - uint256 lqtyAmount = lqty.balanceOf(address(this)); - if (lqtyAmount > 0) lqty.transfer(_recipient, lqtyAmount); + lqtySent = lqty.balanceOf(address(this)); + uint256 lusdAmount = lusd.balanceOf(address(this)); + uint256 ethAmount = address(this).balance; - emit Unstake(_recipient, lqtyAmount - initialLQTYAmount, lqtyAmount); + lqtyReceived = lqtySent - initialLQTYAmount; + lusdReceived = lusdAmount - initialLUSDAmount; + ethReceived = ethAmount - initialETHAmount; - if (_doSendRewards) { - (lusdAmount, ethAmount) = _sendRewards(_recipient, initialLUSDAmount, initialETHAmount); - } + if (lqtySent > 0) lqty.transfer(_recipient, lqtySent); + if (_doSendRewards) (lusdSent, ethSent) = _sendRewards(_recipient, lusdAmount, ethAmount); } - function _sendRewards(address _recipient, uint256 _initialLUSDAmount, uint256 _initialETHAmount) + function _sendRewards(address _recipient, uint256 _lusdAmount, uint256 _ethAmount) internal - returns (uint256 lusdAmount, uint256 ethAmount) + returns (uint256 lusdSent, uint256 ethSent) { - lusdAmount = lusd.balanceOf(address(this)); - if (lusdAmount > 0) lusd.transfer(_recipient, lusdAmount); - ethAmount = address(this).balance; - if (ethAmount > 0) { - (bool success,) = payable(_recipient).call{value: ethAmount}(""); + if (_lusdAmount > 0) lusd.transfer(_recipient, _lusdAmount); + if (_ethAmount > 0) { + (bool success,) = payable(_recipient).call{value: _ethAmount}(""); require(success, "UserProxy: eth-fail"); } - emit SendRewards( - _recipient, lusdAmount - _initialLUSDAmount, lusdAmount, ethAmount - _initialETHAmount, ethAmount - ); + return (_lusdAmount, _ethAmount); } /// @inheritdoc IUserProxy diff --git a/src/interfaces/IGovernance.sol b/src/interfaces/IGovernance.sol index 5b867db..73b9f96 100644 --- a/src/interfaces/IGovernance.sol +++ b/src/interfaces/IGovernance.sol @@ -8,8 +8,26 @@ import {ILQTYStaking} from "./ILQTYStaking.sol"; import {PermitParams} from "../utils/Types.sol"; interface IGovernance { - event DepositLQTY(address indexed user, uint256 depositedLQTY); - event WithdrawLQTY(address indexed user, uint256 withdrawnLQTY, uint256 accruedLUSD, uint256 accruedETH); + event DepositLQTY( + address indexed user, + address rewardRecipient, + uint256 lqtyAmount, + uint256 lusdReceived, + uint256 lusdSent, + uint256 ethReceived, + uint256 ethSent + ); + + event WithdrawLQTY( + address indexed user, + address recipient, + uint256 lqtyReceived, + uint256 lqtySent, + uint256 lusdReceived, + uint256 lusdSent, + uint256 ethReceived, + uint256 ethSent + ); event SnapshotVotes(uint256 votes, uint256 forEpoch, uint256 boldAccrued); event SnapshotVotesForInitiative(address indexed initiative, uint256 votes, uint256 vetos, uint256 forEpoch); @@ -137,7 +155,6 @@ interface IGovernance { uint88 countedVoteLQTY; // Total LQTY that is included in vote counting uint120 countedVoteLQTYAverageTimestamp; // Average timestamp: derived initiativeAllocation.averageTimestamp } - /// TODO: Bold balance? Prob cheaper /// @notice Returns the user's state /// @param _user Address of the user @@ -186,21 +203,50 @@ interface IGovernance { //////////////////////////////////////////////////////////////*/ /// @notice Deposits LQTY - /// @dev The caller has to approve this contract to spend the LQTY tokens + /// @dev The caller has to approve their `UserProxy` address to spend the LQTY tokens /// @param _lqtyAmount Amount of LQTY to deposit function depositLQTY(uint88 _lqtyAmount) external; + + /// @notice Deposits LQTY + /// @dev The caller has to approve their `UserProxy` address to spend the LQTY tokens + /// @param _lqtyAmount Amount of LQTY to deposit + /// @param _doSendRewards If true, send rewards claimed from LQTY staking + /// @param _recipient Address to which the tokens should be sent + function depositLQTY(uint88 _lqtyAmount, bool _doSendRewards, address _recipient) external; + + /// @notice Deposits LQTY via Permit + /// @param _lqtyAmount Amount of LQTY to deposit + /// @param _permitParams Permit parameters + function depositLQTYViaPermit(uint88 _lqtyAmount, PermitParams calldata _permitParams) external; + /// @notice Deposits LQTY via Permit /// @param _lqtyAmount Amount of LQTY to deposit /// @param _permitParams Permit parameters - function depositLQTYViaPermit(uint88 _lqtyAmount, PermitParams memory _permitParams) external; + /// @param _doSendRewards If true, send rewards claimed from LQTY staking + /// @param _recipient Address to which the tokens should be sent + function depositLQTYViaPermit( + uint88 _lqtyAmount, + PermitParams calldata _permitParams, + bool _doSendRewards, + address _recipient + ) external; + /// @notice Withdraws LQTY and claims any accrued LUSD and ETH rewards from StakingV1 /// @param _lqtyAmount Amount of LQTY to withdraw function withdrawLQTY(uint88 _lqtyAmount) external; + + /// @notice Withdraws LQTY and claims any accrued LUSD and ETH rewards from StakingV1 + /// @param _lqtyAmount Amount of LQTY to withdraw + /// @param _doSendRewards If true, send rewards claimed from LQTY staking + /// @param _recipient Address to which the tokens should be sent + function withdrawLQTY(uint88 _lqtyAmount, bool _doSendRewards, address _recipient) external; + /// @notice Claims staking rewards from StakingV1 without unstaking + /// @dev Note: in the unlikely event that the caller's `UserProxy` holds any LQTY tokens, they will also be sent to `_rewardRecipient` /// @param _rewardRecipient Address that will receive the rewards - /// @return accruedLUSD Amount of LUSD accrued - /// @return accruedETH Amount of ETH accrued - function claimFromStakingV1(address _rewardRecipient) external returns (uint256 accruedLUSD, uint256 accruedETH); + /// @return lusdSent Amount of LUSD tokens sent to `_rewardRecipient` (may include previously received LUSD) + /// @return ethSent Amount of ETH sent to `_rewardRecipient` (may include previously received ETH) + function claimFromStakingV1(address _rewardRecipient) external returns (uint256 lusdSent, uint256 ethSent); /*////////////////////////////////////////////////////////////// VOTING diff --git a/src/interfaces/IUserProxy.sol b/src/interfaces/IUserProxy.sol index 0a0423d..166c825 100644 --- a/src/interfaces/IUserProxy.sol +++ b/src/interfaces/IUserProxy.sol @@ -8,16 +8,6 @@ import {ILQTYStaking} from "../interfaces/ILQTYStaking.sol"; import {PermitParams} from "../utils/Types.sol"; interface IUserProxy { - event Stake(uint256 amount, address lqtyFrom); - event Unstake(address indexed lqtyRecipient, uint256 lqtyReceived, uint256 lqtySent); - event SendRewards( - address indexed recipient, - uint256 lusdAmountReceived, - uint256 lusdAmountSent, - uint256 ethAmountReceived, - uint256 ethAmountSent - ); - /// @notice Address of the LQTY token /// @return lqty Address of the LQTY token function lqty() external view returns (IERC20 lqty); @@ -37,35 +27,53 @@ interface IUserProxy { /// @param _lqtyFrom Address from which to transfer the LQTY tokens /// @param _doSendRewards If true, send rewards claimed from LQTY staking /// @param _recipient Address to which the tokens should be sent - /// @return lusdAmount Amount of LUSD tokens claimed - /// @return ethAmount Amount of ETH claimed + /// @return lusdReceived Amount of LUSD tokens received as a side-effect of staking new LQTY + /// @return lusdSent Amount of LUSD tokens sent to `_recipient` (may include previously received LUSD) + /// @return ethReceived Amount of ETH received as a side-effect of staking new LQTY + /// @return ethSent Amount of ETH sent to `_recipient` (may include previously received ETH) function stake(uint256 _amount, address _lqtyFrom, bool _doSendRewards, address _recipient) external - returns (uint256 lusdAmount, uint256 ethAmount); + returns (uint256 lusdReceived, uint256 lusdSent, uint256 ethReceived, uint256 ethSent); + /// @notice Stakes a given amount of LQTY tokens in the V1 staking contract using a permit /// @param _amount Amount of LQTY tokens to stake /// @param _lqtyFrom Address from which to transfer the LQTY tokens /// @param _permitParams Parameters for the permit data /// @param _doSendRewards If true, send rewards claimed from LQTY staking /// @param _recipient Address to which the tokens should be sent - /// @return lusdAmount Amount of LUSD tokens claimed - /// @return ethAmount Amount of ETH claimed + /// @return lusdReceived Amount of LUSD tokens received as a side-effect of staking new LQTY + /// @return lusdSent Amount of LUSD tokens sent to `_recipient` (may include previously received LUSD) + /// @return ethReceived Amount of ETH received as a side-effect of staking new LQTY + /// @return ethSent Amount of ETH sent to `_recipient` (may include previously received ETH) function stakeViaPermit( uint256 _amount, address _lqtyFrom, PermitParams calldata _permitParams, bool _doSendRewards, address _recipient - ) external returns (uint256 lusdAmount, uint256 ethAmount); + ) external returns (uint256 lusdReceived, uint256 lusdSent, uint256 ethReceived, uint256 ethSent); + /// @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 _doSendRewards If true, send rewards claimed from LQTY staking /// @param _recipient Address to which the tokens should be sent - /// @return lusdAmount Amount of LUSD tokens claimed - /// @return ethAmount Amount of ETH claimed + /// @return lqtyReceived Amount of LQTY tokens actually unstaked (may be lower than `_amount`) + /// @return lqtySent Amount of LQTY tokens sent to `_recipient` (may include LQTY sent to the proxy from sources other than V1 staking) + /// @return lusdReceived Amount of LUSD tokens received as a side-effect of staking new LQTY + /// @return lusdSent Amount of LUSD tokens claimed (may include previously received LUSD) + /// @return ethReceived Amount of ETH received as a side-effect of staking new LQTY + /// @return ethSent Amount of ETH claimed (may include previously received ETH) function unstake(uint256 _amount, bool _doSendRewards, address _recipient) external - returns (uint256 lusdAmount, uint256 ethAmount); + returns ( + uint256 lqtyReceived, + uint256 lqtySent, + uint256 lusdReceived, + uint256 lusdSent, + uint256 ethReceived, + uint256 ethSent + ); + /// @notice Returns the current amount LQTY staked by a user in the V1 staking contract /// @return staked Amount of LQTY tokens staked function staked() external view returns (uint88); diff --git a/test/UserProxy.t.sol b/test/UserProxy.t.sol index 158516a..abc650f 100644 --- a/test/UserProxy.t.sol +++ b/test/UserProxy.t.sol @@ -117,7 +117,7 @@ abstract contract UserProxyTest is Test, MockStakingV1Deployer { userProxy.stake(1e18, user, false, address(0)); - (uint256 lusdAmount, uint256 ethAmount) = userProxy.unstake(0, true, user); + (,, uint256 lusdAmount,, uint256 ethAmount,) = userProxy.unstake(0, true, user); assertEq(lusdAmount, 0); assertEq(ethAmount, 0); @@ -130,7 +130,7 @@ abstract contract UserProxyTest is Test, MockStakingV1Deployer { vm.startPrank(address(userProxyFactory)); - (lusdAmount, ethAmount) = userProxy.unstake(1e18, true, user); + (,, lusdAmount,, ethAmount,) = userProxy.unstake(1e18, true, user); assertEq(lusdAmount, 1e18); assertEq(ethAmount, 1e18);