Skip to content

Commit 7583c1f

Browse files
committed
Introduces StableYield contract for minting and distributing reward per app per period
1 parent ede54f4 commit 7583c1f

File tree

5 files changed

+708
-7
lines changed

5 files changed

+708
-7
lines changed

contracts/reward/StableYield.sol

+171
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
// SPDX-License-Identifier: GPL-3.0-or-later
2+
3+
// ██████████████ ▐████▌ ██████████████
4+
// ██████████████ ▐████▌ ██████████████
5+
// ▐████▌ ▐████▌
6+
// ▐████▌ ▐████▌
7+
// ██████████████ ▐████▌ ██████████████
8+
// ██████████████ ▐████▌ ██████████████
9+
// ▐████▌ ▐████▌
10+
// ▐████▌ ▐████▌
11+
// ▐████▌ ▐████▌
12+
// ▐████▌ ▐████▌
13+
// ▐████▌ ▐████▌
14+
// ▐████▌ ▐████▌
15+
16+
pragma solidity ^0.8.9;
17+
18+
import "../token/T.sol";
19+
import "../staking/IStaking.sol";
20+
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
21+
import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
22+
23+
/// @title Stable yield contract
24+
/// @notice Contract that mints and distributes stable yield reward for participating in Threshold Network.
25+
/// Periodically mints reward for each application based on authorization rate and destributes this rewards based on type of application.
26+
contract StableYield is OwnableUpgradeable {
27+
using AddressUpgradeable for address;
28+
29+
struct ApplicationInfo {
30+
uint256 stableYield;
31+
uint256 duration;
32+
address distributor;
33+
string receiveRewardMethod;
34+
uint256 lastMint;
35+
}
36+
37+
uint256 public constant STABLE_YIELD_BASE = 10000;
38+
39+
T immutable token;
40+
IStaking immutable tokenStaking;
41+
mapping(address => ApplicationInfo) public applicationInfo;
42+
43+
/// @dev Event emitted by `setApplicationParameters` function.
44+
event ParametersSet(
45+
address indexed application,
46+
uint256 stableYield,
47+
uint256 duration,
48+
address distributor,
49+
string receiveRewardMethod
50+
);
51+
52+
/// @dev Event emitted by `mintAndPushReward` function.
53+
event MintedReward(address indexed application, uint96 reward);
54+
55+
constructor(T _token, IStaking _tokenStaking) {
56+
// calls to check contracts are working
57+
uint256 totalSupply = _token.totalSupply();
58+
require(
59+
totalSupply > 0 && _tokenStaking.getApplicationsLength() > 0,
60+
"Wrong input parameters"
61+
);
62+
require(
63+
(STABLE_YIELD_BASE * totalSupply * totalSupply) /
64+
totalSupply /
65+
STABLE_YIELD_BASE ==
66+
totalSupply,
67+
"Potential overflow"
68+
);
69+
token = _token;
70+
tokenStaking = _tokenStaking;
71+
_transferOwnership(_msgSender());
72+
}
73+
74+
/// @notice Sets or updates application parameter for minting reward.
75+
/// Can be called only by the governance.
76+
function setApplicationParameters(
77+
address application,
78+
uint256 stableYield,
79+
uint256 duration,
80+
address distributor,
81+
string memory receiveRewardMethod
82+
) external onlyOwner {
83+
// if stable yield is zero then reward will be no longer minted
84+
require(
85+
(stableYield == 0 ||
86+
(stableYield < STABLE_YIELD_BASE && duration > 0)) &&
87+
distributor != address(0),
88+
"Wrong input parameters"
89+
);
90+
ApplicationInfo storage info = applicationInfo[application];
91+
info.stableYield = stableYield;
92+
info.duration = duration;
93+
info.distributor = distributor;
94+
info.receiveRewardMethod = receiveRewardMethod;
95+
emit ParametersSet(
96+
application,
97+
stableYield,
98+
duration,
99+
distributor,
100+
receiveRewardMethod
101+
);
102+
}
103+
104+
/// @notice Mints reward and then pushes it to particular application or distributor.
105+
/// @dev Application must be in `APPROVED` state
106+
function mintAndPushReward(address application) external {
107+
ApplicationInfo storage info = applicationInfo[application];
108+
require(
109+
info.stableYield != 0,
110+
"Reward parameters are not set for the application"
111+
);
112+
require(
113+
block.timestamp >= info.lastMint + info.duration,
114+
"New portion of reward is not ready"
115+
);
116+
IStaking.ApplicationStatus status = tokenStaking.getApplicationStatus(
117+
application
118+
);
119+
require(
120+
status == IStaking.ApplicationStatus.APPROVED,
121+
"Application is not approved"
122+
);
123+
uint96 reward = caclulateReward(application, info.stableYield);
124+
info.lastMint = block.timestamp;
125+
//slither-disable-next-line incorrect-equality
126+
if (bytes(info.receiveRewardMethod).length == 0) {
127+
sendToDistributor(info.distributor, reward);
128+
} else {
129+
executeReceiveReward(application, info.receiveRewardMethod, reward);
130+
}
131+
emit MintedReward(application, reward);
132+
}
133+
134+
function caclulateReward(address application, uint256 stableYield)
135+
internal
136+
view
137+
returns (uint96 reward)
138+
{
139+
uint96 authrorizedOverall = tokenStaking.getAuthorizedOverall(
140+
application
141+
);
142+
uint256 totalSupply = token.totalSupply();
143+
// stableYieldPercent * authorizationRate * authorizedOverall =
144+
// (stableYield / STABLE_YIELD_BASE) * (authrorizedOverall / totalSupply) * authorizedOverall
145+
reward = uint96(
146+
(stableYield * authrorizedOverall * authrorizedOverall) /
147+
totalSupply /
148+
STABLE_YIELD_BASE
149+
);
150+
}
151+
152+
function sendToDistributor(address distributor, uint96 reward) internal {
153+
token.mint(distributor, reward);
154+
}
155+
156+
function executeReceiveReward(
157+
address distributor,
158+
string storage receiveRewardMethod,
159+
uint96 reward
160+
) internal {
161+
token.mint(address(this), reward);
162+
//slither-disable-next-line unused-return
163+
token.approve(distributor, reward);
164+
bytes memory data = abi.encodeWithSignature(
165+
receiveRewardMethod,
166+
reward
167+
);
168+
//slither-disable-next-line unused-return
169+
distributor.functionCall(data);
170+
}
171+
}

