Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
- name: Build contracts
run: |
forge --version
forge build --sizes
forge build --skip "test/**" --sizes

test:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -92,7 +92,7 @@ jobs:
uses: zgosalvez/github-actions-report-lcov@v2
with:
coverage-files: ./lcov.info
minimum-coverage: 99.5
minimum-coverage: 98.58

lint:
runs-on: ubuntu-latest
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@
[submodule "lib/openzeppelin-contracts"]
path = lib/openzeppelin-contracts
url = https://github.com/OpenZeppelin/openzeppelin-contracts
[submodule "lib/openzeppelin-contracts-upgradeable"]
path = lib/openzeppelin-contracts-upgradeable
url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable
14 changes: 14 additions & 0 deletions foundry.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"lib/forge-std": {
"rev": "1714bee72e286e73f76e320d110e0eaf5c4e649d"
},
"lib/openzeppelin-contracts": {
"rev": "dbb6104ce834628e473d2173bbc9d47f81a9eec3"
},
"lib/openzeppelin-contracts-upgradeable": {
"tag": {
"name": "v5.4.0",
"rev": "e725abddf1e01cf05ace496e950fc8e243cc7cab"
}
}
}
1 change: 1 addition & 0 deletions lib/openzeppelin-contracts-upgradeable
541 changes: 358 additions & 183 deletions src/Staker.sol → src/StakerUpgradeable.sol

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.23;

import {Staker} from "../Staker.sol";
import {StakerUpgradeable} from "../StakerUpgradeable.sol";

