-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
422 additions
and
10 deletions.
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,42 @@ | ||
// SPDX-License-Identifier: GPL-3.0-or-later | ||
pragma solidity ^0.8.24; | ||
|
||
import {console} from "forge-std/console.sol"; | ||
import {UUPSProxy} from "./UUPSProxy.sol"; | ||
|
||
import {sGydStaker} from "../../src/sGydStaker.sol"; | ||
import {Deployment} from "./Deployment.sol"; | ||
|
||
contract DeploySGyd is Deployment { | ||
function run() public { | ||
vm.startBroadcast(deployerPrivateKey); | ||
|
||
address gyd_; | ||
address distributor; | ||
address governance; | ||
address treasury; | ||
address sgyd = _getDeployed(SGYD); | ||
if (block.chainid == 1) { | ||
gyd_ = gyd; | ||
distributor = _getDeployed(GYD_DISTRIBUTOR); | ||
governance = l1Governance; | ||
revert("add treasury address"); | ||
} else { | ||
gyd_ = l2Gyd; | ||
distributor = _getDeployed(L2_GYD_DISTRIBUTOR); | ||
governance = l2Governance; | ||
treasury = 0x391714d83db20fde7110Cb80DC3857637c14E251; | ||
} | ||
console.log("gyd", gyd_); | ||
console.log("distributor", distributor); | ||
console.log("governance", governance); | ||
console.log("treasury", treasury); | ||
|
||
sGydStaker sgydStaker = new sGydStaker(); | ||
|
||
bytes memory data = abi.encodeWithSelector(sGydStaker.initialize.selector, sgyd, gyd_, treasury, governance); | ||
bytes memory creationCode = | ||
abi.encodePacked(type(UUPSProxy).creationCode, abi.encode(address(sgydStaker), data)); | ||
console.log("sGydStaker", _deploy(SGYD_STAKER, creationCode)); | ||
} | ||
} |
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
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,136 @@ | ||
// SPDX-License-Identifier: GPL-3.0-or-later | ||
pragma solidity ^0.8.24; | ||
|
||
import "./interfaces/ILiquidityMining.sol"; | ||
import "./libraries/ScaledMath.sol"; | ||
|
||
import "oz/proxy/utils/Initializable.sol"; | ||
import "oz/token/ERC20/IERC20.sol"; | ||
import "oz/token/ERC20/utils/SafeERC20.sol"; | ||
|
||
/// @dev Base contract for liquidity mining. | ||
/// `startMining` and `stopMining` would typically be implemented by the subcontract to perform | ||
/// its own authorization and then call the underscore versions | ||
/// NOTE: this is the same as the LiquidityMining contract in the governance repo | ||
abstract contract LiquidityMining is ILiquidityMining { | ||
using ScaledMath for uint256; | ||
using SafeERC20 for IERC20; | ||
|
||
uint256 public override totalStaked; | ||
|
||
uint256 internal _totalStakedIntegral; | ||
uint256 internal _lastCheckpointTime; | ||
/// @dev This contract only tracks these, we don't use it; but it may be convenient for inheriting contracts. | ||
uint256 internal _totalUnclaimedRewards; | ||
mapping(address => uint256) internal _perUserStakedIntegral; | ||
mapping(address => uint256) internal _perUserShare; | ||
mapping(address => uint256) internal _perUserStaked; | ||
|
||
uint256 internal _rewardsEmissionRate; | ||
uint256 public override rewardsEmissionEndTime; | ||
|
||
IERC20 public rewardToken; | ||
address public daoTreasury; | ||
|
||
function __LiquidityMining_init(address _rewardToken, address _daoTreasury) internal { | ||
_lastCheckpointTime = block.timestamp; | ||
rewardToken = IERC20(_rewardToken); | ||
daoTreasury = _daoTreasury; | ||
} | ||
|
||
function claimRewards() external returns (uint256) { | ||
userCheckpoint(msg.sender); | ||
uint256 amount = _perUserShare[msg.sender]; | ||
if (amount == 0) return 0; | ||
delete _perUserShare[msg.sender]; | ||
emit Claim(msg.sender, amount); | ||
_totalUnclaimedRewards -= amount; | ||
return _mintRewards(msg.sender, amount); | ||
} | ||
|
||
function claimableRewards(address beneficiary) external view virtual returns (uint256) { | ||
uint256 totalStakedIntegral = _totalStakedIntegral; | ||
uint256 rewardsTimestamp = _rewardsTimestamp(); | ||
if (totalStaked > 0 && rewardsTimestamp > _lastCheckpointTime) { | ||
uint256 elapsedTime = rewardsTimestamp - _lastCheckpointTime; | ||
totalStakedIntegral += (_rewardsEmissionRate * elapsedTime).div(totalStaked); | ||
} | ||
|
||
return _perUserShare[beneficiary] | ||
+ stakedBalanceOf(beneficiary).mul(totalStakedIntegral - _perUserStakedIntegral[beneficiary]); | ||
} | ||
|
||
function stakedBalanceOf(address account) public view returns (uint256) { | ||
return _perUserStaked[account]; | ||
} | ||
|
||
function globalCheckpoint() public { | ||
uint256 rewardsTimestamp = _rewardsTimestamp(); | ||
uint256 totalStaked_ = totalStaked; | ||
if (totalStaked_ > 0 && rewardsTimestamp > _lastCheckpointTime) { | ||
uint256 elapsedTime = rewardsTimestamp - _lastCheckpointTime; | ||
uint256 newRewards = _rewardsEmissionRate * elapsedTime; | ||
_totalStakedIntegral += newRewards.div(totalStaked_); | ||
_totalUnclaimedRewards += newRewards; | ||
} | ||
_lastCheckpointTime = rewardsTimestamp; | ||
} | ||
|
||
function userCheckpoint(address account) public virtual { | ||
globalCheckpoint(); | ||
uint256 totalStakedIntegral = _totalStakedIntegral; | ||
_perUserShare[account] += stakedBalanceOf(account).mul(totalStakedIntegral - _perUserStakedIntegral[account]); | ||
_perUserStakedIntegral[account] = totalStakedIntegral; | ||
} | ||
|
||
/// @dev this is a helper function to be used by the inheriting contract | ||
/// this does not perform any checks on the amount that `account` may or not have deposited | ||
/// and should be used with caution. All checks should be performed in the inheriting contract | ||
function _stake(address account, uint256 amount) internal { | ||
userCheckpoint(account); | ||
totalStaked += amount; | ||
_perUserStaked[account] += amount; | ||
emit Stake(account, amount); | ||
} | ||
|
||
/// @dev same as `_stake` but for unstaking | ||
function _unstake(address account, uint256 amount) internal { | ||
userCheckpoint(account); | ||
_perUserStaked[account] -= amount; | ||
totalStaked -= amount; | ||
emit Unstake(account, amount); | ||
} | ||
|
||
/// @dev Helper function for the inheriting contract. Authorization should be performed by the inheriting contract. | ||
function _startMining(address rewardsFrom, uint256 amount, uint256 endTime) internal { | ||
globalCheckpoint(); | ||
rewardToken.safeTransferFrom(rewardsFrom, address(this), amount); | ||
_rewardsEmissionRate = amount / (endTime - block.timestamp); | ||
rewardsEmissionEndTime = endTime; | ||
_lastCheckpointTime = block.timestamp; | ||
emit StartMining(amount, endTime); | ||
} | ||
|
||
/// @dev same as `_startLiquidityMining` but for stopping. | ||
function _stopMining() internal { | ||
globalCheckpoint(); | ||
uint256 reimbursementAmount = rewardToken.balanceOf(address(this)) - _totalUnclaimedRewards; | ||
rewardToken.safeTransfer(daoTreasury, reimbursementAmount); | ||
rewardsEmissionEndTime = 0; | ||
_rewardsEmissionRate = 0; | ||
emit StopMining(); | ||
} | ||
|
||
function _mintRewards(address beneficiary, uint256 amount) internal virtual returns (uint256) { | ||
rewardToken.safeTransfer(beneficiary, amount); | ||
return amount; | ||
} | ||
|
||
function rewardsEmissionRate() external view override returns (uint256) { | ||
return block.timestamp <= rewardsEmissionEndTime ? _rewardsEmissionRate : 0; | ||
} | ||
|
||
function _rewardsTimestamp() internal view returns (uint256) { | ||
return block.timestamp <= rewardsEmissionEndTime ? block.timestamp : rewardsEmissionEndTime; | ||
} | ||
} |
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,45 @@ | ||
// SPDX-License-Identifier: GPL-3.0-or-later | ||
pragma solidity ^0.8.24; | ||
|
||
// NOTE: This is the same code as ILiquidityMining.sol in the governance repo. | ||
|
||
interface ILiquidityMining { | ||
event Stake(address indexed account, uint256 amount); | ||
event Unstake(address indexed account, uint256 amount); | ||
event Claim(address indexed beneficiary, uint256 amount); | ||
|
||
event StartMining(uint256 amount, uint256 endTime); | ||
event StopMining(); | ||
|
||
/// @notice claims rewards for caller | ||
function claimRewards() external returns (uint256); | ||
|
||
/// @notice returns the amount of claimable rewards by `beneficiary` | ||
function claimableRewards(address beneficiary) external view returns (uint256); | ||
|
||
/// @notice the total amount of tokens staked in the contract | ||
function totalStaked() external view returns (uint256); | ||
|
||
/// @notice the amount of tokens staked by `account` | ||
function stakedBalanceOf(address account) external view returns (uint256); | ||
|
||
/// @notice returns the number of rewards token that will be given per second for the contract | ||
/// This emission will be given to all stakers in the contract proportionally to their stake | ||
function rewardsEmissionRate() external view returns (uint256); | ||
|
||
/// @notice time when rewards emission ends | ||
function rewardsEmissionEndTime() external view returns (uint256); | ||
|
||
/// @dev these functions will be called internally but can typically be called by anyone | ||
/// to update the internal tracking state of the contract | ||
function globalCheckpoint() external; | ||
|
||
function userCheckpoint(address account) external; | ||
|
||
/// @notice Deposit `amount` of the reward token from `rewardsFrom` and enable rewards until `endTime`. Typically governanceOnly. `amount` is spread evenly over the time period. | ||
function startMining(address rewardsFrom, uint256 amount, uint256 endTime) external; | ||
|
||
/// @notice Stop liquidity mining early and reimburse leftover rewards to the DAO treasury. | ||
/// This may also be needed after the mining period has ended when we had `totalStaked() == 0` for a while, where no rewards accrue. | ||
function stopMining() 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
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,58 @@ | ||
// SPDX-License-Identifier: GPL-3.0-or-later | ||
pragma solidity ^0.8.24; | ||
|
||
import {AccessControlDefaultAdminRulesUpgradeable} from | ||
"ozu/access/extensions/AccessControlDefaultAdminRulesUpgradeable.sol"; | ||
import {LiquidityMining} from "./LiquidityMining.sol"; | ||
import {IERC20} from "oz/token/ERC20/IERC20.sol"; | ||
import {UUPSUpgradeable} from "ozu/proxy/utils/UUPSUpgradeable.sol"; | ||
import {ERC4626Upgradeable} from "ozu/token/ERC20/extensions/ERC4626Upgradeable.sol"; | ||
|
||
contract sGydStaker is | ||
ERC4626Upgradeable, | ||
AccessControlDefaultAdminRulesUpgradeable, | ||
LiquidityMining, | ||
UUPSUpgradeable | ||
{ | ||
constructor() { | ||
_disableInitializers(); | ||
} | ||
|
||
function initialize(IERC20 _depositToken, IERC20 _rewardToken, address _daoTreasury, address _initialAdmin) | ||
external | ||
initializer | ||
{ | ||
__UUPSUpgradeable_init(); | ||
__ERC20_init("Staked sGYD", "ssGYD"); | ||
__ERC4626_init(_depositToken); | ||
__AccessControlDefaultAdminRules_init(0, _initialAdmin); | ||
__LiquidityMining_init(address(_rewardToken), _daoTreasury); | ||
} | ||
|
||
function _authorizeUpgrade(address) internal override onlyRole(DEFAULT_ADMIN_ROLE) {} | ||
|
||
function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal override { | ||
super._deposit(caller, receiver, assets, shares); | ||
_stake(receiver, shares); | ||
} | ||
|
||
function _withdraw(address caller, address receiver, address owner, uint256 assets, uint256 shares) | ||
internal | ||
override | ||
{ | ||
super._withdraw(caller, receiver, owner, assets, shares); | ||
_unstake(receiver, shares); | ||
} | ||
|
||
function startMining(address rewardsFrom, uint256 amount, uint256 endTime) | ||
external | ||
override | ||
onlyRole(DEFAULT_ADMIN_ROLE) | ||
{ | ||
_startMining(rewardsFrom, amount, endTime); | ||
} | ||
|
||
function stopMining() external override onlyRole(DEFAULT_ADMIN_ROLE) { | ||
_stopMining(); | ||
} | ||
} |
Oops, something went wrong.