-
Notifications
You must be signed in to change notification settings - Fork 0
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
Add SnapshotStakingPool
and SignedSnapshotStakingPool
#2
Merged
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
f150cfb
add initial snapshot staking pool
pblivin0x 8e4002a
First version of tests
ckoopmann 455bf82
Fix failing tests
ckoopmann 2bb10d2
refactor SnapshotStakingPool, add SignedSnapshotStakingPool
pblivin0x abbcc93
test cleanup
pblivin0x e81be72
add tests
pblivin0x e68a09d
add event docs
pblivin0x 0697f39
add custom errors, cleanup checks
pblivin0x 61b8409
boost coverage
pblivin0x 9d80f09
add getLifetimeRewards()
pblivin0x ae1330e
cleanup SignedSnapshotStakingPool, add tests
pblivin0x 3e25e80
add hyEth integration tests
pblivin0x File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.10; | ||
|
||
interface IBaseManagerV2 { | ||
function addExtension(address _newExtension) external; | ||
function isExtension(address _extension) external view returns (bool); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.10; | ||
|
||
interface IPrtFeeSplitExtension { | ||
function accrueFeesAndDistribute() external; | ||
function isAnyoneAllowedToAccrue() external view returns (bool); | ||
function prtStakingPool() external view returns (address); | ||
function updateAnyoneAccrue(bool _anyoneAccrue) external; | ||
function updateFeeRecipient(address _feeRecipient) external; | ||
function updatePrtStakingPool(address _prtStakingPool) external; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.10; | ||
|
||
interface IStreamingFeeModule { | ||
struct FeeState { | ||
address feeRecipient; | ||
uint256 maxStreamingFeePercentage; | ||
uint256 streamingFeePercentage; | ||
uint256 lastStreamingFeeTimestamp; | ||
} | ||
|
||
function feeStates(address _setToken) external view returns (FeeState memory); | ||
function getFee(address _setToken) external view returns (uint256); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
pragma solidity ^0.8.0; | ||
|
||
import {ISnapshotStakingPool} from "./ISnapshotStakingPool.sol"; | ||
|
||
interface ISignedSnapshotStakingPool is ISnapshotStakingPool { | ||
|
||
/// @notice Message to sign when staking | ||
function message() external view returns (string memory); | ||
|
||
/// @notice Mapping of approved stakers | ||
function isApprovedStaker(address) external view returns (bool); | ||
|
||
/// @notice Stake `amount` of stakeToken from `msg.sender` and mint staked tokens. | ||
/// @param amount The amount of stakeToken to stake | ||
/// @dev Must be an approved staker | ||
function stake(uint256 amount) external; | ||
|
||
/// @notice Stake `amount` of stakeToken from `msg.sender` and mint staked tokens. | ||
/// @param amount The amount of stakeToken to stake | ||
/// @param signature The signature of the message | ||
/// @dev Approves the staker if not already approved | ||
function stake(uint256 amount, bytes calldata signature) external; | ||
|
||
/// @notice Approve the signer of the message as an approved staker | ||
/// @param signature The signature of the message | ||
function approveStaker(bytes calldata signature) external; | ||
|
||
/// @notice Get the hashed digest of the message to be signed for staking | ||
/// @return The hashed bytes to be signed | ||
function getStakeSignatureDigest() external view returns (bytes32); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
pragma solidity ^0.8.0; | ||
|
||
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | ||
|
||
interface ISnapshotStakingPool is IERC20 { | ||
|
||
/// @notice Token to be distributed as rewards | ||
function rewardToken() external view returns (IERC20); | ||
|
||
/// @notice Token to be staked | ||
function stakeToken() external view returns (IERC20); | ||
|
||
/// @notice Distributor of rewards | ||
function distributor() external view returns (address); | ||
|
||
/// @notice Snapshot delay | ||
function snapshotDelay() external view returns (uint256); | ||
|
||
/// @notice Last snapshot time | ||
function lastSnapshotTime() external view returns (uint256); | ||
|
||
/// @notice Next snapshot id for `account` to claim | ||
function nextClaimId(address account) external view returns (uint256); | ||
|
||
/// @notice Reward snapshot at `snapshotId` | ||
function rewardSnapshots(uint256) external view returns (uint256); | ||
|
||
/// @notice Get the reward snapshots | ||
function getRewardSnapshots() external view returns (uint256[] memory); | ||
|
||
/// @notice Stake `amount` of stakeToken from `msg.sender` and mint staked tokens. | ||
/// @param amount The amount of stakeToken to stake | ||
function stake(uint256 amount) external; | ||
|
||
/// @notice Unstake `amount` of stakeToken by `msg.sender`. | ||
/// @param amount The amount of stakeToken to unstake | ||
function unstake(uint256 amount) external; | ||
|
||
/// @notice ONLY DISTRIBUTOR: Accrue rewardToken and update snapshot. | ||
/// @param amount The amount of rewardToken to accrue | ||
function accrue(uint256 amount) external; | ||
|
||
/// @notice Claim the staking rewards from pending snapshots for `msg.sender`. | ||
function claim() external; | ||
|
||
/// @notice Claim partial staking rewards from pending snapshots for `msg.sender` from `_startClaimId` to `_endClaimId`. | ||
/// @param startSnapshotId The snapshot id to start the partial claim | ||
/// @param endSnapshotId The snapshot id to end the partial claim | ||
function claimPartial(uint256 startSnapshotId, uint256 endSnapshotId) external; | ||
|
||
/* ========== Admin Functions ========== */ | ||
|
||
/// @notice ONLY OWNER: Update the distributor address. | ||
/// @param newDistributor The new distributor address | ||
function setDistributor(address newDistributor) external; | ||
|
||
/// @notice ONLY OWNER: Update the snapshot delay. Can set to 0 to disable snapshot delay. | ||
/// @param newSnapshotDelay The new snapshot delay | ||
function setSnapshotDelay(uint256 newSnapshotDelay) external; | ||
|
||
/* ========== View Functions ========== */ | ||
|
||
/// @notice Get the current snapshot id. | ||
/// @return The current snapshot id | ||
function getCurrentSnapshotId() external view returns (uint256); | ||
|
||
/// @notice Retrieves the rewards pending to be claimed by `account`. | ||
/// @param account The account to retrieve pending rewards for | ||
/// @return The rewards pending to be claimed by `account` | ||
function getPendingRewards(address account) external view returns (uint256); | ||
|
||
/// @notice Retrives the rewards of `account` in the range of `startSnapshotId` to `endSnapshotId`. | ||
/// @param account The account to retrieve rewards for | ||
/// @param startSnapshotId The start snapshot id | ||
/// @param endSnapshotId The end snapshot id | ||
/// @return The rewards of `account` in the range of `startSnapshotId` to `endSnapshotId` | ||
function rewardOfInRange(address account, uint256 startSnapshotId, uint256 endSnapshotId) external view returns (uint256); | ||
|
||
/// @notice Retrieves the rewards of `account` at the `snapshotId`. | ||
/// @param account The account to retrieve rewards for | ||
/// @param snapshotId The snapshot id | ||
/// @return The rewards of `account` at the `snapshotId` | ||
function rewardOfAt(address account, uint256 snapshotId) external view returns (uint256); | ||
|
||
/// @notice Retrieves the total pool reward at the time `snapshotId`. | ||
/// @param snapshotId The snapshot id | ||
/// @return The total pool reward at the time `snapshotId` | ||
function rewardAt(uint256 snapshotId) external view returns (uint256); | ||
|
||
/// @notice Retrieves the rewards across all snapshots for `account`. | ||
/// @param account The account to retrieve rewards for | ||
/// @return The rewards across all snapshots for `account` | ||
function getLifetimeRewards(address account) external view returns (uint256); | ||
|
||
/// @notice Check if rewards can be accrued. | ||
/// @return Boolean indicating if rewards can be accrued | ||
function canAccrue() external view returns (bool); | ||
|
||
/// @notice Get the time until the next snapshot. | ||
/// @return The time until the next snapshot | ||
function getTimeUntilNextSnapshot() external view returns (uint256); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
pragma solidity ^0.8.0; | ||
|
||
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | ||
import {ISignedSnapshotStakingPool} from "../interfaces/staking/ISignedSnapshotStakingPool.sol"; | ||
|
||
import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; | ||
import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; | ||
import {SnapshotStakingPool} from "./SnapshotStakingPool.sol"; | ||
|
||
/// @title SignedSnapshotStakingPool | ||
/// @author Index Cooperative | ||
/// @notice A contract for staking `stakeToken` and receiving `rewardToken` based | ||
/// on snapshots taken when rewards are accrued. | ||
contract SignedSnapshotStakingPool is ISignedSnapshotStakingPool, SnapshotStakingPool, EIP712 { | ||
string private constant MESSAGE_TYPE = "StakeMessage(string message)"; | ||
|
||
/* ERRORS */ | ||
|
||
/// @notice Error when staker is not approved | ||
error NotApprovedStaker(); | ||
/// @notice Error when signature is invalid | ||
error InvalidSignature(); | ||
|
||
/* EVENTS */ | ||
|
||
/// @notice Emitted when a staker has message signature approved | ||
event StakerApproved(address indexed staker); | ||
|
||
/* STORAGE */ | ||
|
||
/// @inheritdoc ISignedSnapshotStakingPool | ||
string public message; | ||
/// @inheritdoc ISignedSnapshotStakingPool | ||
mapping(address => bool) public isApprovedStaker; | ||
|
||
/* CONSTRUCTOR */ | ||
|
||
/// @param eip712Name Name of the EIP712 signing domain | ||
/// @param eip712Version Current major version of the EIP712 signing domain | ||
/// @param stakeMessage The message to sign when staking | ||
/// @param name Name of the staked token | ||
/// @param symbol Symbol of the staked token | ||
/// @param rewardToken Instance of the reward token | ||
/// @param stakeToken Instance of the stake token | ||
/// @param distributor Address of the distributor | ||
/// @param snapshotDelay The minimum amount of time between snapshots | ||
constructor( | ||
string memory eip712Name, | ||
string memory eip712Version, | ||
string memory stakeMessage, | ||
string memory name, | ||
string memory symbol, | ||
IERC20 rewardToken, | ||
IERC20 stakeToken, | ||
address distributor, | ||
uint256 snapshotDelay | ||
) | ||
EIP712(eip712Name, eip712Version) | ||
SnapshotStakingPool(name, symbol, rewardToken, stakeToken, distributor, snapshotDelay) | ||
{ | ||
message = stakeMessage; | ||
} | ||
|
||
/* STAKER FUNCTIONS */ | ||
|
||
/// @inheritdoc ISignedSnapshotStakingPool | ||
function stake(uint256 _amount) external override(SnapshotStakingPool, ISignedSnapshotStakingPool) nonReentrant { | ||
if (!isApprovedStaker[msg.sender]) revert NotApprovedStaker(); | ||
_stake(msg.sender, _amount); | ||
} | ||
|
||
/// @inheritdoc ISignedSnapshotStakingPool | ||
function stake(uint256 _amount, bytes calldata _signature) external nonReentrant { | ||
_approveStaker(msg.sender, _signature); | ||
_stake(msg.sender, _amount); | ||
} | ||
|
||
/// @inheritdoc ISignedSnapshotStakingPool | ||
function approveStaker(bytes calldata _signature) external { | ||
_approveStaker(msg.sender, _signature); | ||
} | ||
|
||
/* VIEW FUNCTIONS */ | ||
|
||
/// @inheritdoc ISignedSnapshotStakingPool | ||
function getStakeSignatureDigest() public view returns (bytes32) { | ||
return _hashTypedDataV4( | ||
keccak256( | ||
abi.encode( | ||
keccak256(abi.encodePacked(MESSAGE_TYPE)), | ||
keccak256(bytes(message)) | ||
) | ||
) | ||
); | ||
} | ||
|
||
/* INTERNAL FUNCTIONS */ | ||
|
||
/// @dev Approve the `staker` if the `signature` is valid | ||
/// @param staker The staker to approve | ||
/// @param signature The signature to verify | ||
function _approveStaker(address staker, bytes calldata signature) internal { | ||
if (!SignatureChecker.isValidSignatureNow(staker, getStakeSignatureDigest(), signature)) revert InvalidSignature(); | ||
isApprovedStaker[staker] = true; | ||
emit StakerApproved(staker); | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great find of this SignatureChecker library with both ECDSA and SC signature support. Just what we needed 👍