contracts/staking/IStaking.sol

+19
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,13 @@ pragma solidity ^0.8.9;
2727
/// delegation optimizes the network throughput without compromising the
2828
/// security of the owners’ stake.
2929
interface IStaking {
30+
enum ApplicationStatus {
31+
NOT_APPROVED,
32+
APPROVED,
33+
PAUSED,
34+
DISABLED
35+
}
36+
3037
//
3138
//
3239
// Delegating a stake
@@ -267,6 +274,18 @@ interface IStaking {
267274
/// @notice Returns length of application array
268275
function getApplicationsLength() external view returns (uint256);
269276

277+
/// @notice Returns status of the application
278+
function getApplicationStatus(address application)
279+
external
280+
view
281+
returns (ApplicationStatus);
282+
283+
/// @notice Returns overall auhtorizaed value for the application
284+
function getAuthorizedOverall(address application)
285+
external
286+
view
287+
returns (uint96);
288+
270289
/// @notice Returns length of slashing queue
271290
function getSlashingQueueLength() external view returns (uint256);
272291

contracts/staking/TokenStaking.sol

+18-7
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,6 @@ contract TokenStaking is Initializable, IStaking, Checkpoints {
4646
T
4747
}
4848

49-
enum ApplicationStatus {
50-
NOT_APPROVED,
51-
APPROVED,
52-
PAUSED,
53-
DISABLED
54-
}
55-
5649
struct StakingProviderInfo {
5750
uint96 nuInTStake;
5851
address owner;
@@ -920,6 +913,24 @@ contract TokenStaking is Initializable, IStaking, Checkpoints {
920913
return applications.length;
921914
}
922915

916+
/// @notice Returns status of the application
917+
function getApplicationStatus(address application)
918+
external
919+
view
920+
returns (ApplicationStatus)
921+
{
922+
return applicationInfo[application].status;
923+
}
924+
925+
/// @notice Returns overall auhtorizaed value for the application
926+
function getAuthorizedOverall(address application)
927+
external
928+
view
929+
returns (uint96)
930+
{
931+
return applicationInfo[application].authorizedOverall;
932+
}
933+
923934
/// @notice Returns length of slashing queue
924935
function getSlashingQueueLength() external view override returns (uint256) {
925936
return slashingQueue.length;

contracts/test/StableYieldTestSet.sol

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// SPDX-License-Identifier: GPL-3.0-or-later
2+
3+
pragma solidity 0.8.9;
4+
5+
import "../token/T.sol";
6+
import "../staking/IStaking.sol";
7+
8+
contract RewardReceiverMock {
9+
T internal immutable token;
10+
11+
constructor(T _token) {
12+
token = _token;
13+
}
14+
15+
function receiveReward(uint96 reward) external {
16+
token.transferFrom(msg.sender, address(this), reward);
17+
}
18+
}
19+
20+
contract TokenStakingMock {
21+
struct ApplicationInfo {
22+
IStaking.ApplicationStatus status;
23+
uint96 authorizedOverall;
24+
}
25+
26+
mapping(address => ApplicationInfo) public applicationInfo;
27+
28+
function setApplicationInfo(
29+
address application,
30+
IStaking.ApplicationStatus status,
31+
uint96 authorizedOverall
32+
) external {
33+
ApplicationInfo storage info = applicationInfo[application];
34+
info.status = status;
35+
info.authorizedOverall = authorizedOverall;
36+
}
37+
38+
function getApplicationsLength() external pure returns (uint256) {
39+
return 1;
40+
}
41+
42+
function getApplicationStatus(address application)
43+
external
44+
view
45+
returns (IStaking.ApplicationStatus)
46+
{
47+
return applicationInfo[application].status;
48+
}
49+
50+
function getAuthorizedOverall(address application)
51+
external
52+
view
53+
returns (uint96)
54+
{
55+
return applicationInfo[application].authorizedOverall;
56+
}
57+
}

0 commit comments

Comments
 (0)