-
Notifications
You must be signed in to change notification settings - Fork 27
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: Staking contract #453
Changes from 181 commits
55ad8a7
57256e9
7616bb5
0a4ae92
68c9eaa
d63a131
24b9c61
0834d6c
666f5d7
27e2063
e9db526
aed4ed7
de9845f
9930e65
b8f8f60
99a12a5
1e2489d
01b6bab
1c170e6
fc99b2c
c197215
fc51cdc
03b96a1
ba44522
50ce587
1ea4bfc
daa99b8
b256857
c5ccd92
d54c724
c785809
1ef6e2d
bae1c68
6decd64
fa3931d
9b9d856
eb140eb
e87babf
91fac1b
a8af00f
ea5a403
612b626
299d3a4
0dd5b6c
8be8b5e
310fbca
c6c290d
318c277
1763ca2
cd497d7
c6dd4b3
f52c04a
f45cce7
57a519e
538ea24
c4ec69c
1fc2700
4281078
62c73d2
06ef037
f26ecbe
f5d460a
794e29d
35bb886
e336fc7
a34ab4f
437d61e
16e04fc
4b61e03
4a35b8c
f502ea7
d3b8ccc
de7ab80
006de7e
0d815a0
e367998
2be8305
e0ca117
b86ec1a
731c77f
d704a40
eb844c5
6c697c3
6b1670b
9fa3ec2
06e650e
b906016
0fa897d
95b20fa
1836583
ceff4ed
2eb6d02
6372e82
4894609
99976c1
72a6417
65cf972
bb8b32c
94cc0d8
0542c82
07dd6f2
2c1a1b1
1dcb74c
b7f2677
ba9024c
58d5803
22a0a8c
0c67133
d103844
91bef5d
fd0aa94
6ba67b6
56304eb
08d95b8
6fbb9ad
f2c13d5
267d92e
dabde59
d480b4c
2ecf2bf
e29827c
38be7d9
2806198
05cb8d7
db1867d
c382766
dcadbf1
9766b4e
be54336
8096301
695aaff
57256e0
19aa136
5551db9
dac8d50
ffb7151
19f4998
4fb66c9
410fef4
766ebdf
05e2cfd
f03a1f1
38aa2a2
2fd7783
f1dcd6b
7e6cbd5
29259c5
a247eeb
436290b
15d9fbd
44d8097
9abff90
d0ba99e
2faa0af
eed92e4
9019085
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
622e6a8
a7998b8
c9b1556
bc7ddc7
9e80079
491ae79
015d92e
daad525
3286116
6b1e9c0
db44dab
49636c1
b4ea1b3
71230b9
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
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.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
// (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 {IERC20} from "@openzeppelin/[email protected]/token/ERC20/IERC20.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"; | ||
|
||
contract ERC20TokenStakingManager is | ||
Initializable, | ||
PoSValidatorManager, | ||
IERC20TokenStakingManager | ||
{ | ||
using SafeERC20 for IERC20; | ||
using SafeERC20TransferFrom for IERC20; | ||
|
||
// solhint-disable private-vars-leading-underscore | ||
/// @custom:storage-location erc7201:avalanche-icm.storage.ERC20TokenStakingManager | ||
struct ERC20TokenStakingManagerStorage { | ||
IERC20 _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 | ||
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(); | ||
} | ||
} | ||
|
||
function initialize( | ||
PoSValidatorManagerSettings calldata settings, | ||
IERC20 token | ||
) external initializer { | ||
__ERC20TokenStakingManager_init(settings, token); | ||
} | ||
|
||
// solhint-disable func-name-mixedcase | ||
function __ERC20TokenStakingManager_init( | ||
PoSValidatorManagerSettings calldata settings, | ||
IERC20 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 { | ||
ERC20TokenStakingManagerStorage storage $ = _getERC20StakingManagerStorage(); | ||
require(address(token) != address(0), "ERC20TokenStakingManager: zero token address"); | ||
$._token = token; | ||
} | ||
|
||
function initializeValidatorRegistration( | ||
uint256 stakeAmount, | ||
bytes32 nodeID, | ||
uint64 registrationExpiry, | ||
bytes memory blsPublicKey | ||
) external override returns (bytes32 validationID) { | ||
uint64 weight = _processStake(stakeAmount); | ||
return _initializeValidatorRegistration(nodeID, weight, registrationExpiry, blsPublicKey); | ||
} | ||
|
||
// 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(uint256 value, address to) internal virtual override { | ||
_getERC20StakingManagerStorage()._token.safeTransfer(to, value); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
// (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; | ||
|
||
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 startTime, | ||
uint64 endTime, | ||
uint256, // initialSupply | ||
uint256 // endSupply | ||
) external view returns (uint256) { | ||
return (stakeAmount * rewardBasisPoints * (endTime - startTime)) / SECONDS_IN_YEAR / 1000; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
// (c) 2024, Ava Labs, Inc. All rights reserved. | ||
// See the file LICENSE for licensing terms. | ||
|
||
// SPDX-License-Identifier: Ecosystem | ||
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. (nit) Confirm with a lawyer, but I think this should be |
||
|
||
pragma solidity 0.8.25; | ||
|
||
import {INativeTokenStakingManager} from "./interfaces/INativeTokenStakingManager.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"; | ||
|
||
contract NativeTokenStakingManager is | ||
Initializable, | ||
PoSValidatorManager, | ||
INativeTokenStakingManager | ||
{ | ||
using Address for address payable; | ||
|
||
constructor(ICMInitializable init) { | ||
if (init == ICMInitializable.Disallowed) { | ||
_disableInitializers(); | ||
} | ||
} | ||
|
||
// solhint-disable ordering | ||
function initialize(PoSValidatorManagerSettings calldata settings) external initializer { | ||
__NativeTokenStakingManager_init(settings); | ||
} | ||
|
||
// solhint-disable-next-line func-name-mixedcase | ||
function __NativeTokenStakingManager_init(PoSValidatorManagerSettings calldata settings) | ||
internal | ||
onlyInitializing | ||
{ | ||
__POS_Validator_Manager_init(settings); | ||
} | ||
Check warning Code scanning / Slither Conformance to Solidity naming conventions
Function NativeTokenStakingManager.__NativeTokenStakingManager_init(PoSValidatorManagerSettings) (contracts/staking/NativeTokenStakingManager.sol#35-40) is not in mixedCase
|
||
|
||
// solhint-disable-next-line func-name-mixedcase, no-empty-blocks | ||
function __NativeTokenStakingManager_init_unchained() internal onlyInitializing {} | ||
|
||
/** | ||
* @notice Begins the validator registration process. Locks the provided native asset in the contract as the stake. | ||
* @param nodeID The node ID of the validator being registered. | ||
* @param registrationExpiry The time at which the registration is no longer valid on the P-Chain. | ||
* @param signature The raw bytes of the Ed25519 signature over the concatenated bytes of | ||
* [subnetID]+[nodeID]+[blsPublicKey]+[weight]+[balance]+[expiry]. This signature must correspond to the Ed25519 | ||
* public key that is used for the nodeID. This approach prevents nodeIDs from being unwillingly added to Subnets. | ||
* balance is the minimum initial $nAVAX balance that must be attached to the validator serialized as a uint64. | ||
* The signature field will be validated by the P-Chain. Implementations may choose to validate that the signature | ||
* field is well-formed but it is not required. | ||
*/ | ||
function initializeValidatorRegistration( | ||
bytes32 nodeID, | ||
uint64 registrationExpiry, | ||
bytes memory signature | ||
) external payable returns (bytes32) { | ||
uint64 weight = _processStake(msg.value); | ||
|
||
return _initializeValidatorRegistration(nodeID, weight, registrationExpiry, signature); | ||
} | ||
|
||
// solhint-enable ordering | ||
function _lock(uint256 value) internal virtual override returns (uint256) { | ||
return value; | ||
} | ||
|
||
function _unlock(uint256 value, address to) internal virtual override { | ||
payable(to).sendValue(value); | ||
} | ||
Check warning Code scanning / Slither Dead-code
NativeTokenStakingManager._unlock(uint256,address) (contracts/staking/NativeTokenStakingManager.sol#38-40) is never used and should be removed
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
// (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 {IPoAValidatorManager} from "./interfaces/IPoAValidatorManager.sol"; | ||
import {OwnableUpgradeable} from | ||
"@openzeppelin/[email protected]/access/OwnableUpgradeable.sol"; | ||
import {ICMInitializable} from "../utilities/ICMInitializable.sol"; | ||
import {ValidatorManagerSettings} from "./interfaces/IValidatorManager.sol"; | ||
import {ValidatorManager} from "./ValidatorManager.sol"; | ||
|
||
contract PoAValidatorManager is IPoAValidatorManager, ValidatorManager, OwnableUpgradeable { | ||
constructor(ICMInitializable init) { | ||
if (init == ICMInitializable.Disallowed) { | ||
_disableInitializers(); | ||
} | ||
} | ||
|
||
function initialize( | ||
ValidatorManagerSettings calldata settings, | ||
address initialOwner | ||
) external initializer { | ||
__PoAValidatorManager_init(settings, initialOwner); | ||
} | ||
|
||
// solhint-disable func-name-mixedcase, ordering | ||
function __PoAValidatorManager_init( | ||
ValidatorManagerSettings calldata settings, | ||
address initialOwner | ||
) internal onlyInitializing { | ||
__ValidatorManager_init(settings); | ||
__Ownable_init(initialOwner); | ||
} | ||
Check warning Code scanning / Slither Conformance to Solidity naming conventions
Function PoAValidatorManager.__PoAValidatorManager_init(ValidatorManagerSettings,address) (contracts/staking/PoAValidatorManager.sol#30-36) is not in mixedCase
|
||
|
||
// solhint-disable-next-line no-empty-blocks | ||
function __PoAValidatorManager_init_unchained() internal onlyInitializing {} | ||
Check warning Code scanning / Slither Dead-code
PoAValidatorManager.__PoAValidatorManager_init_unchained() (contracts/staking/PoAValidatorManager.sol#39) is never used and should be removed
Check warning Code scanning / Slither Conformance to Solidity naming conventions
Function PoAValidatorManager.__PoAValidatorManager_init_unchained() (contracts/staking/PoAValidatorManager.sol#39) is not in mixedCase
|
||
|
||
// solhint-enable func-name-mixedcase | ||
|
||
function initializeValidatorRegistration( | ||
uint64 weight, | ||
bytes32 nodeID, | ||
uint64 registrationExpiry, | ||
bytes memory signature | ||
) external override onlyOwner returns (bytes32 validationID) { | ||
return _initializeValidatorRegistration(nodeID, weight, registrationExpiry, signature); | ||
} | ||
|
||
// solhint-enable ordering | ||
function initializeEndValidation(bytes32 validationID) external override { | ||
_initializeEndValidation(validationID); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
// (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 {IPoSValidatorManager} from "./interfaces/IPoSValidatorManager.sol"; | ||
import {PoSValidatorManagerSettings} from "./interfaces/IPoSValidatorManager.sol"; | ||
import {ValidatorManager} from "./ValidatorManager.sol"; | ||
import {WarpMessage} from | ||
"@avalabs/[email protected]/contracts/interfaces/IWarpMessenger.sol"; | ||
import {ValidatorMessages} from "./ValidatorMessages.sol"; | ||
import {IRewardCalculator} from "./interfaces/IRewardCalculator.sol"; | ||
|
||
abstract contract PoSValidatorManager is IPoSValidatorManager, ValidatorManager { | ||
// solhint-disable private-vars-leading-underscore | ||
/// @custom:storage-location erc7201:avalanche-icm.storage.PoSValidatorManager | ||
struct PoSValidatorManagerStorage { | ||
uint256 _minimumStakeAmount; | ||
uint256 _maximumStakeAmount; | ||
uint64 _minimumStakeDuration; | ||
IRewardCalculator _rewardCalculator; | ||
mapping(bytes32 validationID => uint64) _validatorUptimes; | ||
} | ||
// solhint-enable private-vars-leading-underscore | ||
|
||
// keccak256(abi.encode(uint256(keccak256("avalanche-icm.storage.PoSValidatorManager")) - 1)) & ~bytes32(uint256(0xff)); | ||
// TODO: Unit test for storage slot and update slot | ||
bytes32 private constant _POS_VALIDATOR_MANAGER_STORAGE_LOCATION = | ||
0x4317713f7ecbdddd4bc99e95d903adedaa883b2e7c2551610bd13e2c7e473d00; | ||
|
||
// solhint-disable ordering | ||
function _getPoSValidatorManagerStorage() | ||
private | ||
pure | ||
returns (PoSValidatorManagerStorage storage $) | ||
{ | ||
// solhint-disable-next-line no-inline-assembly | ||
assembly { | ||
$.slot := _POS_VALIDATOR_MANAGER_STORAGE_LOCATION | ||
} | ||
} | ||
Check warning Code scanning / Slither Assembly usage
PoSValidatorManager._getPoSValidatorManagerStorage() (contracts/staking/PoSValidatorManager.sol#34-43) uses assembly
- INLINE ASM (contracts/staking/PoSValidatorManager.sol#40-42)
|
||
|
||
// solhint-disable-next-line func-name-mixedcase | ||
function __POS_Validator_Manager_init(PoSValidatorManagerSettings calldata settings) | ||
internal | ||
onlyInitializing | ||
{ | ||
__ValidatorManager_init(settings.baseSettings); | ||
__POS_Validator_Manager_init_unchained( | ||
settings.minimumStakeAmount, | ||
settings.maximumStakeAmount, | ||
settings.minimumStakeDuration, | ||
settings.rewardCalculator | ||
); | ||
} | ||
Check warning Code scanning / Slither Conformance to Solidity naming conventions
Function PoSValidatorManager.__POS_Validator_Manager_init(PoSValidatorManagerSettings) (contracts/staking/PoSValidatorManager.sol#46-57) is not in mixedCase
|
||
|
||
// solhint-disable-next-line func-name-mixedcase | ||
function __POS_Validator_Manager_init_unchained( | ||
uint256 minimumStakeAmount, | ||
uint256 maximumStakeAmount, | ||
uint64 minimumStakeDuration, | ||
IRewardCalculator rewardCalculator | ||
) internal onlyInitializing { | ||
PoSValidatorManagerStorage storage s = _getPoSValidatorManagerStorage(); | ||
s._minimumStakeAmount = minimumStakeAmount; | ||
s._maximumStakeAmount = maximumStakeAmount; | ||
s._minimumStakeDuration = minimumStakeDuration; | ||
s._rewardCalculator = rewardCalculator; | ||
} | ||
Check warning Code scanning / Slither Conformance to Solidity naming conventions
Function PoSValidatorManager.__POS_Validator_Manager_init_unchained(uint256,uint256,uint64,IRewardCalculator) (contracts/staking/PoSValidatorManager.sol#60-71) is not in mixedCase
|
||
|
||
function initializeEndValidation( | ||
bytes32 validationID, | ||
bool includeUptimeProof, | ||
uint32 messageIndex | ||
) external { | ||
if (includeUptimeProof) { | ||
PoSValidatorManagerStorage storage $ = _getPoSValidatorManagerStorage(); | ||
(WarpMessage memory warpMessage, bool valid) = | ||
WARP_MESSENGER.getVerifiedWarpMessage(messageIndex); | ||
require(valid, "PoSValidatorManager: invalid warp message"); | ||
|
||
require( | ||
warpMessage.sourceChainID == WARP_MESSENGER.getBlockchainID(), | ||
"PoSValidatorManager: invalid source chain ID" | ||
); | ||
require( | ||
warpMessage.originSenderAddress == address(0), | ||
"PoSValidatorManager: invalid origin sender address" | ||
); | ||
|
||
(bytes32 uptimeValidationID, uint64 uptime) = | ||
ValidatorMessages.unpackValidationUptimeMessage(warpMessage.payload); | ||
require( | ||
validationID == uptimeValidationID, | ||
"PoSValidatorManager: invalid uptime validation ID" | ||
); | ||
|
||
$._validatorUptimes[validationID] = uptime; | ||
emit ValidationUptimeUpdated(validationID, uptime); | ||
} | ||
|
||
_initializeEndValidation(validationID); | ||
} | ||
|
||
function _processStake(uint256 stakeAmount) internal virtual returns (uint64) { | ||
PoSValidatorManagerStorage storage $ = _getPoSValidatorManagerStorage(); | ||
// Lock the stake in the contract. | ||
uint256 lockedValue = _lock(stakeAmount); | ||
|
||
// Ensure the stake churn doesn't exceed the maximum churn rate. | ||
uint64 weight = valueToWeight(lockedValue); | ||
// Ensure the weight is within the valid range. | ||
|
||
require( | ||
weight >= $._minimumStakeAmount && weight <= $._maximumStakeAmount, | ||
"PoSValidatorManager: invalid stake amount" | ||
); | ||
return weight; | ||
} | ||
|
||
function valueToWeight(uint256 value) public pure returns (uint64) { | ||
return uint64(value / 1e12); | ||
} | ||
|
||
function weightToValue(uint64 weight) public pure returns (uint256) { | ||
return uint256(weight) * 1e12; | ||
} | ||
|
||
function _lock(uint256 value) internal virtual returns (uint256); | ||
function _unlock(uint256 value, address to) internal virtual; | ||
} |
Check warning
Code scanning / Slither
Conformance to Solidity naming conventions