Skip to content

Commit

Permalink
Merge pull request #98 from liquity/staking-events
Browse files Browse the repository at this point in the history
feat: more useful staking events in `Governance`
  • Loading branch information
danielattilasimon authored Dec 10, 2024
2 parents 2ac1f97 + d42b2ce commit 5b6db05
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 68 deletions.
42 changes: 32 additions & 10 deletions src/Governance.sol
Original file line number Diff line number Diff line change
Expand Up @@ -188,8 +188,6 @@ contract Governance is MultiDelegateCall, UserProxyFactory, ReentrancyGuard, Own
);
userStates[msg.sender] = userState;

emit DepositLQTY(msg.sender, _lqtyAmount);

return userProxy;
}

Expand All @@ -200,7 +198,11 @@ contract Governance is MultiDelegateCall, UserProxyFactory, ReentrancyGuard, Own

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
Expand All @@ -215,7 +217,11 @@ contract Governance is MultiDelegateCall, UserProxyFactory, ReentrancyGuard, Own
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
Expand All @@ -231,18 +237,34 @@ contract Governance is MultiDelegateCall, UserProxyFactory, ReentrancyGuard, Own
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
);
}

/*//////////////////////////////////////////////////////////////
Expand Down
62 changes: 32 additions & 30 deletions src/UserProxy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,21 @@ 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
Expand All @@ -56,12 +59,9 @@ 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,
Expand All @@ -71,50 +71,52 @@ 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));
uint256 initialETHAmount = address(this).balance;

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
Expand Down
78 changes: 71 additions & 7 deletions src/interfaces/IGovernance.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,43 @@ 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);
/// @notice Emitted when a user deposits LQTY
/// @param user The account depositing LQTY
/// @param rewardRecipient The account receiving the LUSD/ETH rewards earned from staking in V1, if claimed
/// @param lqtyAmount The amount of LQTY being deposited
/// @return lusdReceived Amount of LUSD tokens received as a side-effect of staking new LQTY
/// @return lusdSent Amount of LUSD tokens sent to `rewardRecipient` (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 `rewardRecipient` (may include previously received ETH)
event DepositLQTY(
address indexed user,
address rewardRecipient,
uint256 lqtyAmount,
uint256 lusdReceived,
uint256 lusdSent,
uint256 ethReceived,
uint256 ethSent
);

/// @notice Emitted when a user withdraws LQTY or claims V1 staking rewards
/// @param user The account withdrawing LQTY or claiming V1 staking rewards
/// @param recipient The account receiving the LQTY withdrawn, and if claimed, the LUSD/ETH rewards earned from staking in V1
/// @return lqtyReceived Amount of LQTY tokens actually withdrawn (may be lower than the `_lqtyAmount` passed to `withdrawLQTY`)
/// @return lqtySent Amount of LQTY tokens sent to `recipient` (may include LQTY sent to the user's 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 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)
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);
Expand Down Expand Up @@ -185,21 +220,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 memory _permitParams) external;
function depositLQTYViaPermit(uint88 _lqtyAmount, PermitParams calldata _permitParams) external;

/// @notice Deposits LQTY via Permit
/// @param _lqtyAmount Amount of LQTY to deposit
/// @param _permitParams Permit parameters
/// @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
Expand Down
46 changes: 27 additions & 19 deletions src/interfaces/IUserProxy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions test/UserProxy.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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);

Expand Down

0 comments on commit 5b6db05

Please sign in to comment.