-
Notifications
You must be signed in to change notification settings - Fork 22
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
WIP: Validator Manager contracts #571
base: main
Are you sure you want to change the base?
Changes from 250 commits
2109a68
0eb93c6
69242fd
8d2969a
b465fba
c215b3c
d9ae361
fbeb9c5
fb872a7
b943edc
8e25aa2
9bbfde2
7d96776
be8d434
bda2341
ebe122e
32d2b9e
260b8a1
75b4b38
90e33ad
6623df0
0896ba3
ff7163e
8a791c1
56fae8b
fcaf32c
44807c9
13e210b
061c125
3716819
19e28b3
d5e5e26
25e8de5
622e6a8
a7998b8
c9b1556
bc7ddc7
9e80079
491ae79
4733df1
00504cb
e17bb7f
5a288d5
4087297
2eda1da
25aade4
015d92e
6e46195
daad525
3286116
6b1e9c0
db44dab
49636c1
b4ea1b3
71230b9
8f25e07
099bf13
2b9ff69
c1de7d3
22bd94a
b8d58ee
f01daad
4b2e326
6c4b0f7
0c7b3eb
f03a6c8
ef2425c
fd20c84
bc4de86
627f47b
1aa83fe
59a98b1
ed15d98
5048225
9683e9c
4149155
a2e817a
e6e1629
c8ee395
bafc103
e7ac131
329d1e7
059aee3
6a80d63
556c147
4b00073
396bf4c
b61841d
315ee62
69cf948
1793727
64700da
d0b3724
5d1be58
d9b18da
0eaeea8
d7367d6
f73d2d0
c54321c
9a17f4c
8c27c5a
f3a6194
09e69d7
855eb00
6c38fdd
a23bedb
be119a0
8aa030e
1c5d546
bb4d482
a384357
7077a9a
4dbb52c
2f8e980
b5ee7f4
9dbb1fa
d8635f3
c25a48a
81edf43
0b5e0e2
56fb125
98ff484
fd7cf49
d5771d0
2b7acc6
8532f14
ff9e06e
c1e1b95
698d4a4
14003d2
fadf58f
a3ba881
890ae47
d7924fc
d1304e9
18b0a11
d69a789
3a61c5a
32c5f81
c82792f
3798c23
080824e
0de3381
454157e
0fda279
f119180
6492e9d
c4a34bc
e1c3a48
1420c70
3f15578
46acf6b
c44ae2b
b64f307
44a40e3
3adb900
8d95406
df755ce
9a8a7c6
414f9a9
fd9663f
b9b7d04
0d6cd3d
fed2329
14be1a8
6c729e7
7e5a023
8b01977
debe234
9229121
81754e5
80c4230
6121347
6e8fa6b
f1a5147
b412e48
9515841
3b06b23
80902f0
b015856
54e0c76
e6ffd3b
90967aa
907e41d
506c603
29214d2
85daef0
d618a68
5f26868
3f0b287
f74c575
0ca3a53
2207e65
b553d96
f90c70d
f4bb683
f281179
06a244e
d6e0632
ead93bc
7c1e007
72ea62d
63bad1a
406caf7
2d5bbb3
3b62b9b
c8ebfd2
5a76c07
0fa2ae8
c3a0640
70b85e9
b4710e5
c9320a9
7136669
3ecdb46
2f3390a
e033da0
e0e108b
f57c860
11e8a39
0cb408f
380fc9a
a3b77b0
9123f15
ec11cd1
9a962ed
f49fe5f
6ec27cc
6f73651
f6c5e23
944591a
33d624e
ba5cd03
02ce9d0
43c2279
391b03c
cbe7a7e
4bd2158
52524ea
c12941b
75ecd7c
098442e
646e4dd
4cbde54
dac3220
6174cef
a6eb5b1
7bcb3e3
2c756cd
8af29c8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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.
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.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,12 +13,13 @@ import { | |
ERC20Burnable, | ||
ERC20 | ||
} from "@openzeppelin/[email protected]/token/ERC20/extensions/ERC20Burnable.sol"; | ||
import {IERC20Mintable} from "../validator-manager/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); | ||
|
@@ -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); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
// (c) 2024, Ava Labs, Inc. All rights reserved. | ||
// See the file LICENSE for licensing terms. | ||
|
||
// SPDX-License-Identifier: Ecosystem | ||
pragma solidity 0.8.25; | ||
|
||
enum ICMInitializable { | ||
Allowed, | ||
Disallowed | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
## Delegation | ||
|
||
`PoSValidatorManager` supports Delegation to an active Validator as a way for users to earn staking rewards without having to validate the chain. Delegators pay a configurable percentage fee on any earned staking rewards to the host Validator. Delegations are reflected on the P-Chain by adjusting the Validator's registered weight via a [`SetSubnetValidatorWeightTx`](https://github.com/avalanche-foundation/ACPs/tree/main/ACPs/77-reinventing-subnets#setsubnetvalidatorweighttx). The weight change acknowledgement is delivered to the `PoSValidatorManager` via a [`SubnetValidatorWeightUpdateMessage`](./README.md#subnetvalidatorweightupdatemessage). | ||
|
||
> [!NOTE] | ||
> The P-Chain is only willing to sign a `SubnetValidatorWeightUpdateMessage` for an active Validator. Once Validator exit has been initiated (via a call to `initializeEndValidation`), the `PoSValidatorManager` must assume that the Validator has been deactivated on the P-Chain, and will therefore not sign any further weight updates. Therefore, it is invalid to _initiate_ adding or removing a Delegator when the Validator is in this state, though it _may be_ valid to _complete_ an already initiated Delegator action, depending on the order of delivery to the P-Chain. If the Delegator weight change was submitted (and a Warp signature on the acknowledgement retrieved) before the Validator was removed, then the Delegator action may be completed. Otherwise, the acknowledgement of the Validation end must first be delivered before completing the Delegator action. | ||
|
||
The following state transition diagram illustrates the relationship between Validator and Delegator state. `Validator` is abbreviated as `V`, `Delegator` is abbreviated as `D`, and function names are shortened to improve readability. `Delegator.Completed` is omitted to show equivalance between the Validator's initial state and the state after a Delegation completes. | ||
```mermaid | ||
stateDiagram-v2 | ||
% Happy path | ||
[*] --> V.PendingAdded : initAddVdr | ||
V.PendingAdded --> V.Active : completeVdrReg | ||
V.Active --> V.Active,D.PendingAdded : initAddDel | ||
V.Active,D.PendingAdded --> V.Active,D.Active : completeAddDel | ||
V.Active,D.Active --> V.Active,D.PendingRemoved : initEndDel | ||
V.Active,D.PendingRemoved --> V.Active : completeEndDel | ||
V.Active --> V.PendingRemoved : initEndVdr | ||
V.PendingRemoved --> V.Completed : completeEndVdr | ||
|
||
% Validator/Delegator state changes do not affect the Delegator/Validator state | ||
V.Active,D.PendingRemoved --> V.PendingRemoved,D.PendingRemoved : initEndVdr | ||
V.Active,D.PendingAdded --> V.PendingRemoved,D.PendingAdded : initEndVdr | ||
V.Active,D.Active --> V.PendingRemoved,D.Active: initEndVdr | ||
|
||
% When the Validator is in PendingRemoved or Completed, in general Delegator actions | ||
% may be completed, but not initialized. | ||
V.PendingRemoved,D.PendingAdded --> V.Completed,D.PendingAdded : completeEndVdr | ||
V.PendingRemoved,D.PendingRemoved --> V.Completed,D.PendingRemoved : completeEndVdr | ||
V.PendingRemoved,D.PendingRemoved --> V.PendingRemoved : completeEndDel | ||
V.PendingRemoved,D.Active --> V.Completed,D.Active : completeEndVdr | ||
% This is a no-op | ||
V.PendingRemoved,D.PendingAdded --> V.PendingRemoved,D.Active : completeAddDel | ||
V.Completed,D.PendingRemoved --> V.Completed : endDelegationCompletedValidator | ||
V.Completed,D.Active --> V.Completed : endDelegationCompletedValidator | ||
V.Completed,D.PendingAdded --> V.Completed : endDelegationCompletedValidator | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
// (c) 2024, Ava Labs, Inc. All rights reserved. | ||
// See the file LICENSE for licensing terms. | ||
|
||
// SPDX-License-Identifier: Ecosystem | ||
|
||
pragma solidity 0.8.25; | ||
|
||
import {IERC20TokenStakingManager} from "./interfaces/IERC20TokenStakingManager.sol"; | ||
import {Initializable} from | ||
"@openzeppelin/[email protected]/proxy/utils/Initializable.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"; | ||
import {PoSValidatorManager} from "./PoSValidatorManager.sol"; | ||
import {PoSValidatorManagerSettings} from "./interfaces/IPoSValidatorManager.sol"; | ||
import {ValidatorRegistrationInput} from "./interfaces/IValidatorManager.sol"; | ||
|
||
contract ERC20TokenStakingManager is | ||
Initializable, | ||
PoSValidatorManager, | ||
IERC20TokenStakingManager | ||
{ | ||
using SafeERC20 for IERC20Mintable; | ||
using SafeERC20TransferFrom for IERC20Mintable; | ||
|
||
// solhint-disable private-vars-leading-underscore | ||
/// @custom:storage-location erc7201:avalanche-icm.storage.ERC20TokenStakingManager | ||
struct ERC20TokenStakingManagerStorage { | ||
IERC20Mintable _token; | ||
uint8 _tokenDecimals; | ||
} | ||
// solhint-enable private-vars-leading-underscore | ||
|
||
// keccak256(abi.encode(uint256(keccak256("avalanche-icm.storage.ERC20TokenStakingManager")) - 1)) & ~bytes32(uint256(0xff)); | ||
// TODO: Update to correct storage slot | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. remove this after verified |
||
bytes32 private constant _ERC20_STAKING_MANAGER_STORAGE_LOCATION = | ||
0x6e5bdfcce15e53c3406ea67bfce37dcd26f5152d5492824e43fd5e3c8ac5ab00; | ||
|
||
// solhint-disable ordering | ||
function _getERC20StakingManagerStorage() | ||
private | ||
pure | ||
returns (ERC20TokenStakingManagerStorage storage $) | ||
{ | ||
// solhint-disable-next-line no-inline-assembly | ||
assembly { | ||
$.slot := _ERC20_STAKING_MANAGER_STORAGE_LOCATION | ||
} | ||
} | ||
|
||
constructor(ICMInitializable init) { | ||
if (init == ICMInitializable.Disallowed) { | ||
_disableInitializers(); | ||
} | ||
} | ||
|
||
/** | ||
* @notice Initialize the ERC20 token staking manager | ||
* @dev Uses reinitializer(2) on the PoS staking contracts to make sure after migration from PoA, the PoS contracts can reinitialize with its needed values. | ||
* @param settings Initial settings for the PoS validator manager | ||
* @param token The ERC20 token to be staked | ||
*/ | ||
function initialize( | ||
PoSValidatorManagerSettings calldata settings, | ||
IERC20Mintable token | ||
) external reinitializer(2) { | ||
__ERC20TokenStakingManager_init(settings, token); | ||
} | ||
|
||
// solhint-disable func-name-mixedcase | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should use |
||
function __ERC20TokenStakingManager_init( | ||
PoSValidatorManagerSettings calldata settings, | ||
IERC20Mintable token | ||
) internal onlyInitializing { | ||
__POS_Validator_Manager_init(settings); | ||
__ERC20TokenStakingManager_init_unchained(token); | ||
} | ||
|
||
// solhint-disable func-name-mixedcase | ||
function __ERC20TokenStakingManager_init_unchained(IERC20Mintable token) | ||
internal | ||
onlyInitializing | ||
{ | ||
ERC20TokenStakingManagerStorage storage $ = _getERC20StakingManagerStorage(); | ||
if (address(token) == address(0)) { | ||
revert InvalidAddress(); | ||
} | ||
$._token = token; | ||
} | ||
|
||
/** | ||
* @notice See {IERC20TokenStakingManager-initializeValidatorRegistration} | ||
* Begins the validator registration process. Locks the configured ERC20 in the contract as the stake. | ||
*/ | ||
function initializeValidatorRegistration( | ||
ValidatorRegistrationInput calldata registrationInput, | ||
uint16 delegationFeeBips, | ||
uint64 minStakeDuration, | ||
uint256 stakeAmount | ||
) external nonReentrant returns (bytes32 validationID) { | ||
return _initializeValidatorRegistration( | ||
registrationInput, delegationFeeBips, minStakeDuration, stakeAmount | ||
); | ||
} | ||
|
||
/** | ||
* @notice Begins the delegator registration process. Locks the configured ERC20 in the contract as the delegated stake. | ||
* @param validationID The ID of the validation period being delegated to. | ||
* @param delegationAmount The amount to be delegated. | ||
*/ | ||
function initializeDelegatorRegistration( | ||
bytes32 validationID, | ||
uint256 delegationAmount | ||
) external nonReentrant returns (bytes32) { | ||
return _initializeDelegatorRegistration(validationID, _msgSender(), delegationAmount); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. don't think this internal funciton needs the |
||
} | ||
|
||
// Must be guarded with reentrancy guard for safe transfer from | ||
function _lock(uint256 value) internal virtual override returns (uint256) { | ||
return _getERC20StakingManagerStorage()._token.safeTransferFrom(value); | ||
} | ||
|
||
function _unlock(address to, uint256 value) internal virtual override { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can also add a note about reentrancy guard like the one above for |
||
_getERC20StakingManagerStorage()._token.safeTransfer(to, value); | ||
} | ||
|
||
function _reward(address account, uint256 amount) internal virtual override { | ||
ERC20TokenStakingManagerStorage storage $ = _getERC20StakingManagerStorage(); | ||
$._token.mint(account, amount); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
// (c) 2024, Ava Labs, Inc. All rights reserved. | ||
// See the file LICENSE for licensing terms. | ||
|
||
// SPDX-License-Identifier: Ecosystem | ||
|
||
pragma solidity 0.8.25; | ||
|
||
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_) { | ||
rewardBasisPoints = rewardBasisPoints_; | ||
} | ||
|
||
/** | ||
* @notice A linear, non-compounding reward calculation that rewards a set percentage of tokens per year. | ||
*/ | ||
function calculateReward( | ||
uint256 stakeAmount, | ||
uint64 validatorStartTime, | ||
uint64 stakingStartTime, | ||
uint64 stakingEndTime, | ||
uint64 uptimeSeconds, | ||
uint256, // initialSupply | ||
uint256 // endSupply | ||
) external view returns (uint256) { | ||
// 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; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
// (c) 2024, Ava Labs, Inc. All rights reserved. | ||
// See the file LICENSE for licensing terms. | ||
|
||
// SPDX-License-Identifier: Ecosystem | ||
|
||
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"; | ||
import {ICMInitializable} from "../utilities/ICMInitializable.sol"; | ||
import {PoSValidatorManager} from "./PoSValidatorManager.sol"; | ||
import {PoSValidatorManagerSettings} from "./interfaces/IPoSValidatorManager.sol"; | ||
import {ValidatorRegistrationInput} from "./interfaces/IValidatorManager.sol"; | ||
|
||
contract NativeTokenStakingManager is | ||
Initializable, | ||
PoSValidatorManager, | ||
INativeTokenStakingManager | ||
{ | ||
using Address for address payable; | ||
|
||
INativeMinter public constant NATIVE_MINTER = | ||
INativeMinter(0x0200000000000000000000000000000000000001); | ||
|
||
constructor(ICMInitializable init) { | ||
if (init == ICMInitializable.Disallowed) { | ||
_disableInitializers(); | ||
} | ||
} | ||
|
||
/** | ||
* @notice Initialize the ERC20 token staking manager | ||
* @dev Uses reinitializer(2) on the PoS staking contracts to make sure after migration from PoA, the PoS contracts can reinitialize with its needed values. | ||
* @param settings Initial settings for the PoS validator manager | ||
*/ | ||
// solhint-disable ordering | ||
function initialize(PoSValidatorManagerSettings calldata settings) external reinitializer(2) { | ||
__NativeTokenStakingManager_init(settings); | ||
} | ||
|
||
// solhint-disable-next-line func-name-mixedcase | ||
function __NativeTokenStakingManager_init(PoSValidatorManagerSettings calldata settings) | ||
internal | ||
onlyInitializing | ||
{ | ||
__POS_Validator_Manager_init(settings); | ||
} | ||
|
||
// solhint-disable-next-line func-name-mixedcase, no-empty-blocks | ||
function __NativeTokenStakingManager_init_unchained() internal onlyInitializing {} | ||
|
||
/** | ||
* @notice See {INativeTokenStakingManager-initializeValidatorRegistration}. | ||
* Begins the validator registration process. Locks the provided native asset in the contract as the stake. | ||
*/ | ||
function initializeValidatorRegistration( | ||
ValidatorRegistrationInput calldata registrationInput, | ||
uint16 delegationFeeBips, | ||
uint64 minStakeDuration | ||
) external payable nonReentrant returns (bytes32) { | ||
return _initializeValidatorRegistration( | ||
registrationInput, delegationFeeBips, minStakeDuration, msg.value | ||
); | ||
} | ||
|
||
/** | ||
* @notice Begins the delegator registration process. Locks the provided native asset in the contract as the delegated stake. | ||
* @param validationID The ID of the validation period being delegated to. | ||
*/ | ||
function initializeDelegatorRegistration(bytes32 validationID) | ||
external | ||
payable | ||
nonReentrant | ||
returns (bytes32) | ||
{ | ||
return _initializeDelegatorRegistration(validationID, _msgSender(), msg.value); | ||
} | ||
|
||
// solhint-enable ordering | ||
function _lock(uint256 value) internal virtual override returns (uint256) { | ||
return value; | ||
} | ||
|
||
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); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should add natspec contract docs here including security contact