Skip to content

Commit

Permalink
Introduces StableYield contract for minting and distributing reward p…
Browse files Browse the repository at this point in the history
…er app per period
  • Loading branch information
vzotova committed Apr 3, 2024
1 parent ede54f4 commit 9a42c75
Show file tree
Hide file tree
Showing 5 changed files with 708 additions and 7 deletions.
171 changes: 171 additions & 0 deletions contracts/reward/StableYield.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
// 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 Stable yield 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;

T immutable token;
IStaking 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(
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 = caclulateReward(application, info.stableYield);
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 caclulateReward(address application, uint256 stableYield)
internal
view
returns (uint96 reward)
{
uint96 authrorizedOverall = tokenStaking.getAuthorizedOverall(
application
);
uint256 totalSupply = token.totalSupply();
// stableYieldPercent * authorizationRate * authorizedOverall =
// (stableYield / STABLE_YIELD_BASE) * (authrorizedOverall / totalSupply) * authorizedOverall
reward = uint96(
(stableYield * authrorizedOverall * authrorizedOverall) /
totalSupply /
STABLE_YIELD_BASE
);
}

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);
}
}
19 changes: 19 additions & 0 deletions contracts/staking/IStaking.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ pragma solidity ^0.8.9;
/// delegation optimizes the network throughput without compromising the
/// security of the owners’ stake.
interface IStaking {
enum ApplicationStatus {
NOT_APPROVED,
APPROVED,
PAUSED,
DISABLED
}

//
//
// Delegating a stake
Expand Down Expand Up @@ -267,6 +274,18 @@ interface IStaking {
/// @notice Returns length of application array
function getApplicationsLength() external view returns (uint256);

/// @notice Returns status of the application
function getApplicationStatus(address application)
external
view
returns (ApplicationStatus);

/// @notice Returns overall auhtorizaed value for the application
function getAuthorizedOverall(address application)
external
view
returns (uint96);

/// @notice Returns length of slashing queue
function getSlashingQueueLength() external view returns (uint256);

Expand Down
25 changes: 18 additions & 7 deletions contracts/staking/TokenStaking.sol
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,6 @@ contract TokenStaking is Initializable, IStaking, Checkpoints {
T
}

enum ApplicationStatus {
NOT_APPROVED,
APPROVED,
PAUSED,
DISABLED
}

struct StakingProviderInfo {
uint96 nuInTStake;
address owner;
Expand Down Expand Up @@ -920,6 +913,24 @@ contract TokenStaking is Initializable, IStaking, Checkpoints {
return applications.length;
}

/// @notice Returns status of the application
function getApplicationStatus(address application)
external
view
returns (ApplicationStatus)
{
return applicationInfo[application].status;
}

/// @notice Returns overall auhtorizaed value for the application
function getAuthorizedOverall(address application)
external
view
returns (uint96)
{
return applicationInfo[application].authorizedOverall;
}

/// @notice Returns length of slashing queue
function getSlashingQueueLength() external view override returns (uint256) {
return slashingQueue.length;
Expand Down
57 changes: 57 additions & 0 deletions contracts/test/StableYieldTestSet.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// SPDX-License-Identifier: GPL-3.0-or-later

pragma solidity 0.8.9;

import "../token/T.sol";
import "../staking/IStaking.sol";

contract RewardReceiverMock {
T internal immutable token;

constructor(T _token) {
token = _token;
}

function receiveReward(uint96 reward) external {
token.transferFrom(msg.sender, address(this), reward);
}
}

contract TokenStakingMock {
struct ApplicationInfo {
IStaking.ApplicationStatus status;
uint96 authorizedOverall;
}

mapping(address => ApplicationInfo) public applicationInfo;

function setApplicationInfo(
address application,
IStaking.ApplicationStatus status,
uint96 authorizedOverall
) external {
ApplicationInfo storage info = applicationInfo[application];
info.status = status;
info.authorizedOverall = authorizedOverall;
}

function getApplicationsLength() external pure returns (uint256) {
return 1;
}

function getApplicationStatus(address application)
external
view
returns (IStaking.ApplicationStatus)
{
return applicationInfo[application].status;
}

function getAuthorizedOverall(address application)
external
view
returns (uint96)
{
return applicationInfo[application].authorizedOverall;
}
}
Loading

0 comments on commit 9a42c75

Please sign in to comment.