Cryo Explorer Ethereum Mainnet

Address Contract Verified

Address 0x93d176dd54FF38b08f33b4Fc62573ec80F1da185
Balance 0 ETH
Nonce 1
Code Size 6161 bytes
Indexed Transactions 0
External Etherscan · Sourcify

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