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

Delegate fuzz #37

Merged
merged 17 commits into from
Jul 28, 2024
Merged
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 @@ -9,7 +9,7 @@ on:

env:
FOUNDRY_PROFILE: ci
MAINNET_RPC_URL: ${{secrets.MAINNET_RPC_URL}}
MAINNET_RPC_URL: ${{ vars.MAINNET_RPC_URL }}

jobs:
build:
Expand Down Expand Up @@ -92,7 +92,7 @@ jobs:
uses: zgosalvez/github-actions-report-lcov@v2
with:
coverage-files: ./lcov.info
minimum-coverage: 100
minimum-coverage: 97

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 @@ -10,3 +10,6 @@
[submodule "lib/solmate"]
path = lib/solmate
url = https://github.com/transmissions11/solmate
[submodule "lib/solady"]
path = lib/solady
url = https://github.com/Vectorized/solady
306 changes: 306 additions & 0 deletions broadcast/DeployTestnet.s.sol/11155111/run-1721609738.json

Large diffs are not rendered by default.

236 changes: 118 additions & 118 deletions broadcast/DeployTestnet.s.sol/11155111/run-latest.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions lib/solady
Submodule solady added at 1f43cc
1 change: 1 addition & 0 deletions remappings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
@openzeppelin=lib/openzeppelin-contracts/
@openzeppelin-upgradeable=lib/openzeppelin-contracts-upgradeable/
@solmate=lib/solmate/src/
@solady=lib/solady/src
282 changes: 282 additions & 0 deletions src/BaseStaking.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,282 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;

import {OwnableUpgradeable} from "@openzeppelin-upgradeable/contracts/access/OwnableUpgradeable.sol";
import {ERC20VotesUpgradeable} from "@openzeppelin-upgradeable/contracts/token/ERC20/extensions/ERC20VotesUpgradeable.sol";
import {EnumerableSetLib} from "@solady/utils/EnumerableSetLib.sol";

import {IERC20} from "./interfaces/IERC20.sol";
import {SafeTransferLib} from "./libraries/SafeTransferLib.sol";
import {FixedPointMathLib} from "./libraries/FixedPointMathLib.sol";
import {IRewardsDistributor} from "./interfaces/IRewardsDistributor.sol";

interface IStaking {
function keypers(address user) external returns (bool);
}

