-
Notifications
You must be signed in to change notification settings - Fork 18
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
Introduces StableYield
contract for minting and distributing reward per app per period
#167
Draft
vzotova
wants to merge
7
commits into
main
Choose a base branch
from
stable-yield
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
e633afe
TokenStaking: removes all functions related to legacy stakes
vzotova 2faa2ea
Apply suggestions from code review
vzotova d5b0303
Updates github workflow and hardhat config, removes "legacy" info fro…
vzotova fe1353a
Removes unused deploymentsL sepolia and goerli
vzotova ede54f4
Adds tracking of overall authorized amount for each application
vzotova 265529a
Introduces StableYield contract for minting and distributing reward p…
vzotova 897633a
Apply suggestions from code review
vzotova 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
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
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,176 @@ | ||
// SPDX-License-Identifier: GPL-3.0-or-later | ||
|
||
// ██████████████ ▐████▌ ██████████████ | ||
// ██████████████ ▐████▌ ██████████████ | ||
// ▐████▌ ▐████▌ | ||
// ▐████▌ ▐████▌ | ||
// ██████████████ ▐████▌ ██████████████ | ||
// ██████████████ ▐████▌ ██████████████ | ||
// ▐████▌ ▐████▌ | ||
// ▐████▌ ▐████▌ | ||
// ▐████▌ ▐████▌ | ||
// ▐████▌ ▐████▌ | ||
// ▐████▌ ▐████▌ | ||
// ▐████▌ ▐████▌ | ||
|
||
pragma solidity ^0.8.9; | ||
|
||
import "../token/T.sol"; | ||
import "../staking/IStaking.sol"; | ||
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; | ||
import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; | ||
|
||
/// @title StableYield contract | ||
/// @notice Contract that mints and distributes stable yield reward for participating in Threshold Network. | ||
/// Periodically mints reward for each application based on authorization rate and destributes this rewards based on type of application. | ||
contract StableYield is OwnableUpgradeable { | ||
using AddressUpgradeable for address; | ||
|
||
struct ApplicationInfo { | ||
uint256 stableYield; | ||
uint256 duration; | ||
address distributor; | ||
string receiveRewardMethod; | ||
uint256 lastMint; | ||
} | ||
|
||
uint256 public constant STABLE_YIELD_BASE = 10000; | ||
|
||
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable | ||
T internal immutable token; | ||
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable | ||
IStaking internal immutable tokenStaking; | ||
|
||
mapping(address => ApplicationInfo) public applicationInfo; | ||
|
||
/// @dev Event emitted by `setApplicationParameters` function. | ||
event ParametersSet( | ||
address indexed application, | ||
uint256 stableYield, | ||
uint256 duration, | ||
address distributor, | ||
string receiveRewardMethod | ||
); | ||
|
||
/// @dev Event emitted by `mintAndPushReward` function. | ||
event MintedReward(address indexed application, uint96 reward); | ||
|
||
constructor(T _token, IStaking _tokenStaking) { | ||
// calls to check contracts are working | ||
uint256 totalSupply = _token.totalSupply(); | ||
require( | ||
totalSupply > 0 && _tokenStaking.getApplicationsLength() > 0, | ||
"Wrong input parameters" | ||
); | ||
require( | ||
(STABLE_YIELD_BASE * totalSupply * totalSupply) / | ||
totalSupply / | ||
STABLE_YIELD_BASE == | ||
totalSupply, | ||
"Potential overflow" | ||
); | ||
token = _token; | ||
tokenStaking = _tokenStaking; | ||
_transferOwnership(_msgSender()); | ||
} | ||
|
||
/// @notice Sets or updates application parameter for minting reward. | ||
/// Can be called only by the governance. | ||
function setApplicationParameters( | ||
address application, | ||
uint256 stableYield, | ||
uint256 duration, | ||
address distributor, | ||
string memory receiveRewardMethod | ||
) external onlyOwner { | ||
// if stable yield is zero then reward will be no longer minted | ||
require( | ||
(stableYield == 0 || | ||
(stableYield < STABLE_YIELD_BASE && duration > 0)) && | ||
distributor != address(0), | ||
"Wrong input parameters" | ||
); | ||
ApplicationInfo storage info = applicationInfo[application]; | ||
info.stableYield = stableYield; | ||
info.duration = duration; | ||
info.distributor = distributor; | ||
info.receiveRewardMethod = receiveRewardMethod; | ||
emit ParametersSet( | ||
application, | ||
stableYield, | ||
duration, | ||
distributor, | ||
receiveRewardMethod | ||
); | ||
} | ||
|
||
/// @notice Mints reward and then pushes it to particular application or distributor. | ||
/// @dev Application must be in `APPROVED` state | ||
function mintAndPushReward(address application) external { | ||
ApplicationInfo storage info = applicationInfo[application]; | ||
require( | ||
info.stableYield != 0, | ||
"Reward parameters are not set for the application" | ||
); | ||
require( | ||
/* solhint-disable-next-line not-rely-on-time */ | ||
block.timestamp >= info.lastMint + info.duration, | ||
"New portion of reward is not ready" | ||
); | ||
IStaking.ApplicationStatus status = tokenStaking.getApplicationStatus( | ||
application | ||
); | ||
require( | ||
status == IStaking.ApplicationStatus.APPROVED, | ||
"Application is not approved" | ||
); | ||
uint96 reward = calculateReward(application, info.stableYield); | ||
/* solhint-disable-next-line not-rely-on-time */ | ||
info.lastMint = block.timestamp; | ||
//slither-disable-next-line incorrect-equality | ||
if (bytes(info.receiveRewardMethod).length == 0) { | ||
sendToDistributor(info.distributor, reward); | ||
} else { | ||
executeReceiveReward(application, info.receiveRewardMethod, reward); | ||
} | ||
emit MintedReward(application, reward); | ||
} | ||
|
||
function sendToDistributor(address distributor, uint96 reward) internal { | ||
token.mint(distributor, reward); | ||
} | ||
|
||
function executeReceiveReward( | ||
address distributor, | ||
string storage receiveRewardMethod, | ||
uint96 reward | ||
) internal { | ||
token.mint(address(this), reward); | ||
//slither-disable-next-line unused-return | ||
token.approve(distributor, reward); | ||
bytes memory data = abi.encodeWithSignature( | ||
receiveRewardMethod, | ||
reward | ||
); | ||
//slither-disable-next-line unused-return | ||
distributor.functionCall(data); | ||
} | ||
|
||
function calculateReward(address application, uint256 stableYield) | ||
internal | ||
view | ||
returns (uint96 reward) | ||
{ | ||
uint96 authorizedOverall = tokenStaking.getAuthorizedOverall( | ||
application | ||
); | ||
uint256 totalSupply = token.totalSupply(); | ||
// stableYieldPercent * authorizationRate * authorizedOverall = | ||
// (stableYield / STABLE_YIELD_BASE) * (authorizedOverall / totalSupply) * authorizedOverall | ||
reward = uint96( | ||
(stableYield * authorizedOverall * authorizedOverall) / | ||
totalSupply / | ||
STABLE_YIELD_BASE | ||
); | ||
} | ||
} |
Oops, something went wrong.
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.
Given the criticality of minting tokens, what do you think if we add a manual approval step (e.g., from the council multisig)?
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.
I was hoping to have
mintAndPush
as permissionless method. Can you elaborate how this will work with council? in which moment? Like sort of queue?