Skip to content

Commit 265529a

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

File tree

5 files changed

+713
-7
lines changed

5 files changed

+713
-7
lines changed

contracts/reward/StableYield.sol

+176
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
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+
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
40+
T internal immutable token;
41+
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
42+
IStaking internal immutable tokenStaking;
43+
44+
mapping(address => ApplicationInfo) public applicationInfo;
45+
46+
/// @dev Event emitted by `setApplicationParameters` function.
47+
event ParametersSet(
48+
address indexed application,
49+
uint256 stableYield,
50+
uint256 duration,
51+
address distributor,
52+
string receiveRewardMethod
53+
);
54+
55+
/// @dev Event emitted by `mintAndPushReward` function.
56+
event MintedReward(address indexed application, uint96 reward);
57+
58+
constructor(T _token, IStaking _tokenStaking) {
59+
// calls to check contracts are working
60+
uint256 totalSupply = _token.totalSupply();
61+
require(
62+
totalSupply > 0 && _tokenStaking.getApplicationsLength() > 0,
63+
"Wrong input parameters"
64+
);
65+
require(
66+
(STABLE_YIELD_BASE * totalSupply * totalSupply) /
67+
totalSupply /
68+
STABLE_YIELD_BASE ==
69+
totalSupply,
70+
"Potential overflow"
71+
);
72+
token = _token;
73+
tokenStaking = _tokenStaking;
74+
_transferOwnership(_msgSender());
75+
}
76+
77+
/// @notice Sets or updates application parameter for minting reward.
78+
/// Can be called only by the governance.
79+
function setApplicationParameters(
80+
address application,
81+
uint256 stableYield,
82+
uint256 duration,
83+
address distributor,
84+
string memory receiveRewardMethod
85+
) external onlyOwner {
86+
// if stable yield is zero then reward will be no longer minted
87+
require(
88+
(stableYield == 0 ||
89+
(stableYield < STABLE_YIELD_BASE && duration > 0)) &&
90+
distributor != address(0),
91+
"Wrong input parameters"
92+
);
93+
ApplicationInfo storage info = applicationInfo[application];
94+
info.stableYield = stableYield;
95+
info.duration = duration;
96+
info.distributor = distributor;
97+
info.receiveRewardMethod = receiveRewardMethod;
98+
emit ParametersSet(
99+
application,
100+
stableYield,
101+
duration,
102+
distributor,
103+
receiveRewardMethod
104+
);
105+
}
106+
107+
/// @notice Mints reward and then pushes it to particular application or distributor.
108+
/// @dev Application must be in `APPROVED` state
109+
function mintAndPushReward(address application) external {
110+
ApplicationInfo storage info = applicationInfo[application];
111+
require(
112+
info.stableYield != 0,
113+
"Reward parameters are not set for the application"
114+
);
115+
require(
116+
/* solhint-disable-next-line not-rely-on-time */
117+
block.timestamp >= info.lastMint + info.duration,
118+
"New portion of reward is not ready"
119+
);
120+
IStaking.ApplicationStatus status = tokenStaking.getApplicationStatus(
121+
application
122+
);
123+
require(
124+
status == IStaking.ApplicationStatus.APPROVED,
125+
"Application is not approved"
126+
);
127+
uint96 reward = caclulateReward(application, info.stableYield);
128+
/* solhint-disable-next-line not-rely-on-time */
129+
info.lastMint = block.timestamp;
130+
//slither-disable-next-line incorrect-equality
131+
if (bytes(info.receiveRewardMethod).length == 0) {
132+
sendToDistributor(info.distributor, reward);
133+
} else {
134+
executeReceiveReward(application, info.receiveRewardMethod, reward);
135+
}
136+
emit MintedReward(application, reward);
137+
}
138+
139+
function sendToDistributor(address distributor, uint96 reward) internal {
140+
token.mint(distributor, reward);
141+
}
142+
143+
function executeReceiveReward(
144+
address distributor,
145+
string storage receiveRewardMethod,
146+
uint96 reward
147+
) internal {
148+
token.mint(address(this), reward);
149+
//slither-disable-next-line unused-return
150+
token.approve(distributor, reward);
151+
bytes memory data = abi.encodeWithSignature(
152+
receiveRewardMethod,
153+
reward
154+
);
155+
//slither-disable-next-line unused-return
156+
distributor.functionCall(data);
157+
}
158+
159+
function caclulateReward(address application, uint256 stableYield)
160+
internal
161+
view
162+
returns (uint96 reward)
163+
{
164+
uint96 authrorizedOverall = tokenStaking.getAuthorizedOverall(
165+
application
166+
);
167+
uint256 totalSupply = token.totalSupply();
168+
// stableYieldPercent * authorizationRate * authorizedOverall =
169+
// (stableYield / STABLE_YIELD_BASE) * (authrorizedOverall / totalSupply) * authorizedOverall
170+
reward = uint96(
171+
(stableYield * authrorizedOverall * authrorizedOverall) /
172+
totalSupply /
173+
STABLE_YIELD_BASE
174+
);
175+
}
176+
}

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 getApplicationStatus(address application)
39+
external
40+
view
41+
returns (IStaking.ApplicationStatus)
42+
{
43+
return applicationInfo[application].status;
44+
}
45+
46+
function getAuthorizedOverall(address application)
47+
external
48+
view
49+
returns (uint96)
50+
{
51+
return applicationInfo[application].authorizedOverall;
52+
}
53+
54+
function getApplicationsLength() external pure returns (uint256) {
55+
return 1;
56+
}
57+
}

0 commit comments

Comments
 (0)