Skip to content

Commit

Permalink
Native staking rewards
Browse files Browse the repository at this point in the history
  • Loading branch information
feuGeneA committed Sep 11, 2024
1 parent f26ecbe commit b12166b
Show file tree
Hide file tree
Showing 8 changed files with 2,201 additions and 33 deletions.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

7 changes: 6 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 @@ -20,6 +22,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 @@ -85,6 +90,6 @@ contract NativeTokenStakingManager is

// solhint-disable-next-line no-empty-blocks
function _reward(address account, uint256 amount) internal virtual override {
// TODO: call the native minter precompile to mint `amount` for `account`
NATIVE_MINTER.mintNativeCoin(account, amount);
}
}
11 changes: 6 additions & 5 deletions contracts/staking/PoSValidatorManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -106,13 +106,14 @@ abstract contract PoSValidatorManager is IPoSValidatorManager, ValidatorManager

function completeEndValidation(uint32 messageIndex) external {
Validator memory validator = _completeEndValidation(messageIndex);
if (validator.status == ValidatorStatus.Completed) {
PoSValidatorManagerStorage storage $ = _getPoSValidatorManagerStorage();

PoSValidatorManagerStorage storage $ = _getPoSValidatorManagerStorage();

_reward(validator.owner, $._pendingValidatorRewards[validator.owner]);
delete $._pendingValidatorRewards[validator.owner];
_reward(validator.owner, $._pendingValidatorRewards[validator.owner]);
delete $._pendingValidatorRewards[validator.owner];

_unlock(validator.startingWeight, validator.owner);
_unlock(validator.startingWeight, validator.owner);
}
}

