Skip to content

Commit

Permalink
Make _fetchOrDeploySurrogate an abstract method (#74)
Browse files Browse the repository at this point in the history
* Make deploy and fetching surrogate an abstract contract
  • Loading branch information
alexkeating authored Nov 8, 2024
1 parent bc46ac2 commit a90fb14
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 21 deletions.
34 changes: 14 additions & 20 deletions src/GovernanceStaker.sol
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,6 @@ abstract contract GovernanceStaker is INotifiableRewardReceiver, Multicall {
/// @notice Emitted when a reward notifier address is enabled or disabled.
event RewardNotifierSet(address indexed account, bool isEnabled);

/// @notice Emitted when a surrogate contract is deployed.
event SurrogateDeployed(address indexed delegatee, address indexed surrogate);

/// @notice Thrown when an account attempts a call for which it lacks appropriate permission.
/// @param reason Human readable code explaining why the call is unauthorized.
/// @param caller The address that attempted the unauthorized call.
Expand Down Expand Up @@ -202,10 +199,6 @@ abstract contract GovernanceStaker is INotifiableRewardReceiver, Multicall {
/// @notice Stores the metadata associated with a given deposit.
mapping(DepositIdentifier depositId => Deposit deposit) public deposits;

/// @notice Maps the account of each governance delegate with the surrogate contract which holds
/// the staked tokens from deposits which assign voting weight to said delegate.
mapping(address delegatee => DelegationSurrogate surrogate) public surrogates;

/// @notice Time at which rewards distribution will complete if there are no new rewards.
uint256 public rewardEndTime;

Expand Down Expand Up @@ -284,6 +277,14 @@ abstract contract GovernanceStaker is INotifiableRewardReceiver, Multicall {
_setClaimFeeParameters(_params);
}

/// @notice A method to get a delegation surrogate contract for a given delegate.
/// @param _delegatee The address the delegation surrogate is delegating voting power.
/// @return The delegation surrogate.
/// @dev A concrete implementation should return a delegate surrogate address for a given
/// delegatee. In practice this may be as simple as returning an address stored in a mapping or
/// computing it's create2 address.
function surrogates(address _delegatee) public view virtual returns (DelegationSurrogate);

/// @notice Timestamp representing the last time at which rewards have been distributed, which is
/// either the current timestamp (because rewards are still actively being streamed) or the time
/// at which the reward duration ended (because all rewards to date have already been streamed).
Expand Down Expand Up @@ -523,19 +524,12 @@ abstract contract GovernanceStaker is INotifiableRewardReceiver, Multicall {
/// none exists—for a given delegatee.
/// @param _delegatee Account for which a surrogate is sought.
/// @return _surrogate The address of the surrogate contract for the delegatee.
/// @dev A concrete implementation would either deploy a new delegate surrogate or return an
/// existing surrogate for a given delegatee address.
function _fetchOrDeploySurrogate(address _delegatee)
internal
virtual
returns (DelegationSurrogate _surrogate)
{
_surrogate = surrogates[_delegatee];

if (address(_surrogate) == address(0)) {
_surrogate = new DelegationSurrogateVotes(STAKE_TOKEN, _delegatee);
surrogates[_delegatee] = _surrogate;
emit SurrogateDeployed(_delegatee, address(_surrogate));
}
}
returns (DelegationSurrogate _surrogate);

/// @notice Internal convenience method which calls the `transferFrom` method on the stake token
/// contract and reverts on failure.
Expand Down Expand Up @@ -601,7 +595,7 @@ abstract contract GovernanceStaker is INotifiableRewardReceiver, Multicall {
_checkpointGlobalReward();
_checkpointReward(deposit);

DelegationSurrogate _surrogate = surrogates[deposit.delegatee];
DelegationSurrogate _surrogate = surrogates(deposit.delegatee);

uint256 _newBalance = deposit.balance + _amount;
uint256 _newEarningPower =
Expand Down Expand Up @@ -629,7 +623,7 @@ abstract contract GovernanceStaker is INotifiableRewardReceiver, Multicall {
address _newDelegatee
) internal virtual {
_revertIfAddressZero(_newDelegatee);
DelegationSurrogate _oldSurrogate = surrogates[deposit.delegatee];
DelegationSurrogate _oldSurrogate = surrogates(deposit.delegatee);
uint256 _newEarningPower =
earningPowerCalculator.getEarningPower(deposit.balance, deposit.owner, _newDelegatee);

Expand Down Expand Up @@ -696,7 +690,7 @@ abstract contract GovernanceStaker is INotifiableRewardReceiver, Multicall {

deposit.balance = _newBalance.toUint96();
deposit.earningPower = _newEarningPower.toUint96();
_stakeTokenSafeTransferFrom(address(surrogates[deposit.delegatee]), deposit.owner, _amount);
_stakeTokenSafeTransferFrom(address(surrogates(deposit.delegatee)), deposit.owner, _amount);
emit StakeWithdrawn(deposit.owner, _depositId, _amount, deposit.balance);
}

Expand Down
40 changes: 40 additions & 0 deletions src/extensions/GovernanceStakerDelegateSurrogateVotes.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.23;

import {DelegationSurrogate} from "src/DelegationSurrogate.sol";
import {DelegationSurrogateVotes} from "src/DelegationSurrogateVotes.sol";
import {GovernanceStaker} from "src/GovernanceStaker.sol";

/// @title GovernaceStakerDelegateSurrogateVotes
/// @author [ScopeLift](https://scopelift.co)
/// @notice This contract extension adds delegation surrogates to the GovernanceStaker base
/// contract, allowing staked tokens to be delegated to a specific delegate.
abstract contract GovernanceStakerDelegateSurrogateVotes is GovernanceStaker {
/// @notice Emitted when a surrogate contract is deployed.
event SurrogateDeployed(address indexed delegatee, address indexed surrogate);

/// @notice Maps the account of each governance delegate with the surrogate contract which holds
/// the staked tokens from deposits which assign voting weight to said delegate.
mapping(address delegatee => DelegationSurrogate surrogate) private storedSurrogates;

/// @inheritdoc GovernanceStaker
function surrogates(address _delegatee) public view override returns (DelegationSurrogate) {
return storedSurrogates[_delegatee];
}

/// @inheritdoc GovernanceStaker
function _fetchOrDeploySurrogate(address _delegatee)
internal
virtual
override
returns (DelegationSurrogate _surrogate)
{
_surrogate = storedSurrogates[_delegatee];

if (address(_surrogate) == address(0)) {
_surrogate = new DelegationSurrogateVotes(STAKE_TOKEN, _delegatee);
storedSurrogates[_delegatee] = _surrogate;
emit SurrogateDeployed(_delegatee, address(_surrogate));
}
}
}
6 changes: 5 additions & 1 deletion test/harnesses/GovernanceStakerHarness.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import {DelegationSurrogateVotes} from "src/DelegationSurrogateVotes.sol";
import {GovernanceStaker} from "src/GovernanceStaker.sol";
import {GovernanceStakerPermitAndStake} from "src/extensions/GovernanceStakerPermitAndStake.sol";
import {GovernanceStakerOnBehalf} from "src/extensions/GovernanceStakerOnBehalf.sol";
import {GovernanceStakerDelegateSurrogateVotes} from
"src/extensions/GovernanceStakerDelegateSurrogateVotes.sol";

import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol";
import {SafeERC20} from "openzeppelin/token/ERC20/utils/SafeERC20.sol";
import {EIP712} from "openzeppelin/utils/cryptography/EIP712.sol";
Expand All @@ -15,7 +18,8 @@ import {DelegationSurrogate} from "src/DelegationSurrogate.sol";
contract GovernanceStakerHarness is
GovernanceStaker,
GovernanceStakerPermitAndStake,
GovernanceStakerOnBehalf
GovernanceStakerOnBehalf,
GovernanceStakerDelegateSurrogateVotes
{
constructor(
IERC20 _rewardsToken,
Expand Down

0 comments on commit a90fb14

Please sign in to comment.