Skip to content
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
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 0 additions & 13 deletions .github/workflows/contracts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,6 @@ jobs:
- name: Install dependencies
run: yarn install --frozen-lockfile

- name: Resolve latest contracts
run: yarn upgrade @keep-network/keep-core@${{ github.event.inputs.environment }}

- name: Configure tenderly
env:
TENDERLY_TOKEN: ${{ secrets.TENDERLY_TOKEN }}
Expand Down Expand Up @@ -186,13 +183,6 @@ jobs:
- name: Install needed dependencies
run: yarn install --frozen-lockfile

# If we don't remove the `keep-core` contracts from `node-modules`, the
# `etherscan-verify` plugins tries to verify them, which is not desired.
- name: Prepare for verification on Etherscan
run: |
rm -rf ./node_modules/@keep-network/keep-core
rm -rf ./external/npm

- name: Verify contracts on Etherscan
env:
ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY }}
Expand Down Expand Up @@ -226,9 +216,6 @@ jobs:
- name: Install dependencies
run: yarn install --frozen-lockfile

- name: Resolve latest contracts
run: yarn upgrade @keep-network/keep-core@${{ github.event.inputs.environment }}

- name: Deploy contracts
env:
CHAIN_API_URL: ${{ secrets.SEPOLIA_ETH_HOSTNAME_HTTP }}
Expand Down
6 changes: 2 additions & 4 deletions .github/workflows/npm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,8 @@ jobs:
registry-url: "https://registry.npmjs.org"
cache: "yarn"

- name: Resolve latest contracts
run: |
yarn upgrade --exact \
@keep-network/keep-core
- name: Install needed dependencies
run: yarn install --frozen-lockfile

# Deploy contracts to a local network to generate deployment artifacts that
# are required by dashboard compilation.
Expand Down
6 changes: 2 additions & 4 deletions contracts/governance/StakerGovernorVotes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ import "./GovernorParameters.sol";
import "./IVotesHistory.sol";

/// @title StakerGovernorVotes
/// @notice Staker DAO voting power extraction from staked T positions,
// including legacy stakes (NU/KEEP).
/// @notice Staker DAO voting power extraction from staked T positions.
abstract contract StakerGovernorVotes is GovernorParameters {
IVotesHistory public immutable staking;

Expand All @@ -29,8 +28,7 @@ abstract contract StakerGovernorVotes is GovernorParameters {
}

/// @notice Read the voting weight from the snapshot mechanism in the T
/// staking contracts. Note that this also tracks legacy stakes
/// (NU/KEEP).
/// staking contracts.
/// @param account Delegate account with T staking voting power
/// @param blockNumber The block number to get the vote balance at
/// @dev See {IGovernor-getVotes}
Expand Down
11 changes: 2 additions & 9 deletions contracts/governance/TokenholderGovernorVotes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import "./IVotesHistory.sol";

/// @title TokenholderGovernorVotes
/// @notice Tokenholder DAO voting power extraction from both liquid and staked
/// T token positions, including legacy stakes (NU/KEEP).
/// T token positions.
abstract contract TokenholderGovernorVotes is GovernorParameters {
IVotesHistory public immutable token;
IVotesHistory public immutable staking;
Expand All @@ -35,10 +35,6 @@ abstract contract TokenholderGovernorVotes is GovernorParameters {
/// two voting power sources:
/// - Liquid T, tracked by the T token contract
/// - Stakes in the T network, tracked by the T staking contract.
/// Note that this also tracks legacy stakes (NU/KEEP); legacy
/// stakes count for tokenholders' voting power, but not for the
/// total voting power of the Tokenholder DAO
/// (see {_getPastTotalSupply}).
/// @param account Tokenholder account in the T network
/// @param blockNumber The block number to get the vote balance at
/// @dev See {IGovernor-getVotes}
Expand All @@ -57,10 +53,7 @@ abstract contract TokenholderGovernorVotes is GovernorParameters {
/// @notice Compute the total voting power for Tokenholder DAO. Note how it
/// only uses the token total supply as source, as native T tokens
/// that are staked continue existing, but as deposits in the
/// staking contract. However, legacy stakes can't contribute to the
/// total voting power as they're already implicitly counted as part
/// of Vending Machines' liquid balance; hence, we only need to read
/// total voting power from the token.
/// staking contract.
/// @param blockNumber The block number to get the vote power at
function _getPastTotalSupply(uint256 blockNumber)
internal
Expand Down
176 changes: 176 additions & 0 deletions contracts/reward/StableYield.sol
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 Stable yield contract
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// @title Stable yield contract
/// @title StableYield contract

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✔️

/// @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 = caclulateReward(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);
Copy link
Member

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)?

Copy link
Contributor Author

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?

}

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 caclulateReward(address application, uint256 stableYield)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
function caclulateReward(address application, uint256 stableYield)
function caculateReward(address application, uint256 stableYield)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✔️

internal
view
returns (uint96 reward)
{
uint96 authrorizedOverall = tokenStaking.getAuthorizedOverall(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
uint96 authrorizedOverall = tokenStaking.getAuthorizedOverall(
uint96 authorizedOverall = tokenStaking.getAuthorizedOverall(

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✔️

application
);
uint256 totalSupply = token.totalSupply();
// stableYieldPercent * authorizationRate * authorizedOverall =
// (stableYield / STABLE_YIELD_BASE) * (authrorizedOverall / totalSupply) * authorizedOverall
reward = uint96(
(stableYield * authrorizedOverall * authrorizedOverall) /
totalSupply /
STABLE_YIELD_BASE
);
}
}
Loading
Loading