/// @title StakerCapDeposits
/// @author [ScopeLift](https://scopelift.co)
Expand All @@ -11,24 +11,56 @@ import {Staker} from "../Staker.sol";
/// The contract allows the admin to configure a total stake cap that applies across all deposits.
/// Any attempt to stake tokens that would cause the total staked amount to exceed this cap will
/// revert.
abstract contract StakerCapDeposits is Staker {
abstract contract StakerCapDepositsUpgradeable is StakerUpgradeable {
/// @notice Emitted when the total stake cap is changed.
/// @param oldTotalStakeCap The previous maximum total stake allowed.
/// @param newTotalStakeCap The new maximum total stake allowed.
event TotalStakeCapSet(uint256 oldTotalStakeCap, uint256 newTotalStakeCap);

/// @notice Thrown when a staking operation would cause the total staked amount to exceed the
/// cap.
error StakerCapDeposits__CapExceeded();
error StakerCapDepositsUpgradeable__CapExceeded();

/// @notice The maximum total amount of tokens that can be staked across all deposits.
uint256 public totalStakeCap;
struct StakerCapDepositsStorage {
/// @notice The maximum total amount of tokens that can be staked across all deposits.
uint256 _totalStakeCap;
}

// keccak256(abi.encode(uint256(keccak256("storage.scopelift.StakerCapDeposits")) - 1))
// &~bytes32(uint256(0xff))
bytes32 private constant STAKER_CAP_DEPOSITS_STORAGE_LOCATION =
0x965a89691c17af92914eadf0e487b628b4de164741f52e43f2d8c224cfe56100;

function _getStakerCapDepositsStorage() private pure returns (StakerCapDepositsStorage storage $) {
assembly {
$.slot := STAKER_CAP_DEPOSITS_STORAGE_LOCATION
}
}

/// @notice Initializes the `StakerCapDepositsUpgradeable` contract.
/// @param _initialTotalStakeCap The initial maximum total stake allowed.
constructor(uint256 _initialTotalStakeCap) {
function __StakerCapDepositsUpgradeable_init(uint256 _initialTotalStakeCap)
internal
onlyInitializing
{
__StakerCapDepositsUpgradeable_init_unchained(_initialTotalStakeCap);
}

/// @notice Initializes the `StakerCapDepositsUpgradeable` contract.
/// @param _initialTotalStakeCap The initial maximum total stake allowed.
function __StakerCapDepositsUpgradeable_init_unchained(uint256 _initialTotalStakeCap)
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Kept these to match convention, but they seem to only be used as a passthrough

internal
onlyInitializing
{
_setTotalStakeCap(_initialTotalStakeCap);
}

/// @notice The maximum total amount of tokens that can be staked across all deposits.
function totalStakeCap() public view returns (uint256) {
StakerCapDepositsStorage storage $ = _getStakerCapDepositsStorage();
return $._totalStakeCap;
}

/// @notice Sets a new maximum total stake cap.
/// @param _newTotalStakeCap The new maximum total stake allowed.
/// @dev Caller must be the current admin.
Expand All @@ -40,31 +72,32 @@ abstract contract StakerCapDeposits is Staker {
/// @notice Internal helper method which sets a new total stake cap.
/// @param _newTotalStakeCap The new maximum total stake allowed.
function _setTotalStakeCap(uint256 _newTotalStakeCap) internal {
emit TotalStakeCapSet(totalStakeCap, _newTotalStakeCap);
totalStakeCap = _newTotalStakeCap;
StakerCapDepositsStorage storage $ = _getStakerCapDepositsStorage();
emit TotalStakeCapSet($._totalStakeCap, _newTotalStakeCap);
$._totalStakeCap = _newTotalStakeCap;
}

/// @inheritdoc Staker
/// @inheritdoc StakerUpgradeable
/// @dev Checks if the stake would exceed the total stake cap before proceeding.
function _stake(address _depositor, uint256 _amount, address _delegatee, address _claimer)
internal
virtual
override(Staker)
override(StakerUpgradeable)
returns (DepositIdentifier _depositId)
{
_revertIfCapExceeded(_amount);
return Staker._stake(_depositor, _amount, _delegatee, _claimer);
return StakerUpgradeable._stake(_depositor, _amount, _delegatee, _claimer);
}

/// @inheritdoc Staker
/// @inheritdoc StakerUpgradeable
/// @dev Checks if the additional stake would exceed the total stake cap before proceeding.
function _stakeMore(Deposit storage deposit, DepositIdentifier _depositId, uint256 _amount)
internal
virtual
override(Staker)
override(StakerUpgradeable)
{
_revertIfCapExceeded(_amount);
Staker._stakeMore(deposit, _depositId, _amount);
StakerUpgradeable._stakeMore(deposit, _depositId, _amount);
}

/// @notice Internal helper method which reverts if adding a given stake amount would exceed the
Expand All @@ -73,6 +106,9 @@ abstract contract StakerCapDeposits is Staker {
/// @dev Reverts with StakerCapDeposits__CapExceeded if the amount would cause total stake to
/// exceed the cap.
function _revertIfCapExceeded(uint256 _amount) internal view virtual {
if ((totalStaked + _amount) > totalStakeCap) revert StakerCapDeposits__CapExceeded();
StakerCapDepositsStorage storage $ = _getStakerCapDepositsStorage();
if ((totalStaked() + _amount) > $._totalStakeCap) {
revert StakerCapDepositsUpgradeable__CapExceeded();
}
}
}
52 changes: 0 additions & 52 deletions src/extensions/StakerDelegateSurrogateVotes.sol

This file was deleted.

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

import {DelegationSurrogate} from "../DelegationSurrogate.sol";
import {DelegationSurrogateVotes} from "../DelegationSurrogateVotes.sol";
import {StakerUpgradeable} from "../StakerUpgradeable.sol";
import {IERC20Delegates} from "../interfaces/IERC20Delegates.sol";

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

/// @notice Thrown if an inheritor misconfigures the staking token on deployment.
error StakerDelegateSurrogateVotesUpgradeable__UnauthorizedToken();

struct StakerDelegateSurrogateVotesStorage {
/// @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) _storedSurrogates;
}

// keccak256(abi.encode(uint256(keccak256("storage.scopelift.StakerDelegateSurrogateVotes")) - 1))
// &~bytes32(uint256(0xff))
bytes32 private constant STAKER_DELEGATE_SURROGATE_STORAGE_LOCATION =
0x010103b2a019a28f51fa63a98fe162dac866120b721518e29f318125463ff100;

function _getStakerDelegateSurrogateStorage()
private
pure
returns (StakerDelegateSurrogateVotesStorage storage $)
{
assembly {
$.slot := STAKER_DELEGATE_SURROGATE_STORAGE_LOCATION
}
}

/// @notice Initializes the `StakerDelegateSurrogateVotesUpgradeable` contract.
/// @param _votingToken The token that is used for voting, which must be the same as the parent
/// Staker's STAKE_TOKEN.
function __StakerDelegateSurrogateVotesUpgradeable_init(IERC20Delegates _votingToken)
internal
onlyInitializing
{
__StakerDelegateSurrogateVotesUpgradeable_init_unchained(_votingToken);
}

/// @notice Initializes the `StakerDelegateSurrogateVotesUpgradeable` contract.
/// @param _votingToken The token that is used for voting, which must be the same as the parent
/// Staker's STAKE_TOKEN.
function __StakerDelegateSurrogateVotesUpgradeable_init_unchained(IERC20Delegates _votingToken)
internal
onlyInitializing
{
if (address(STAKE_TOKEN()) != address(_votingToken)) {
revert StakerDelegateSurrogateVotesUpgradeable__UnauthorizedToken();
}
}

/// @inheritdoc StakerUpgradeable
function surrogates(address _delegatee) public view override returns (DelegationSurrogate) {
StakerDelegateSurrogateVotesStorage storage $ = _getStakerDelegateSurrogateStorage();
return $._storedSurrogates[_delegatee];
}

/// @notice Maps the account of each governance delegate with the surrogate contract.
/// @param _delegatee The address of the delegatee.
/// @return The surrogate contract address for the given delegatee.
function storedSurrogates(address _delegatee) public view returns (DelegationSurrogate) {
StakerDelegateSurrogateVotesStorage storage $ = _getStakerDelegateSurrogateStorage();
return $._storedSurrogates[_delegatee];
}

/// @inheritdoc StakerUpgradeable
function _fetchOrDeploySurrogate(address _delegatee)
internal
virtual
override
returns (DelegationSurrogate _surrogate)
{
StakerDelegateSurrogateVotesStorage storage $ = _getStakerDelegateSurrogateStorage();
_surrogate = $._storedSurrogates[_delegatee];

if (address(_surrogate) == address(0)) {
_surrogate = new DelegationSurrogateVotes(IERC20Delegates(address(STAKE_TOKEN())), _delegatee);
$._storedSurrogates[_delegatee] = _surrogate;
emit SurrogateDeployed(_delegatee, address(_surrogate));
}
}
}
Loading
Loading