function _getUptime(bytes32 validationID, uint32 messageIndex) internal view returns (uint64) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ contract ERC20TokenStakingManagerTest is PoSValidatorManagerTest {

ERC20TokenStakingManager public app;
IERC20Mintable public token;
ExampleRewardCalculator public rewardCalculator;

function setUp() public virtual {
// Construct the object under test
Expand Down
34 changes: 19 additions & 15 deletions contracts/staking/tests/NativeTokenStakingManagerTests.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ import {PoSValidatorManagerTest} from "./PoSValidatorManagerTests.t.sol";
import {NativeTokenStakingManager} from "../NativeTokenStakingManager.sol";
import {ValidatorManagerSettings} from "../interfaces/IValidatorManager.sol";
import {PoSValidatorManagerSettings} from "../interfaces/IPoSValidatorManager.sol";
import {IRewardCalculator} from "../interfaces/IRewardCalculator.sol";
import {ExampleRewardCalculator} from "../ExampleRewardCalculator.sol";
import {ICMInitializable} from "../../utilities/ICMInitializable.sol";
import {INativeMinter} from
"@avalabs/[email protected]/contracts/interfaces/INativeMinter.sol";

// TODO: Remove this once all unit tests implemented
// solhint-disable no-empty-blocks
Expand All @@ -21,6 +22,7 @@ contract NativeTokenStakingManagerTest is PoSValidatorManagerTest {
function setUp() public virtual {
// Construct the object under test
app = new NativeTokenStakingManager(ICMInitializable.Allowed);
rewardCalculator = new ExampleRewardCalculator(DEFAULT_REWARD_RATE);
app.initialize(
PoSValidatorManagerSettings({
baseSettings: ValidatorManagerSettings({
Expand All @@ -31,25 +33,13 @@ contract NativeTokenStakingManagerTest is PoSValidatorManagerTest {
minimumStakeAmount: DEFAULT_MINIMUM_STAKE,
maximumStakeAmount: DEFAULT_MAXIMUM_STAKE,
minimumStakeDuration: DEFAULT_MINIMUM_STAKE_DURATION,
rewardCalculator: IRewardCalculator(new ExampleRewardCalculator(DEFAULT_REWARD_RATE))
rewardCalculator: rewardCalculator
})
);
validatorManager = app;
posValidatorManager = app;
}

function testCompleteEndValidation() public override {
// TODO: get native token staking rewards working, then remove this
// method and let the implementation in PosValidatorManagerTests do the
// test, and remove the `virtual` modifier from that implementation.
}

function testCompleteEndDelegation() public override {
// TODO: get native token staking rewards working, then remove this
// method and let the implementation in PosValidatorManagerTests do the
// test, and remove the `virtual` modifier from that implementation.
}

// Helpers
function _initializeValidatorRegistration(
bytes32 nodeID,
Expand Down Expand Up @@ -82,7 +72,21 @@ contract NativeTokenStakingManagerTest is PoSValidatorManagerTest {
vm.expectCall(account, amount, "");
}

function _expectRewardIssuance(address account, uint256 amount) internal override {}
function _expectRewardIssuance(address account, uint256 amount) internal override {
vm.mockCall(
address(app.NATIVE_MINTER()),
abi.encodeCall(INativeMinter.mintNativeCoin, (account, amount)),
""
);
// empty calldata implies the receive function will be called:
vm.mockCall({
callee: account,
msgValue: amount,
data: "", // implies receive()
returnData: ""
});
vm.deal(account, account.balance + amount);
}

function _getStakeAssetBalance(address account) internal view override returns (uint256) {
return account.balance;
Expand Down
53 changes: 45 additions & 8 deletions contracts/staking/tests/PoSValidatorManagerTests.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

pragma solidity 0.8.25;

import {ExampleRewardCalculator} from "../ExampleRewardCalculator.sol";
import {ValidatorManagerTest} from "./ValidatorManagerTests.t.sol";
import {PoSValidatorManager} from "../PoSValidatorManager.sol";
import {
Expand All @@ -24,6 +25,7 @@ abstract contract PoSValidatorManagerTest is ValidatorManagerTest {
uint64 public constant DEFAULT_REWARD_RATE = uint64(10);

PoSValidatorManager public posValidatorManager;
ExampleRewardCalculator public rewardCalculator;

event DelegatorAdded(
bytes32 indexed delegationID,
Expand Down Expand Up @@ -404,7 +406,7 @@ abstract contract PoSValidatorManagerTest is ValidatorManagerTest {
validatorManager.resendEndValidatorMessage(validationID);
}

function testCompleteEndDelegation() public virtual {
function testCompleteEndDelegation() public {
uint256 registrationDuration = 1000 * 60 * 60 * 24; // 1 day

uint256 registrationExpiry = vm.getBlockTimestamp() + registrationDuration;
Expand Down Expand Up @@ -444,13 +446,28 @@ abstract contract PoSValidatorManagerTest is ValidatorManagerTest {

address delegator = DEFAULT_DELEGATOR_ADDRESS;
uint256 balanceBefore = _getStakeAssetBalance(delegator);
uint256 expectedReward = DEFAULT_DELEGATOR_WEIGHT * DEFAULT_REWARD_RATE / 365;
_expectStakeUnlock(delegator, DEFAULT_DELEGATOR_WEIGHT);
_expectRewardIssuance(delegator, expectedReward);
uint256 expectedReward = rewardCalculator.calculateReward(
DEFAULT_DELEGATOR_WEIGHT,
uint64(registrationExpiry) - DEFAULT_DELEGATOR_COMPLETE_REGISTRATION_TIMESTAMP,
0,
0
);

_setUpCompleteEndDelegation(validationID, delegationID, DEFAULT_WEIGHT, DEFAULT_WEIGHT, 2);
_setUpCompleteEndDelegation({
validationID: validationID,
delegationID: delegationID,
delegator: delegator,
delegatorWeight: DEFAULT_DELEGATOR_WEIGHT,
expectedReward: expectedReward,
validatorWeight: DEFAULT_WEIGHT,
expectedValidatorWeight: DEFAULT_WEIGHT,
expectedNonce: 2
});

uint256 balanceChange = _getStakeAssetBalance(delegator) - balanceBefore;
uint256 balanceAfter = _getStakeAssetBalance(delegator);
vm.assertEq(balanceAfter, balanceAfter);
vm.assertEq(expectedReward, expectedReward);
uint256 balanceChange = balanceAfter - balanceBefore;
require(
balanceChange == DEFAULT_DELEGATOR_WEIGHT + expectedReward,
"delegator should have received their stake back and been rewarded"
Expand Down Expand Up @@ -598,10 +615,25 @@ abstract contract PoSValidatorManagerTest is ValidatorManagerTest {
});

// Complete delegation1 by delivering the weight update from nonce 4 (delegator2's nonce)
_setUpCompleteEndDelegation(validationID, delegationID1, DEFAULT_WEIGHT, DEFAULT_WEIGHT, 4);
_setUpCompleteEndDelegation({
validationID: validationID,
delegationID: delegationID1,
delegator: DEFAULT_DELEGATOR_ADDRESS,
delegatorWeight: DEFAULT_DELEGATOR_WEIGHT,
expectedReward: rewardCalculator.calculateReward(
DEFAULT_DELEGATOR_WEIGHT,
DEFAULT_DELEGATOR_COMPLETE_REGISTRATION_TIMESTAMP
- DEFAULT_DELEGATOR_INIT_REGISTRATION_TIMESTAMP,
0,
0
),
validatorWeight: DEFAULT_WEIGHT,
expectedValidatorWeight: DEFAULT_WEIGHT,
expectedNonce: 4
});
}

function testCompleteEndValidation() public virtual override {
function testCompleteEndValidation() public override {
uint256 registrationDuration = 1000 * 60 * 60 * 24; // 1 day

uint256 registrationExpiry = vm.getBlockTimestamp() + registrationDuration;
Expand Down Expand Up @@ -756,6 +788,9 @@ abstract contract PoSValidatorManagerTest is ValidatorManagerTest {
function _setUpCompleteEndDelegation(
bytes32 validationID,
bytes32 delegationID,
address delegator,
uint256 delegatorWeight,
uint256 expectedReward,
uint64 validatorWeight,
uint64 expectedValidatorWeight,
uint64 expectedNonce
Expand All @@ -767,6 +802,8 @@ abstract contract PoSValidatorManagerTest is ValidatorManagerTest {

vm.expectEmit(true, true, true, true, address(posValidatorManager));
emit DelegationEnded(delegationID, validationID, expectedNonce);
_expectStakeUnlock(delegator, delegatorWeight);
_expectRewardIssuance(delegator, expectedReward);
posValidatorManager.completeEndDelegation(0, delegationID);
assertEq(posValidatorManager.getWeight(validationID), expectedValidatorWeight);
return delegationID;
Expand Down

0 comments on commit b12166b

Please sign in to comment.