Address Contract Verified
Address
0x93d176dd54FF38b08f33b4Fc62573ec80F1da185
Balance
0 ETH
Nonce
1
Code Size
6161 bytes
Creator
0x16a9097E...A9D1 at tx 0x16221e63...38ea97
Indexed Transactions
0
Contract Bytecode
6161 bytes
0x608060405234801561000f575f5ffd5b5060043610610163575f3560e01c80638c5a07df116100c7578063ef2d78311161007d578063f5330e9611610063578063f5330e9614610370578063f84fe0a414610383578063febe3098146103aa575f5ffd5b8063ef2d78311461034a578063f2fde38b1461035d575f5ffd5b8063ac601474116100ad578063ac601474146102f0578063c12e16b514610310578063d34fec6814610337575f5ffd5b80638c5a07df146102b75780638da5cb5b146102e0575f5ffd5b80635b7f415c1161011c5780636670613f116101025780636670613f14610259578063715018a6146102805780637623ad3514610288575f5ffd5b80635b7f415c146102125780635c975abb1461022c575f5ffd5b80632dde81111161014c5780632dde8111146101b55780633a5bf240146101bd5780635a6bea7f146101d0575f5ffd5b80630a3ec230146101675780630bdf530014610171575b5f5ffd5b61016f6103b2565b005b6101987f000000000000000000000000e6bfd33f52d82ccb5b37e16d3dd81f9ffdabb19581565b6040516001600160a01b0390911681526020015b60405180910390f35b61016f610508565b61016f6101cb3660046112fc565b61051a565b6101f96101de36600461132b565b60026020525f908152604090205467ffffffffffffffff1681565b60405167ffffffffffffffff90911681526020016101ac565b61021a601281565b60405160ff90911681526020016101ac565b5f5474010000000000000000000000000000000000000000900460ff1660405190151581526020016101ac565b6101987f00000000000000000000000076589f5cb9da870ff6fca4c1c16733c58c3af22381565b61016f610605565b6102aa61029636600461132b565b60036020525f908152604090205460ff1681565b6040516101ac9190611378565b6101f96102c536600461132b565b60016020525f908152604090205467ffffffffffffffff1681565b5f546001600160a01b0316610198565b6102f8610616565b6040516001600160f81b0390911681526020016101ac565b6101987f000000000000000000000000e9230d22ccff84362fa1a11e5989314a5df04a9f81565b61016f6103453660046113e5565b610630565b61016f6103583660046112fc565b61091f565b61016f61036b36600461132b565b610b11565b61016f61037e3660046114e6565b610b67565b6101f97f0000000000000000000000000000000000000000000000000000000000093a8081565b61016f610cab565b335f81815260036020819052604090912054600291829160ff16908111156103dc576103dc611344565b14610422576001600160a01b0382165f90815260036020526040908190205490516320608a4360e01b81526104199160ff16908390600401611525565b60405180910390fd5b61042a610d27565b335f9081526001602052604081205461046e907f0000000000000000000000000000000000000000000000000000000000093a809067ffffffffffffffff16611554565b90508067ffffffffffffffff164210156104b4576040517feec1263900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b335f81815260036020818152604092839020805460ff191690921790915590519182527fbd52891d37d5590f79b40a2bb2b582dec4d50061c020f1d8aab9aa3f99dc592091015b60405180910390a1505050565b610510610d7b565b610518610dc0565b565b335f81815260036020819052604090912054600191829160ff169081111561054457610544611344565b14610581576001600160a01b0382165f90815260036020526040908190205490516320608a4360e01b81526104199160ff16908390600401611525565b610589610d27565b335f818152600160209081526040808320805467ffffffffffffffff421667ffffffffffffffff199091161790556003825291829020805460ff1916600217905581519283526001600160f81b038616908301527f539231fb10fd7e21a4f335a67a5a74be2f20522d5c8a3674a1523601c7aeb6b891016104fb565b61060d610d7b565b6105185f610e24565b6106226012600a611657565b61062d906064611665565b81565b8a6003806001600160a01b0383165f9081526003602081905260409091205460ff169081111561066257610662611344565b1461069f576001600160a01b0382165f90815260036020526040908190205490516320608a4360e01b81526104199160ff16908390600401611525565b6106a7610d27565b6001600160a01b038d165f9081526002602052604090205467ffffffffffffffff808d16911610610704576040517f90b14ad200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6107178d8d8d8d8d8d8d8d8d8d8d610e8b565b61074d576040517f8baa579f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8a60025f8f6001600160a01b03166001600160a01b031681526020019081526020015f205f6101000a81548167ffffffffffffffff021916908367ffffffffffffffff1602179055505f60015f8f6001600160a01b03166001600160a01b031681526020019081526020015f205f6101000a81548167ffffffffffffffff021916908367ffffffffffffffff1602179055505f60035f8f6001600160a01b03166001600160a01b031681526020019081526020015f205f6101000a81548160ff0219169083600381111561082357610823611344565b0217905550604080516001600160a01b038f1681526001600160f81b038e1660208201527f02a2cd4660df12ea2f3d5f5962a50282f60aa5190fc1a8651099f26d57b1cd41910160405180910390a16040517f4ac465c30000000000000000000000000000000000000000000000000000000081526001600160a01b038e811660048301526001600160f81b038e1660248301527f00000000000000000000000076589f5cb9da870ff6fca4c1c16733c58c3af2231690634ac465c3906044015f604051808303815f87803b1580156108fa575f5ffd5b505af115801561090c573d5f5f3e3d5ffd5b5050505050505050505050505050505050565b335f8181526003602081905260409091205460ff168181111561094457610944611344565b0361097b576040517f580bb5c900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b816001600160f81b03165f036109bd576040517fc40ef2ee00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6109c96012600a611657565b6109d4906064611665565b6001600160f81b0316826001600160f81b03161015610a1f576040517f57683ba900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6002335f9081526003602081905260409091205460ff1690811115610a4657610a46611344565b03610a5457610a5433611098565b335f818152600360205260409020805460ff19166001179055610acc907f000000000000000000000000e6bfd33f52d82ccb5b37e16d3dd81f9ffdabb1956001600160a01b0316907f00000000000000000000000076589f5cb9da870ff6fca4c1c16733c58c3af2236001600160f81b038616611108565b604080513381526001600160f81b03841660208201527fc921d4041dca0c6686a8306d3bc4b41f005d254ffa8d0b2d199cd0046d5392f4910160405180910390a15050565b610b19610d7b565b6001600160a01b038116610b5b576040517f1e4fbdf70000000000000000000000000000000000000000000000000000000081525f6004820152602401610419565b610b6481610e24565b50565b5f819003610ba1576040517f2539db4f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f82828281610bb257610bb26116a4565b9050602002013503610bf0576040517f87e4000000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8060015b81811015610c77578383610c096001846116b8565b818110610c1857610c186116a4565b90506020020135848483818110610c3157610c316116a4565b9050602002013511610c6f576040517f366a85a300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600101610bf4565b507f0b6b0c27d5056a277bcbc828b5133dcbcec5d108091a5e2306f499752b44bd888383336040516104fb93929190611714565b335f81815260036020819052604090912054600291829160ff1690811115610cd557610cd5611344565b14610d12576001600160a01b0382165f90815260036020526040908190205490516320608a4360e01b81526104199160ff16908390600401611525565b610d1a610d27565b610d2333611098565b5050565b5f5474010000000000000000000000000000000000000000900460ff1615610518576040517fd93c066500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f546001600160a01b03163314610518576040517f118cdaa7000000000000000000000000000000000000000000000000000000008152336004820152602401610419565b610dc8611196565b5f80547fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff1690556040805133815290517f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa9181900360200190a1565b5f80546001600160a01b038381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b604080516001600160a01b038d16602082015260ff1960088d901b169181019190915246605f8201527fffffffffffffffffffffffffffffffffffffffff0000000000000000000000003060601b16607f8201525f90819060930160408051601f19818403018152828252805160209182012090830152016040516020818303038152906040528051906020012090505f610f598b8b808060200260200160405190810160405280939291908181526020018383602002808284375f920191909152508692506111e9915050565b6040517f19457468657265756d205369676e6564204d6573736167653a0a3430000000006020820152603c81018290527fffffffffffffffff00000000000000000000000000000000000000000000000060c08f901b16605c8201529091505f9060640160408051601f198184030181529082905280516020909101207f663596d100000000000000000000000000000000000000000000000000000000825291506001600160a01b037f000000000000000000000000e9230d22ccff84362fa1a11e5989314a5df04a9f169063663596d1906110469084908e908e908e908e908e908e90600401611740565b602060405180830381865afa158015611061573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061108591906117bc565b9f9e505050505050505050505050505050565b6001600160a01b0381165f818152600160208181526040808420805467ffffffffffffffff191690556003825292839020805460ff191690921790915590519182527f5f197e6a21b96261cfbae08b5e513b55ce707b3ab3f64214f5705eda87e2d010910160405180910390a150565b604080516001600160a01b0385811660248301528416604482015260648082018490528251808303909101815260849091019091526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f23b872dd0000000000000000000000000000000000000000000000000000000017905261119090859061122d565b50505050565b5f5474010000000000000000000000000000000000000000900460ff16610518576040517f8dfc202b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f81815b8451811015611223576112198286838151811061120c5761120c6116a4565b60200260200101516112b2565b91506001016111ed565b5090505b92915050565b5f5f60205f8451602086015f885af18061124c576040513d5f823e3d81fd5b50505f513d91508115611263578060011415611270565b6001600160a01b0384163b155b15611190576040517f5274afe70000000000000000000000000000000000000000000000000000000081526001600160a01b0385166004820152602401610419565b5f8183106112cc575f8281526020849052604090206112da565b5f8381526020839052604090205b9392505050565b80356001600160f81b03811681146112f7575f5ffd5b919050565b5f6020828403121561130c575f5ffd5b6112da826112e1565b80356001600160a01b03811681146112f7575f5ffd5b5f6020828403121561133b575f5ffd5b6112da82611315565b634e487b7160e01b5f52602160045260245ffd5b6004811061137457634e487b7160e01b5f52602160045260245ffd5b9052565b602081016112278284611358565b803567ffffffffffffffff811681146112f7575f5ffd5b5f5f83601f8401126113ad575f5ffd5b50813567ffffffffffffffff8111156113c4575f5ffd5b6020830191508360208260051b85010111156113de575f5ffd5b9250929050565b5f5f5f5f5f5f5f5f5f5f5f60e08c8e0312156113ff575f5ffd5b6114088c611315565b9a5061141660208d016112e1565b995061142460408d01611386565b985060608c013567ffffffffffffffff81111561143f575f5ffd5b61144b8e828f0161139d565b90995097505060808c013567ffffffffffffffff81111561146a575f5ffd5b6114768e828f0161139d565b90975095505060a08c013567ffffffffffffffff811115611495575f5ffd5b6114a18e828f0161139d565b90955093505060c08c013567ffffffffffffffff8111156114c0575f5ffd5b6114cc8e828f0161139d565b915080935050809150509295989b509295989b9093969950565b5f5f602083850312156114f7575f5ffd5b823567ffffffffffffffff81111561150d575f5ffd5b6115198582860161139d565b90969095509350505050565b604081016115338285611358565b6112da6020830184611358565b634e487b7160e01b5f52601160045260245ffd5b67ffffffffffffffff818116838216019081111561122757611227611540565b6001815b60018411156115af5780850481111561159357611593611540565b60018416156115a157908102905b60019390931c928002611578565b935093915050565b5f826115c557506001611227565b816115d157505f611227565b81600181146115e757600281146115f15761160d565b6001915050611227565b60ff84111561160257611602611540565b50506001821b611227565b5060208310610133831016604e8410600b8410161715611630575081810a611227565b61163c5f198484611574565b805f190482111561164f5761164f611540565b029392505050565b5f6112da60ff8416836115b7565b5f6001600160f81b0382166001600160f81b0384166001600160f81b03818302169250818304811482151761169c5761169c611540565b505092915050565b634e487b7160e01b5f52603260045260245ffd5b8181038181111561122757611227611540565b8183525f7f07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8311156116fb575f5ffd5b8260051b80836020870137939093016020019392505050565b604081525f6117276040830185876116cb565b90506001600160a01b0383166020830152949350505050565b878152608060208201525f61175960808301888a6116cb565b828103604084015261176c8187896116cb565b83810360608501528481528591506020015f5b858110156117ad57823560ff8116808214611798575f5ffd5b8352506020928301929091019060010161177f565b509a9950505050505050505050565b5f602082840312156117cc575f5ffd5b815180151581146112da575f5ffdfea264697066735822122064f37e5ba936a35195a12146cff9b37755101f464b27d8ceaf3f9c97231b418e64736f6c634300081c0033
Verified Source Code Full Match
Compiler: v0.8.28+commit.7893614a
EVM: cancun
Optimization: Yes (2000 runs)
Staking.sol 231 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IStaking} from "./interfaces/IStaking.sol";
import {MerkleProof} from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
import {ISubstrateSignatureValidator} from "./interfaces/ISubstrateSignatureValidator.sol";
import {IStakingPool} from "./interfaces/IStakingPool.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {Pausable} from "@openzeppelin/contracts/utils/Pausable.sol";
contract Staking is IStaking, Ownable, Pausable {
using SafeERC20 for IERC20;
/// @notice token address is the zero address
error ZeroAddress();
/// @notice nodes list is empty
error EmptyNodesList();
/// @notice amount is zero
error StakingZeroAmount();
/// @notice node Ed25519 public key is invalid
error InvalidNodeEd25519PubKey();
/// @notice node Ed25519 public key is duplicate
error DuplicateNodeEd25519PubKey();
/// @notice user has already initiated an unstake
error UnstakeAlreadyInitiated();
/// @notice unstake initiation has not met the unbonding period
error UnstakeNotUnbonded();
/// @notice user has no unstake initiation
error InitiateUnstakeNotFound();
/// @notice unbonding period is zero
error UnbondingPeriodZero();
/// @notice amount is below the minimum staking amount
error StakingBelowMinAmount();
/// @notice signature is invalid
error InvalidSignature();
/// @notice sxt block number is invalid
error InvalidSxtBlockNumber();
/// @notice staker is in an invalid state for the requested operation
error InvalidStakerState(StakerState current, StakerState required);
/// @notice staker has claimed unstake and is waiting for fulfillment
error PendingUnstakeFulfillment();
/// The number of decimals for the token
uint8 public constant TOKEN_DECIMALS = 18;
/// @notice The minimum amount of tokens that can be staked
uint248 public constant MIN_STAKING_AMOUNT = 100 * uint248(10 ** TOKEN_DECIMALS);
/// @notice The address of the token to stake
address public immutable TOKEN_ADDRESS;
/// @notice The address of the staking pool
address public immutable STAKING_POOL_ADDRESS;
/// @notice The unstaking unbonding period in seconds
uint64 public immutable UNSTAKING_UNBONDING_PERIOD;
/// @notice The address of the SubstrateSignatureValidator contract
address public immutable SUBSTRATE_SIGNATURE_VALIDATOR_ADDRESS;
/// @notice The unstake requests timestamp
mapping(address => uint64) public initiateUnstakeRequestsTimestamp;
/// @notice The latest sxtBlock unstake fulfillment by that staker
mapping(address => uint64) public latestSxtBlockFulfillmentByStaker;
/// @notice The staker state
mapping(address => StakerState) public stakerState;
/**
* @dev Modifier to validate a staker is in a specific state
* @param staker The address of the staker to check
* @param requiredState The state the staker must be in
*/
modifier requireState(address staker, StakerState requiredState) {
if (stakerState[staker] != requiredState) {
revert InvalidStakerState(stakerState[staker], requiredState);
}
_;
}
/**
* @dev Modifier to validate a staker is not in the UnstakeClaimed state
* @param staker The address of the staker to check
*/
modifier requireStateNotUnstakeClaimed(address staker) {
if (stakerState[staker] == StakerState.UnstakeClaimed) {
revert PendingUnstakeFulfillment();
}
_;
}
constructor(
address tokenAddress,
address stakingPoolAddress,
uint64 unstakingUnbondingPeriod,
address substrateSignatureValidatorAddress
) Ownable(msg.sender) {
if (tokenAddress == address(0)) revert ZeroAddress();
if (stakingPoolAddress == address(0)) revert ZeroAddress();
if (unstakingUnbondingPeriod == 0) revert UnbondingPeriodZero();
if (substrateSignatureValidatorAddress == address(0)) revert ZeroAddress();
TOKEN_ADDRESS = tokenAddress;
emit StakingTokenSet(tokenAddress);
STAKING_POOL_ADDRESS = stakingPoolAddress;
emit StakingPoolSet(stakingPoolAddress);
UNSTAKING_UNBONDING_PERIOD = unstakingUnbondingPeriod;
emit UnstakingUnbondingPeriodSet(unstakingUnbondingPeriod);
SUBSTRATE_SIGNATURE_VALIDATOR_ADDRESS = substrateSignatureValidatorAddress;
emit SubstrateSignatureValidatorSet(substrateSignatureValidatorAddress);
_pause();
}
// @inheritdoc IStaking
function stake(uint248 amount) external requireStateNotUnstakeClaimed(msg.sender) {
if (amount == 0) revert StakingZeroAmount();
if (amount < MIN_STAKING_AMOUNT) revert StakingBelowMinAmount();
// If the staker has already initiated an unstake, cancel it
if (stakerState[msg.sender] == StakerState.UnstakeInitiated) {
_cancelInitiateUnstake(msg.sender);
}
// Update state before external call (following checks-effects-interactions pattern)
stakerState[msg.sender] = StakerState.Staked;
// Transfer tokens directly from user to the staking pool
IERC20(TOKEN_ADDRESS).safeTransferFrom(msg.sender, STAKING_POOL_ADDRESS, amount);
// Emit event after all operations are complete
emit Staked(msg.sender, amount);
}
// @inheritdoc IStaking
function nominate(bytes32[] calldata nodesEd25519PubKeys) external {
if (nodesEd25519PubKeys.length == 0) revert EmptyNodesList();
if (nodesEd25519PubKeys[0] == bytes32(0)) revert InvalidNodeEd25519PubKey();
uint256 nodesEd25519PubKeysLength = nodesEd25519PubKeys.length;
for (uint256 i = 1; i < nodesEd25519PubKeysLength; ++i) {
// solhint-disable-next-line gas-strict-inequalities
if (nodesEd25519PubKeys[i] <= nodesEd25519PubKeys[i - 1]) {
revert DuplicateNodeEd25519PubKey();
}
}
emit Nominated(nodesEd25519PubKeys, msg.sender);
}
// @inheritdoc IStaking
function initiateUnstake(uint248 amount) external requireState(msg.sender, StakerState.Staked) whenNotPaused {
initiateUnstakeRequestsTimestamp[msg.sender] = uint64(block.timestamp);
stakerState[msg.sender] = StakerState.UnstakeInitiated;
emit UnstakeInitiated(msg.sender, amount);
}
function _cancelInitiateUnstake(address user) internal {
initiateUnstakeRequestsTimestamp[user] = 0;
stakerState[user] = StakerState.Staked;
emit InitiateUnstakeCancelled(user);
}
// @inheritdoc IStaking
function cancelInitiateUnstake() external requireState(msg.sender, StakerState.UnstakeInitiated) whenNotPaused {
_cancelInitiateUnstake(msg.sender);
}
// @inheritdoc IStaking
function claimUnstake() external requireState(msg.sender, StakerState.UnstakeInitiated) whenNotPaused {
uint64 earliestPossibleClaimUnstakeRequest =
initiateUnstakeRequestsTimestamp[msg.sender] + UNSTAKING_UNBONDING_PERIOD;
// slither-disable-next-line timestamp
if (block.timestamp < earliestPossibleClaimUnstakeRequest) revert UnstakeNotUnbonded();
stakerState[msg.sender] = StakerState.UnstakeClaimed;
emit UnstakeClaimed(msg.sender);
}
function _validateSxtFulfillUnstake(
address staker,
uint248 amount,
uint64 sxtBlockNumber,
bytes32[] calldata proof,
bytes32[] calldata r,
bytes32[] calldata s,
uint8[] calldata v
) internal view returns (bool isValid) {
bytes32 leaf = keccak256(
bytes.concat(keccak256(abi.encodePacked(uint256(uint160(staker)), amount, block.chainid, address(this))))
);
bytes32 rootHash = MerkleProof.processProof(proof, leaf);
bytes32 messageHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n40", rootHash, sxtBlockNumber));
isValid =
ISubstrateSignatureValidator(SUBSTRATE_SIGNATURE_VALIDATOR_ADDRESS).validateMessage(messageHash, r, s, v);
}
// @inheritdoc IStaking
function sxtFulfillUnstake(
address staker,
uint248 amount,
uint64 sxtBlockNumber,
bytes32[] calldata proof,
bytes32[] calldata r,
bytes32[] calldata s,
uint8[] calldata v
) external requireState(staker, StakerState.UnstakeClaimed) whenNotPaused {
// solhint-disable-next-line gas-strict-inequalities
if (latestSxtBlockFulfillmentByStaker[staker] >= sxtBlockNumber) revert InvalidSxtBlockNumber();
// Validate signature
if (!_validateSxtFulfillUnstake(staker, amount, sxtBlockNumber, proof, r, s, v)) revert InvalidSignature();
// State changes first (Effects)
latestSxtBlockFulfillmentByStaker[staker] = sxtBlockNumber;
initiateUnstakeRequestsTimestamp[staker] = 0;
stakerState[staker] = StakerState.Unstaked;
// Event emission before external calls
emit Unstaked(staker, amount);
// External interactions last - direct withdraw to staker
IStakingPool(STAKING_POOL_ADDRESS).withdraw(staker, amount);
}
function unpauseUnstaking() external onlyOwner {
_unpause();
}
}
SafeERC20.sol 198 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.2.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
import {IERC1363} from "../../../interfaces/IERC1363.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC-20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
/**
* @dev An operation with an ERC-20 token failed.
*/
error SafeERC20FailedOperation(address token);
/**
* @dev Indicates a failed `decreaseAllowance` request.
*/
error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*
* IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
* smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
* this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
* that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
*/
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
forceApprove(token, spender, oldAllowance + value);
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
* value, non-reverting calls are assumed to be successful.
*
* IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
* smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
* this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
* that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
*/
function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
unchecked {
uint256 currentAllowance = token.allowance(address(this), spender);
if (currentAllowance < requestedDecrease) {
revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
}
forceApprove(token, spender, currentAllowance - requestedDecrease);
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
* to be set to zero before setting it to a non-zero value, such as USDT.
*
* NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function
* only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being
* set here.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no
* code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* Reverts if the returned value is other than `true`.
*/
function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
if (to.code.length == 0) {
safeTransfer(token, to, value);
} else if (!token.transferAndCall(to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target
* has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* Reverts if the returned value is other than `true`.
*/
function transferFromAndCallRelaxed(
IERC1363 token,
address from,
address to,
uint256 value,
bytes memory data
) internal {
if (to.code.length == 0) {
safeTransferFrom(token, from, to, value);
} else if (!token.transferFromAndCall(from, to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no
* code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.
* Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}
* once without retrying, and relies on the returned value to be true.
*
* Reverts if the returned value is other than `true`.
*/
function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
if (to.code.length == 0) {
forceApprove(token, to, value);
} else if (!token.approveAndCall(to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements.
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
uint256 returnSize;
uint256 returnValue;
assembly ("memory-safe") {
let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
// bubble errors
if iszero(success) {
let ptr := mload(0x40)
returndatacopy(ptr, 0, returndatasize())
revert(ptr, returndatasize())
}
returnSize := returndatasize()
returnValue := mload(0)
}
if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
bool success;
uint256 returnSize;
uint256 returnValue;
assembly ("memory-safe") {
success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
returnSize := returndatasize()
returnValue := mload(0)
}
return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);
}
}
IERC20.sol 79 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC-20 standard as defined in the ERC.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 value) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 value) external returns (bool);
}
IStaking.sol 119 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;
interface IStaking {
// ** enum ** //
/// @notice The state of a staker in the state machine
enum StakerState {
Unstaked, // Initial state, not staked
Staked, // Tokens staked
UnstakeInitiated, // Unstake process has been initiated
UnstakeClaimed // Unstake claimed and ready for fulfillment
}
// ** events ** //
/// @notice Emitted when the staking token is set
/// @param token The new staking token
/// @dev this event is emitted only by the constructor
event StakingTokenSet(address token);
/// @notice Emitted when the staking pool is set
/// @param stakingPool The new staking pool
/// @dev this event is emitted only by the constructor
event StakingPoolSet(address stakingPool);
/// @notice Emitted when the unstaking unbonding period is set
/// @param unstakingUnbondingPeriod The new unstaking unbonding period
/// @dev this event is emitted only by the constructor
event UnstakingUnbondingPeriodSet(uint64 unstakingUnbondingPeriod);
/// @notice Emitted when a user stakes tokens for a set of nodes
/// @param staker The address of the user who staked the tokens
/// @param amount The amount of tokens staked
event Staked(address staker, uint248 amount);
/// @notice Emitted when a user nominates a set of nodes
/// @param nodesEd25519PubKeys nodes Ed25519 public keys to nominate, the amount of staked tokens will be evenly distributed to the nodes
/// @param nominator The address of the user who nominated the nodes
event Nominated(bytes32[] nodesEd25519PubKeys, address nominator);
/// @notice Emitted when a user initiates an unstake
/// @param staker The address of the user who initiated the unstake
event UnstakeInitiated(address staker, uint248 amount);
/// @notice Emitted when a user cancels an unstake
/// @param staker The address of the user who cancelled the unstake
event InitiateUnstakeCancelled(address staker);
/// @notice Emitted when a user's unstake request is processed
/// @param staker The address of the user who had their unstake request processed
event UnstakeClaimed(address staker);
/// @notice Emitted when the SubstrateSignatureValidator address is set
/// @param substrateSignatureValidator The new SubstrateSignatureValidator address
/// @dev this event is emitted only by the constructor
event SubstrateSignatureValidatorSet(address substrateSignatureValidator);
/// @notice Emitted when the unstake is completed and tokens are transferred to the staker
/// @param staker The address of the user who had their unstake request processed
/// @param amount The amount of tokens unstaked
event Unstaked(address staker, uint248 amount);
// ** functions ** //
/// @notice Stake tokens by msg.sender
/// @param amount The amount of tokens to stake
/// @dev the staking balance will be distributed to the nodes evenly
/// @dev requires:
/// 1 - amount is > 100 wei of SXT token.
/// 2 - user already UnstakeInitiated.
function stake(uint248 amount) external;
/// @notice Nominate a set of nodes
/// @param nodesEd25519PubKeys nodes Ed25519 public keys to nominate, the amount of staked tokens will be evenly distributed to the nodes
/// @dev the list of nodesEd25519PubKeys must be sorted in ascending order and unique
function nominate(bytes32[] calldata nodesEd25519PubKeys) external;
/// @notice Initiate an unstake request, the staker will not receive any rewards during the unbonding period
/// @dev can be called only if user has not already initiated an unstake
/// @custom:events
/// * UnstakeInitiated
function initiateUnstake(uint248 amount) external;
/// @notice Cancel an unstake request
/// @dev can only be called if the unstake request has not been processed
/// @custom:events
/// * InitiateUnstakeCancelled
function cancelInitiateUnstake() external;
/// @notice Request to process an unstake, this will be picked up by the SXT Chain which will fulfill the unstake
/// @custom:events
/// * UnstakeClaimed
function claimUnstake() external;
/// @notice Callback by the SXT Chain to fulfill an unstake request
/// @param staker The staker of the unstake request
/// @param amount The amount of tokens to unstake
/// @param sxtBlockNumber The SXT Chain block number when the unstake was processed
/// @param proof list of proof nodes
/// @param r list of r values
/// @param s list of s values
/// @param v list of v values
/// @dev the leaf consists of <staker, amount>, then we derive the root hash from the proofs and leaf.
/// @dev the message hash is derived from the root hash, the sxt block number and the chain id.
/// @dev the signature is validated by the SubstrateSignatureValidator contract against the attestors list and threshold.
/// @dev attestors should be unique and sorted in ascending order.
function sxtFulfillUnstake(
address staker,
uint248 amount,
uint64 sxtBlockNumber,
bytes32[] calldata proof,
bytes32[] calldata r,
bytes32[] calldata s,
uint8[] calldata v
) external;
/// @notice Unpause unstaking
/// @dev can only be called by the owner
function unpauseUnstaking() external;
}
MerkleProof.sol 514 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/cryptography/MerkleProof.sol)
// This file was procedurally generated from scripts/generate/templates/MerkleProof.js.
pragma solidity ^0.8.20;
import {Hashes} from "./Hashes.sol";
/**
* @dev These functions deal with verification of Merkle Tree proofs.
*
* The tree and the proofs can be generated using our
* https://github.com/OpenZeppelin/merkle-tree[JavaScript library].
* You will find a quickstart guide in the readme.
*
* WARNING: You should avoid using leaf values that are 64 bytes long prior to
* hashing, or use a hash function other than keccak256 for hashing leaves.
* This is because the concatenation of a sorted pair of internal nodes in
* the Merkle tree could be reinterpreted as a leaf value.
* OpenZeppelin's JavaScript library generates Merkle trees that are safe
* against this attack out of the box.
*
* IMPORTANT: Consider memory side-effects when using custom hashing functions
* that access memory in an unsafe way.
*
* NOTE: This library supports proof verification for merkle trees built using
* custom _commutative_ hashing functions (i.e. `H(a, b) == H(b, a)`). Proving
* leaf inclusion in trees built using non-commutative hashing functions requires
* additional logic that is not supported by this library.
*/
library MerkleProof {
/**
*@dev The multiproof provided is not valid.
*/
error MerkleProofInvalidMultiproof();
/**
* @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
* defined by `root`. For this, a `proof` must be provided, containing
* sibling hashes on the branch from the leaf to the root of the tree. Each
* pair of leaves and each pair of pre-images are assumed to be sorted.
*
* This version handles proofs in memory with the default hashing function.
*/
function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
return processProof(proof, leaf) == root;
}
/**
* @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
* from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
* hash matches the root of the tree. When processing the proof, the pairs
* of leaves & pre-images are assumed to be sorted.
*
* This version handles proofs in memory with the default hashing function.
*/
function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = Hashes.commutativeKeccak256(computedHash, proof[i]);
}
return computedHash;
}
/**
* @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
* defined by `root`. For this, a `proof` must be provided, containing
* sibling hashes on the branch from the leaf to the root of the tree. Each
* pair of leaves and each pair of pre-images are assumed to be sorted.
*
* This version handles proofs in memory with a custom hashing function.
*/
function verify(
bytes32[] memory proof,
bytes32 root,
bytes32 leaf,
function(bytes32, bytes32) view returns (bytes32) hasher
) internal view returns (bool) {
return processProof(proof, leaf, hasher) == root;
}
/**
* @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
* from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
* hash matches the root of the tree. When processing the proof, the pairs
* of leaves & pre-images are assumed to be sorted.
*
* This version handles proofs in memory with a custom hashing function.
*/
function processProof(
bytes32[] memory proof,
bytes32 leaf,
function(bytes32, bytes32) view returns (bytes32) hasher
) internal view returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = hasher(computedHash, proof[i]);
}
return computedHash;
}
/**
* @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
* defined by `root`. For this, a `proof` must be provided, containing
* sibling hashes on the branch from the leaf to the root of the tree. Each
* pair of leaves and each pair of pre-images are assumed to be sorted.
*
* This version handles proofs in calldata with the default hashing function.
*/
function verifyCalldata(bytes32[] calldata proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
return processProofCalldata(proof, leaf) == root;
}
/**
* @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
* from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
* hash matches the root of the tree. When processing the proof, the pairs
* of leaves & pre-images are assumed to be sorted.
*
* This version handles proofs in calldata with the default hashing function.
*/
function processProofCalldata(bytes32[] calldata proof, bytes32 leaf) internal pure returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = Hashes.commutativeKeccak256(computedHash, proof[i]);
}
return computedHash;
}
/**
* @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
* defined by `root`. For this, a `proof` must be provided, containing
* sibling hashes on the branch from the leaf to the root of the tree. Each
* pair of leaves and each pair of pre-images are assumed to be sorted.
*
* This version handles proofs in calldata with a custom hashing function.
*/
function verifyCalldata(
bytes32[] calldata proof,
bytes32 root,
bytes32 leaf,
function(bytes32, bytes32) view returns (bytes32) hasher
) internal view returns (bool) {
return processProofCalldata(proof, leaf, hasher) == root;
}
/**
* @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
* from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
* hash matches the root of the tree. When processing the proof, the pairs
* of leaves & pre-images are assumed to be sorted.
*
* This version handles proofs in calldata with a custom hashing function.
*/
function processProofCalldata(
bytes32[] calldata proof,
bytes32 leaf,
function(bytes32, bytes32) view returns (bytes32) hasher
) internal view returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = hasher(computedHash, proof[i]);
}
return computedHash;
}
/**
* @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by
* `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
*
* This version handles multiproofs in memory with the default hashing function.
*
* CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
*
* NOTE: Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`.
* The `leaves` must be validated independently. See {processMultiProof}.
*/
function multiProofVerify(
bytes32[] memory proof,
bool[] memory proofFlags,
bytes32 root,
bytes32[] memory leaves
) internal pure returns (bool) {
return processMultiProof(proof, proofFlags, leaves) == root;
}
/**
* @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
* proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
* leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
* respectively.
*
* This version handles multiproofs in memory with the default hashing function.
*
* CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
* is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
* tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
*
* NOTE: The _empty set_ (i.e. the case where `proof.length == 1 && leaves.length == 0`) is considered a no-op,
* and therefore a valid multiproof (i.e. it returns `proof[0]`). Consider disallowing this case if you're not
* validating the leaves elsewhere.
*/
function processMultiProof(
bytes32[] memory proof,
bool[] memory proofFlags,
bytes32[] memory leaves
) internal pure returns (bytes32 merkleRoot) {
// This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
// the Merkle tree.
uint256 leavesLen = leaves.length;
uint256 proofFlagsLen = proofFlags.length;
// Check proof validity.
if (leavesLen + proof.length != proofFlagsLen + 1) {
revert MerkleProofInvalidMultiproof();
}
// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
bytes32[] memory hashes = new bytes32[](proofFlagsLen);
uint256 leafPos = 0;
uint256 hashPos = 0;
uint256 proofPos = 0;
// At each step, we compute the next hash using two values:
// - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
// get the next hash.
// - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
// `proof` array.
for (uint256 i = 0; i < proofFlagsLen; i++) {
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
bytes32 b = proofFlags[i]
? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
: proof[proofPos++];
hashes[i] = Hashes.commutativeKeccak256(a, b);
}
if (proofFlagsLen > 0) {
if (proofPos != proof.length) {
revert MerkleProofInvalidMultiproof();
}
unchecked {
return hashes[proofFlagsLen - 1];
}
} else if (leavesLen > 0) {
return leaves[0];
} else {
return proof[0];
}
}
/**
* @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by
* `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
*
* This version handles multiproofs in memory with a custom hashing function.
*
* CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
*
* NOTE: Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`.
* The `leaves` must be validated independently. See {processMultiProof}.
*/
function multiProofVerify(
bytes32[] memory proof,
bool[] memory proofFlags,
bytes32 root,
bytes32[] memory leaves,
function(bytes32, bytes32) view returns (bytes32) hasher
) internal view returns (bool) {
return processMultiProof(proof, proofFlags, leaves, hasher) == root;
}
/**
* @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
* proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
* leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
* respectively.
*
* This version handles multiproofs in memory with a custom hashing function.
*
* CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
* is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
* tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
*
* NOTE: The _empty set_ (i.e. the case where `proof.length == 1 && leaves.length == 0`) is considered a no-op,
* and therefore a valid multiproof (i.e. it returns `proof[0]`). Consider disallowing this case if you're not
* validating the leaves elsewhere.
*/
function processMultiProof(
bytes32[] memory proof,
bool[] memory proofFlags,
bytes32[] memory leaves,
function(bytes32, bytes32) view returns (bytes32) hasher
) internal view returns (bytes32 merkleRoot) {
// This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
// the Merkle tree.
uint256 leavesLen = leaves.length;
uint256 proofFlagsLen = proofFlags.length;
// Check proof validity.
if (leavesLen + proof.length != proofFlagsLen + 1) {
revert MerkleProofInvalidMultiproof();
}
// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
bytes32[] memory hashes = new bytes32[](proofFlagsLen);
uint256 leafPos = 0;
uint256 hashPos = 0;
uint256 proofPos = 0;
// At each step, we compute the next hash using two values:
// - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
// get the next hash.
// - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
// `proof` array.
for (uint256 i = 0; i < proofFlagsLen; i++) {
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
bytes32 b = proofFlags[i]
? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
: proof[proofPos++];
hashes[i] = hasher(a, b);
}
if (proofFlagsLen > 0) {
if (proofPos != proof.length) {
revert MerkleProofInvalidMultiproof();
}
unchecked {
return hashes[proofFlagsLen - 1];
}
} else if (leavesLen > 0) {
return leaves[0];
} else {
return proof[0];
}
}
/**
* @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by
* `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
*
* This version handles multiproofs in calldata with the default hashing function.
*
* CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
*
* NOTE: Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`.
* The `leaves` must be validated independently. See {processMultiProofCalldata}.
*/
function multiProofVerifyCalldata(
bytes32[] calldata proof,
bool[] calldata proofFlags,
bytes32 root,
bytes32[] memory leaves
) internal pure returns (bool) {
return processMultiProofCalldata(proof, proofFlags, leaves) == root;
}
/**
* @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
* proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
* leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
* respectively.
*
* This version handles multiproofs in calldata with the default hashing function.
*
* CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
* is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
* tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
*
* NOTE: The _empty set_ (i.e. the case where `proof.length == 1 && leaves.length == 0`) is considered a no-op,
* and therefore a valid multiproof (i.e. it returns `proof[0]`). Consider disallowing this case if you're not
* validating the leaves elsewhere.
*/
function processMultiProofCalldata(
bytes32[] calldata proof,
bool[] calldata proofFlags,
bytes32[] memory leaves
) internal pure returns (bytes32 merkleRoot) {
// This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
// the Merkle tree.
uint256 leavesLen = leaves.length;
uint256 proofFlagsLen = proofFlags.length;
// Check proof validity.
if (leavesLen + proof.length != proofFlagsLen + 1) {
revert MerkleProofInvalidMultiproof();
}
// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
bytes32[] memory hashes = new bytes32[](proofFlagsLen);
uint256 leafPos = 0;
uint256 hashPos = 0;
uint256 proofPos = 0;
// At each step, we compute the next hash using two values:
// - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
// get the next hash.
// - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
// `proof` array.
for (uint256 i = 0; i < proofFlagsLen; i++) {
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
bytes32 b = proofFlags[i]
? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
: proof[proofPos++];
hashes[i] = Hashes.commutativeKeccak256(a, b);
}
if (proofFlagsLen > 0) {
if (proofPos != proof.length) {
revert MerkleProofInvalidMultiproof();
}
unchecked {
return hashes[proofFlagsLen - 1];
}
} else if (leavesLen > 0) {
return leaves[0];
} else {
return proof[0];
}
}
/**
* @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by
* `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
*
* This version handles multiproofs in calldata with a custom hashing function.
*
* CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
*
* NOTE: Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`.
* The `leaves` must be validated independently. See {processMultiProofCalldata}.
*/
function multiProofVerifyCalldata(
bytes32[] calldata proof,
bool[] calldata proofFlags,
bytes32 root,
bytes32[] memory leaves,
function(bytes32, bytes32) view returns (bytes32) hasher
) internal view returns (bool) {
return processMultiProofCalldata(proof, proofFlags, leaves, hasher) == root;
}
/**
* @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
* proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
* leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
* respectively.
*
* This version handles multiproofs in calldata with a custom hashing function.
*
* CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
* is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
* tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
*
* NOTE: The _empty set_ (i.e. the case where `proof.length == 1 && leaves.length == 0`) is considered a no-op,
* and therefore a valid multiproof (i.e. it returns `proof[0]`). Consider disallowing this case if you're not
* validating the leaves elsewhere.
*/
function processMultiProofCalldata(
bytes32[] calldata proof,
bool[] calldata proofFlags,
bytes32[] memory leaves,
function(bytes32, bytes32) view returns (bytes32) hasher
) internal view returns (bytes32 merkleRoot) {
// This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
// the Merkle tree.
uint256 leavesLen = leaves.length;
uint256 proofFlagsLen = proofFlags.length;
// Check proof validity.
if (leavesLen + proof.length != proofFlagsLen + 1) {
revert MerkleProofInvalidMultiproof();
}
// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
bytes32[] memory hashes = new bytes32[](proofFlagsLen);
uint256 leafPos = 0;
uint256 hashPos = 0;
uint256 proofPos = 0;
// At each step, we compute the next hash using two values:
// - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
// get the next hash.
// - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
// `proof` array.
for (uint256 i = 0; i < proofFlagsLen; i++) {
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
bytes32 b = proofFlags[i]
? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
: proof[proofPos++];
hashes[i] = hasher(a, b);
}
if (proofFlagsLen > 0) {
if (proofPos != proof.length) {
revert MerkleProofInvalidMultiproof();
}
unchecked {
return hashes[proofFlagsLen - 1];
}
} else if (leavesLen > 0) {
return leaves[0];
} else {
return proof[0];
}
}
}
ISubstrateSignatureValidator.sol 50 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;
/// @title ISubstrateSignatureValidator
/// @notice Interface for the SubstrateSignatureValidator contract
/// @notice This contract is used to validate messages signed by the substrate SXT Chain attestors.
interface ISubstrateSignatureValidator {
/* ********** events ********** */
/// @notice Emitted when the attestors are updated
/// @param attestors The addresses of the attestors
event AttestorsUpdated(address[] attestors);
/// @notice Emitted when the threshold is updated
/// @param threshold The threshold
event ThresholdUpdated(uint16 threshold);
/* ********** functions ********** */
/// @notice Get the attestors
/// @return attestors The addresses of the attestors
function getAttestors() external view returns (address[] memory attestors);
/// @notice Check if an address is an attestor
/// @param attestor The address to check
/// @return result True if the address is an attestor, false otherwise
function isAttestor(address attestor) external view returns (bool result);
/// @notice Get the threshold
/// @return threshold The threshold
function getThreshold() external view returns (uint16 threshold);
/// @notice Atomic method to update both attestors and threshold
/// @param attestors The addresses of the attestors
/// @param threshold The threshold
/// @dev this function can only be called by the owner [Multisig Safe]
/// @dev the attestors addresses should be unique and sorted in ascending order
function updateAttestorsAndThreshold(address[] calldata attestors, uint16 threshold) external;
/// @notice Validate a message signed by the substrate SXT Chain attestors
/// @param message The message to validate
/// @param r The r values of the signatures
/// @param s The s values of the signatures
/// @param v The v values of the signatures
/// @dev the signed attestors should be ordered in ascending order
function validateMessage(bytes32 message, bytes32[] calldata r, bytes32[] calldata s, uint8[] calldata v)
external
view
returns (bool result);
}
IStakingPool.sol 45 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;
/// @title IStakingPool
/// @notice Interface for the StakingPool contract that will hold SXT tokens.
interface IStakingPool {
/* ********** events ********** */
/// @notice Emitted when the staking token is set
/// @param token The new staking token
/// @dev this event is emitted only by the constructor
event StakingTokenSet(address token);
/// @notice Emitted when the staking contract is added
/// @param stakingContractAddress The new staking contract address
event StakingContractAdded(address stakingContractAddress);
/// @notice Emitted when the staking contract is removed
/// @param stakingContractAddress The staking contract address to remove
event StakingContractRemoved(address stakingContractAddress);
/// @notice Emitted when tokens are withdrawn from the staking pool
/// @param amount The amount of tokens withdrawn
/// @param staker The address to receive the tokens
/// @param sender The address that initiated the withdrawal
event AmountWithdrawn(uint248 amount, address staker, address sender);
/* ********** functions ********** */
/// @notice Add a staking contract
/// @param stakingContractAddress The staking contract address to add
function addStakingContract(address stakingContractAddress) external;
/// @notice Remove a staking contract
/// @param stakingContractAddress The staking contract address to remove
function removeStakingContract(address stakingContractAddress) external;
/// @notice Withdraw tokens from the staking pool and send directly to staker
/// @param staker The address to receive the tokens
/// @param amount The amount of tokens to withdraw
/// @dev can only be called by the staking contract
/// @custom:events
/// * Withdraw
function withdraw(address staker, uint248 amount) external;
}
Ownable.sol 100 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is set to the address provided by the deployer. This can
* later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
/**
* @dev The caller account is not authorized to perform an operation.
*/
error OwnableUnauthorizedAccount(address account);
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/
constructor(address initialOwner) {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
Pausable.sol 119 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Pausable.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
/**
* @dev Contract module which allows children to implement an emergency stop
* mechanism that can be triggered by an authorized account.
*
* This module is used through inheritance. It will make available the
* modifiers `whenNotPaused` and `whenPaused`, which can be applied to
* the functions of your contract. Note that they will not be pausable by
* simply including this module, only once the modifiers are put in place.
*/
abstract contract Pausable is Context {
bool private _paused;
/**
* @dev Emitted when the pause is triggered by `account`.
*/
event Paused(address account);
/**
* @dev Emitted when the pause is lifted by `account`.
*/
event Unpaused(address account);
/**
* @dev The operation failed because the contract is paused.
*/
error EnforcedPause();
/**
* @dev The operation failed because the contract is not paused.
*/
error ExpectedPause();
/**
* @dev Initializes the contract in unpaused state.
*/
constructor() {
_paused = false;
}
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*
* Requirements:
*
* - The contract must not be paused.
*/
modifier whenNotPaused() {
_requireNotPaused();
_;
}
/**
* @dev Modifier to make a function callable only when the contract is paused.
*
* Requirements:
*
* - The contract must be paused.
*/
modifier whenPaused() {
_requirePaused();
_;
}
/**
* @dev Returns true if the contract is paused, and false otherwise.
*/
function paused() public view virtual returns (bool) {
return _paused;
}
/**
* @dev Throws if the contract is paused.
*/
function _requireNotPaused() internal view virtual {
if (paused()) {
revert EnforcedPause();
}
}
/**
* @dev Throws if the contract is not paused.
*/
function _requirePaused() internal view virtual {
if (!paused()) {
revert ExpectedPause();
}
}
/**
* @dev Triggers stopped state.
*
* Requirements:
*
* - The contract must not be paused.
*/
function _pause() internal virtual whenNotPaused {
_paused = true;
emit Paused(_msgSender());
}
/**
* @dev Returns to normal state.
*
* Requirements:
*
* - The contract must be paused.
*/
function _unpause() internal virtual whenPaused {
_paused = false;
emit Unpaused(_msgSender());
}
}
IERC1363.sol 86 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC1363.sol)
pragma solidity ^0.8.20;
import {IERC20} from "./IERC20.sol";
import {IERC165} from "./IERC165.sol";
/**
* @title IERC1363
* @dev Interface of the ERC-1363 standard as defined in the https://eips.ethereum.org/EIPS/eip-1363[ERC-1363].
*
* Defines an extension interface for ERC-20 tokens that supports executing code on a recipient contract
* after `transfer` or `transferFrom`, or code on a spender contract after `approve`, in a single transaction.
*/
interface IERC1363 is IERC20, IERC165 {
/*
* Note: the ERC-165 identifier for this interface is 0xb0202a11.
* 0xb0202a11 ===
* bytes4(keccak256('transferAndCall(address,uint256)')) ^
* bytes4(keccak256('transferAndCall(address,uint256,bytes)')) ^
* bytes4(keccak256('transferFromAndCall(address,address,uint256)')) ^
* bytes4(keccak256('transferFromAndCall(address,address,uint256,bytes)')) ^
* bytes4(keccak256('approveAndCall(address,uint256)')) ^
* bytes4(keccak256('approveAndCall(address,uint256,bytes)'))
*/
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferAndCall(address to, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @param data Additional data with no specified format, sent in call to `to`.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param from The address which you want to send tokens from.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferFromAndCall(address from, address to, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param from The address which you want to send tokens from.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @param data Additional data with no specified format, sent in call to `to`.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferFromAndCall(address from, address to, uint256 value, bytes calldata data) external returns (bool);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
* @param spender The address which will spend the funds.
* @param value The amount of tokens to be spent.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function approveAndCall(address spender, uint256 value) external returns (bool);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
* @param spender The address which will spend the funds.
* @param value The amount of tokens to be spent.
* @param data Additional data with no specified format, sent in call to `spender`.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function approveAndCall(address spender, uint256 value, bytes calldata data) external returns (bool);
}
Hashes.sol 31 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/cryptography/Hashes.sol)
pragma solidity ^0.8.20;
/**
* @dev Library of standard hash functions.
*
* _Available since v5.1._
*/
library Hashes {
/**
* @dev Commutative Keccak256 hash of a sorted pair of bytes32. Frequently used when working with merkle proofs.
*
* NOTE: Equivalent to the `standardNodeHash` in our https://github.com/OpenZeppelin/merkle-tree[JavaScript library].
*/
function commutativeKeccak256(bytes32 a, bytes32 b) internal pure returns (bytes32) {
return a < b ? _efficientKeccak256(a, b) : _efficientKeccak256(b, a);
}
/**
* @dev Implementation of keccak256(abi.encode(a, b)) that doesn't allocate or expand memory.
*/
function _efficientKeccak256(bytes32 a, bytes32 b) private pure returns (bytes32 value) {
assembly ("memory-safe") {
mstore(0x00, a)
mstore(0x20, b)
value := keccak256(0x00, 0x40)
}
}
}
Context.sol 28 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}
IERC20.sol 6 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../token/ERC20/IERC20.sol";
IERC165.sol 6 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC165.sol)
pragma solidity ^0.8.20;
import {IERC165} from "../utils/introspection/IERC165.sol";
IERC165.sol 25 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/IERC165.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC-165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[ERC].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
Read Contract
MIN_STAKING_AMOUNT 0xac601474 → uint248
STAKING_POOL_ADDRESS 0x6670613f → address
SUBSTRATE_SIGNATURE_VALIDATOR_ADDRESS 0xc12e16b5 → address
TOKEN_ADDRESS 0x0bdf5300 → address
TOKEN_DECIMALS 0x5b7f415c → uint8
UNSTAKING_UNBONDING_PERIOD 0xf84fe0a4 → uint64
initiateUnstakeRequestsTimestamp 0x8c5a07df → uint64
latestSxtBlockFulfillmentByStaker 0x5a6bea7f → uint64
owner 0x8da5cb5b → address
paused 0x5c975abb → bool
stakerState 0x7623ad35 → uint8
Write Contract 9 functions
These functions modify contract state and require a wallet transaction to execute.
cancelInitiateUnstake 0xfebe3098
No parameters
claimUnstake 0x0a3ec230
No parameters
initiateUnstake 0x3a5bf240
uint248 amount
nominate 0xf5330e96
bytes32[] nodesEd25519PubKeys
renounceOwnership 0x715018a6
No parameters
stake 0xef2d7831
uint248 amount
sxtFulfillUnstake 0xd34fec68
address staker
uint248 amount
uint64 sxtBlockNumber
bytes32[] proof
bytes32[] r
bytes32[] s
uint8[] v
transferOwnership 0xf2fde38b
address newOwner
unpauseUnstaking 0x2dde8111
No parameters
Recent Transactions
No transactions found for this address