abstract contract BaseStaking is OwnableUpgradeable, ERC20VotesUpgradeable {
/*//////////////////////////////////////////////////////////////
LIBRARIES
//////////////////////////////////////////////////////////////*/
using EnumerableSetLib for EnumerableSetLib.Uint256Set;

using SafeTransferLib for IERC20;

using FixedPointMathLib for uint256;

/*//////////////////////////////////////////////////////////////
VARIABLES
//////////////////////////////////////////////////////////////*/

/// @notice the staking token, i.e. SHU
/// @dev set in initialize, can't be changed
IERC20 public stakingToken;

/// @notice the rewards distributor contract
/// @dev only owner can change
IRewardsDistributor public rewardsDistributor;

/// @notice the lock period in seconds
/// @dev only owner can change
uint256 public lockPeriod;

/// @notice Unique identifier that will be used for the next stake.
uint256 internal nextStakeId;

/*//////////////////////////////////////////////////////////////
MAPPINGS
//////////////////////////////////////////////////////////////*/

/// @notice how many SHU a user has locked
mapping(address user => uint256 totalLocked) public totalLocked;

// @notice stake ids belonging to a user
mapping(address user => EnumerableSetLib.Uint256Set stakeIds)
internal userStakes;

/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/

/// @notice Emitted when a keyper claims rewards
event RewardsClaimed(address indexed user, uint256 rewards);

/// @notice Emitted when the rewards distributor is changed
event NewRewardsDistributor(address indexed rewardsDistributor);

/// @notice Emitted when the lock period is changed
event NewLockPeriod(uint256 indexed lockPeriod);

/*//////////////////////////////////////////////////////////////
ERRORS
//////////////////////////////////////////////////////////////*/

/// @notice Thrown when someone try to unstake a amount that is greater than
/// the stake amount belonging to the stake id
error WithdrawAmountTooHigh();

/// @notice Thrown when transfer/tranferFrom is called
error TransferDisabled();

/// @notice Thrown when a user has no shares
error UserHasNoShares();

/// @notice Thrown when a user try to claim rewards but has no rewards to
/// claim
error NoRewardsToClaim();

/// @notice Thrown when the argument is the zero address
error AddressZero();

/*//////////////////////////////////////////////////////////////
MODIFIERS
//////////////////////////////////////////////////////////////*/

/// @notice Update rewards for a keyper
modifier updateRewards() {
// Distribute rewards
rewardsDistributor.collectRewards();

_;
}

/// @notice Ensure logic contract is unusable
constructor() {
_disableInitializers();
}

/// @notice Claim rewards
/// - If no amount is specified, will claim all the rewards
/// - If the amount is specified, the amount must be less than the
/// maximum withdrawable amount. The maximum withdrawable amount
/// is the total amount of assets the user has minus the
/// total locked amount
/// - If the claim results in a balance less than the total locked
/// amount, the claim will be rejected
/// - The keyper can claim the rewards at any time as longs there is
/// a reward to claim
/// @param amount The amount of rewards to claim
function claimRewards(
uint256 amount
) external updateRewards returns (uint256 rewards) {
address user = msg.sender;

// Prevents the keyper from claiming more than they should
uint256 maxWithdrawAmount = maxWithdraw(user);

rewards = _calculateWithdrawAmount(amount, maxWithdrawAmount);

require(rewards > 0, NoRewardsToClaim());

// Calculates the amount of shares to burn
uint256 shares = previewWithdraw(rewards);

_burn(user, shares);

stakingToken.safeTransfer(user, rewards);

emit RewardsClaimed(user, rewards);
}

/*//////////////////////////////////////////////////////////////
RESTRICTED FUNCTIONS
//////////////////////////////////////////////////////////////*/

/// @notice Set the rewards distributor contract
/// @param _rewardsDistributor The address of the rewards distributor contract
function setRewardsDistributor(
address _rewardsDistributor
) external onlyOwner {
require(_rewardsDistributor != address(0), AddressZero());
rewardsDistributor = IRewardsDistributor(_rewardsDistributor);

emit NewRewardsDistributor(_rewardsDistributor);
}

/// @notice Set the lock period
/// @param _lockPeriod The lock period in seconds
function setLockPeriod(uint256 _lockPeriod) external onlyOwner {
lockPeriod = _lockPeriod;

emit NewLockPeriod(_lockPeriod);
}

/*//////////////////////////////////////////////////////////////
TRANSFER LOGIC
//////////////////////////////////////////////////////////////*/

/// @notice Transfer is disabled
function transfer(address, uint256) public pure override returns (bool) {
revert TransferDisabled();
}

/// @notice Transfer is disabled
function transferFrom(
address,
address,
uint256
) public pure override returns (bool) {
revert TransferDisabled();
}

/*//////////////////////////////////////////////////////////////
VIEW FUNCTIONS
//////////////////////////////////////////////////////////////*/

function previewWithdraw(
uint256 assets
) public view virtual returns (uint256) {
// sum + 1 on both sides to prevent donation attack
return assets.mulDivUp(totalSupply() + 1, _totalAssets() + 1);
}

/// @notice Get the total amount of shares the assets are worth
/// @param assets The amount of assets
function convertToShares(
uint256 assets
) public view virtual returns (uint256) {
// sum + 1 on both sides to prevent donation attack
return assets.mulDivDown(totalSupply() + 1, _totalAssets() + 1);
}

/// @notice Get the total amount of assets the shares are worth
/// @param shares The amount of shares
function convertToAssets(
uint256 shares
) public view virtual returns (uint256) {
// sum + 1 on both sides to prevent donation attack
return shares.mulDivDown(_totalAssets() + 1, totalSupply() + 1);
}

/// @notice Get the stake ids belonging to a user
function getUserStakeIds(
address user
) external view returns (uint256[] memory) {
return userStakes[user].values();
}

/// @notice Get the total amount of assets that a keyper can withdraw
/// @dev must be implemented by the child contract
function maxWithdraw(address user) public view virtual returns (uint256);

/*//////////////////////////////////////////////////////////////
INTERNAL FUNCTIONS
//////////////////////////////////////////////////////////////*/

/// @notice Deposit SHU into the contract
/// @param user The user address
/// @param amount The amount of SHU to deposit
function _deposit(address user, uint256 amount) internal {
// Calculate the amount of shares to mint
uint256 shares = convertToShares(amount);

// Update the total locked amount
totalLocked[user] += amount;

// Mint the shares
_mint(user, shares);

// Lock the SHU in the contract
stakingToken.safeTransferFrom(user, address(this), amount);
}

/// @notice Withdraw SHU from the contract
/// @param user The user address
/// @param amount The amount of SHU to withdraw
function _withdraw(
address user,
uint256 amount
) internal returns (uint256 shares) {
shares = previewWithdraw(amount);

// Decrease the amount from the total locked
totalLocked[user] -= amount;

// Burn the shares
_burn(user, shares);

// Transfer the SHU to the keyper
stakingToken.safeTransfer(user, amount);
}

/// @notice Get the amount of SHU staked for all keypers
function _totalAssets() internal view virtual returns (uint256) {
return stakingToken.balanceOf(address(this));
}

/// @notice Calculates the amount to withdraw
/// @param _amount The amount to withdraw
/// @param maxWithdrawAmount The maximum amount that can be withdrawn
function _calculateWithdrawAmount(
uint256 _amount,
uint256 maxWithdrawAmount
) internal pure returns (uint256 amount) {
// If the amount is 0, withdraw all available amount
if (_amount == 0) {
amount = maxWithdrawAmount;
} else {
require(_amount <= maxWithdrawAmount, WithdrawAmountTooHigh());
amount = _amount;
}
}
}
Loading
Loading