Skip to content

Commit

Permalink
Merge pull request #534 from ava-labs/staking-rewards
Browse files Browse the repository at this point in the history
Staking rewards
  • Loading branch information
geoff-vball committed Sep 19, 2024
2 parents ff7163e + 099bf13 commit c1de7d3
Show file tree
Hide file tree
Showing 29 changed files with 1,704 additions and 180 deletions.
634 changes: 634 additions & 0 deletions abi-bindings/go/INativeMinter/INativeMinter.go

Large diffs are not rendered by default.

43 changes: 32 additions & 11 deletions abi-bindings/go/mocks/ExampleERC20/ExampleERC20.go

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

12 changes: 10 additions & 2 deletions contracts/mocks/ExampleERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ import {
ERC20Burnable,
ERC20
} from "@openzeppelin/[email protected]/token/ERC20/extensions/ERC20Burnable.sol";
import {IERC20Mintable} from "../staking/interfaces/IERC20Mintable.sol";

contract ExampleERC20 is ERC20Burnable {
contract ExampleERC20 is ERC20Burnable, IERC20Mintable {
string private constant _TOKEN_NAME = "Mock Token";
string private constant _TOKEN_SYMBOL = "EXMP";

uint256 private constant _MAX_MINT = 1e16;
uint256 private constant _MAX_MINT = 1e19;

constructor() ERC20(_TOKEN_NAME, _TOKEN_SYMBOL) {
_mint(msg.sender, 1e28);
Expand All @@ -30,4 +31,11 @@ contract ExampleERC20 is ERC20Burnable {

_mint(msg.sender, amount);
}

function mint(address account, uint256 amount) external {
// Can only mint 10 at a time.
require(amount <= _MAX_MINT, "ExampleERC20: max mint exceeded");

_mint(account, amount);
}
}
24 changes: 16 additions & 8 deletions contracts/staking/ERC20TokenStakingManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ pragma solidity 0.8.25;
import {IERC20TokenStakingManager} from "./interfaces/IERC20TokenStakingManager.sol";
import {Initializable} from
"@openzeppelin/[email protected]/proxy/utils/Initializable.sol";
import {IERC20} from "@openzeppelin/[email protected]/token/ERC20/IERC20.sol";
import {IERC20Mintable} from "./interfaces/IERC20Mintable.sol";
import {SafeERC20TransferFrom} from "@utilities/SafeERC20TransferFrom.sol";
import {SafeERC20} from "@openzeppelin/[email protected]/token/ERC20/utils/SafeERC20.sol";
import {ICMInitializable} from "../utilities/ICMInitializable.sol";
Expand All @@ -21,13 +21,13 @@ contract ERC20TokenStakingManager is
PoSValidatorManager,
IERC20TokenStakingManager
{
using SafeERC20 for IERC20;
using SafeERC20TransferFrom for IERC20;
using SafeERC20 for IERC20Mintable;
using SafeERC20TransferFrom for IERC20Mintable;

// solhint-disable private-vars-leading-underscore
/// @custom:storage-location erc7201:avalanche-icm.storage.ERC20TokenStakingManager
struct ERC20TokenStakingManagerStorage {
IERC20 _token;
IERC20Mintable _token;
uint8 _tokenDecimals;
}
// solhint-enable private-vars-leading-underscore
Expand Down Expand Up @@ -63,22 +63,25 @@ contract ERC20TokenStakingManager is
*/
function initialize(
PoSValidatorManagerSettings calldata settings,
IERC20 token
IERC20Mintable token
) external reinitializer(2) {
__ERC20TokenStakingManager_init(settings, token);
}

// solhint-disable func-name-mixedcase
function __ERC20TokenStakingManager_init(
PoSValidatorManagerSettings calldata settings,
IERC20 token
IERC20Mintable token
) internal onlyInitializing {
__POS_Validator_Manager_init(settings);
__ERC20TokenStakingManager_init_unchained(token);
}

// solhint-disable func-name-mixedcase
function __ERC20TokenStakingManager_init_unchained(IERC20 token) internal onlyInitializing {
function __ERC20TokenStakingManager_init_unchained(IERC20Mintable token)
internal
onlyInitializing
{
ERC20TokenStakingManagerStorage storage $ = _getERC20StakingManagerStorage();
require(address(token) != address(0), "ERC20TokenStakingManager: zero token address");
$._token = token;
Expand Down Expand Up @@ -116,7 +119,12 @@ contract ERC20TokenStakingManager is
return _getERC20StakingManagerStorage()._token.safeTransferFrom(value);
}

function _unlock(uint256 value, address to) internal virtual override {
function _unlock(address to, uint256 value) internal virtual override {
_getERC20StakingManagerStorage()._token.safeTransfer(to, value);
}

function _reward(address account, uint256 amount) internal virtual override {
ERC20TokenStakingManagerStorage storage $ = _getERC20StakingManagerStorage();
$._token.mint(account, amount);
}
}
20 changes: 17 additions & 3 deletions contracts/staking/ExampleRewardCalculator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {IRewardCalculator} from "./interfaces/IRewardCalculator.sol";
contract ExampleRewardCalculator is IRewardCalculator {
uint256 public constant SECONDS_IN_YEAR = 31536000;

uint8 public constant UPTIME_REWARDS_THRESHOLD_PERCENTAGE = 80;

uint64 public immutable rewardBasisPoints;

constructor(uint64 rewardBasisPoints_) {
Expand All @@ -21,11 +23,23 @@ contract ExampleRewardCalculator is IRewardCalculator {
*/
function calculateReward(
uint256 stakeAmount,
uint64 startTime,
uint64 endTime,
uint64 validatorStartTime,
uint64 stakingStartTime,
uint64 stakingEndTime,
uint64 uptimeSeconds,
uint256, // initialSupply
uint256 // endSupply
) external view returns (uint256) {
return (stakeAmount * rewardBasisPoints * (endTime - startTime)) / SECONDS_IN_YEAR / 1000;
// Equivalent to uptimeSeconds/(validator.endedAt - validator.startedAt) < UPTIME_REWARDS_THRESHOLD_PERCENTAGE/100
// Rearranged to prevent integer division truncation.
if (
uptimeSeconds * 100
< (stakingEndTime - validatorStartTime) * UPTIME_REWARDS_THRESHOLD_PERCENTAGE
) {
return 0;
}

return (stakeAmount * rewardBasisPoints * (stakingEndTime - stakingStartTime))
/ SECONDS_IN_YEAR / 10000;
}
}
11 changes: 10 additions & 1 deletion contracts/staking/NativeTokenStakingManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
pragma solidity 0.8.25;

import {INativeTokenStakingManager} from "./interfaces/INativeTokenStakingManager.sol";
import {INativeMinter} from
"@avalabs/[email protected]/contracts/interfaces/INativeMinter.sol";
import {Address} from "@openzeppelin/[email protected]/utils/Address.sol";
import {Initializable} from
"@openzeppelin/[email protected]/proxy/utils/Initializable.sol";
Expand All @@ -21,6 +23,9 @@ contract NativeTokenStakingManager is
{
using Address for address payable;

INativeMinter public constant NATIVE_MINTER =
INativeMinter(0x0200000000000000000000000000000000000001);

constructor(ICMInitializable init) {
if (init == ICMInitializable.Disallowed) {
_disableInitializers();
Expand Down Expand Up @@ -80,7 +85,11 @@ contract NativeTokenStakingManager is
return value;
}

function _unlock(uint256 value, address to) internal virtual override {
function _unlock(address to, uint256 value) internal virtual override {
payable(to).sendValue(value);
}

function _reward(address account, uint256 amount) internal virtual override {
NATIVE_MINTER.mintNativeCoin(account, amount);
}
}
Loading

0 comments on commit c1de7d3

Please sign in to comment.