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

feat: more useful staking events in Governance #98

Merged
merged 3 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
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 Multicall, UserProxyFactory, ReentrancyGuard, Ownable, IG
);
userStates[msg.sender] = userState;

emit DepositLQTY(msg.sender, _lqtyAmount);

return userProxy;
}

Expand All @@ -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
Expand All @@ -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
Expand All @@ -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) =
Copy link
Contributor

Choose a reason for hiding this comment

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

I guess lqty values will always be zero here, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

lqtyReceived yes, but lqtySent may be positive if someone has previously sent LQTY to the UserProxy address, which UserProxy.unstake() would send to _rewardRecipient. This is documented in IGovernance.sol.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I considered returning the value of lqtySent, but in the end I didn't do it because it wouldn't normally be useful.

UserProxy(userProxyAddress).unstake(0, true, _rewardRecipient);

emit WithdrawLQTY(
msg.sender, _rewardRecipient, lqtyReceived, lqtySent, lusdReceived, lusdSent, ethReceived, ethSent
);
}

/*//////////////////////////////////////////////////////////////
Expand Down
76 changes: 46 additions & 30 deletions src/UserProxy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand All @@ -71,50 +85,52 @@ contract UserProxy is IUserProxy {
_permitParams.r,
_permitParams.s
) {} catch {}
stake(_amount, _lqtyFrom, _doSendRewards, _recipient);

if (_doSendRewards) {
(lusdAmount, ethAmount) = _sendRewards(_recipient, initialLUSDAmount, initialETHAmount);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removing duplicate _sendRewards() (it's already called by stake() above.

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
62 changes: 54 additions & 8 deletions src/interfaces/IGovernance.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
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
Loading