EnsoStaking

Description:

Multi-signature wallet contract requiring multiple confirmations for transaction execution.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "src/EnsoStaking.sol": {
      "content": "// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.20;

import { EnsoStakingUpgradeable } from "./upgradeable/EnsoStakingUpgradeable.sol";
import { UUPSUpgradeable } from "openzeppelin-contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";

contract EnsoStaking is EnsoStakingUpgradeable, UUPSUpgradeable {
    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() {
        _disableInitializers();
    }

    function initialize(
        string memory _name,
        string memory _symbol,
        address _owner,
        address _token,
        uint64 _minPeriod,
        uint64 _maxPeriod,
        uint256 _maxMulitplier
    )
        external
        initializer
    {
        __ERC721_init_unchained(_name, _symbol);
        __Ownable_init_unchained(_owner);
        __EnsoStaking_init_unchained(_token, _minPeriod, _maxPeriod, _maxMulitplier);
    }

    function _authorizeUpgrade(address) internal override onlyOwner {
        // all necessary validation is handled by the onlyOwner modifier
    }
}
"
    },
    "src/upgradeable/EnsoStakingUpgradeable.sol": {
      "content": "// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.20;

import { IEnsoStaking } from "../interfaces/IEnsoStaking.sol";
import { Ownable2StepUpgradeable } from "openzeppelin-contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
import { Initializable } from "openzeppelin-contracts-upgradeable/proxy/utils/Initializable.sol";
import { ERC721Upgradeable } from "openzeppelin-contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
import { IERC20, SafeERC20 } from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";
import { Math } from "openzeppelin-contracts/utils/math/Math.sol";

contract EnsoStakingUpgradeable is IEnsoStaking, Initializable, ERC721Upgradeable, Ownable2StepUpgradeable {
    using SafeERC20 for IERC20;

    uint256 public constant PRECISION = 1e18;
    uint96 public constant DEFAULT_FEE = 0.2e18; // 20% fee
    uint64 public constant VALIDATOR_FEE_UPDATE_DELAY = 3 days;

    /// @custom:storage-location erc7201:enso.storage.Staking
    struct EnsoStakingStorage {
        IERC20 token;
        uint256 maxMultiplier;
        uint256 maxRange;
        uint64 maxPeriod;
        uint64 minPeriod;
        uint256 nextPositionId;
        mapping(bytes32 => ValidatorData) validators;
        mapping(bytes32 => address) pendingValidators;
        mapping(bytes32 => PendingValidatorFee) pendingFees;
        mapping(uint256 => Position) positions;
        mapping(address => uint256) depositorStakes; // depositor => stake
        mapping(address => uint256) collectedRewards; // depositor => rewards collected
        mapping(bytes32 => uint256) delegateStakes; // delegate => stake
        mapping(bytes32 => uint256) rewardsPerStake; // delegate => value per stake
        mapping(bytes32 => uint256) totalRewards; // delegate => rewards earned
    }

    /// forge-lint: disable-next-item(screaming-snake-case-const)
    // keccak256(abi.encode(uint256(keccak256("enso.storage.Staking")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant EnsoStakingStorageLocation =
        0xab7f204927625a68eb5fa7fefd90efd520ba1844238d9aca28f6f0f9cde79b00;

    function _getEnsoStakingStorage() private pure returns (EnsoStakingStorage storage $) {
        assembly {
            $.slot := EnsoStakingStorageLocation
        }
    }

    /// forge-lint: disable-next-item(mixed-case-function)
    function __EnsoStaking_init(
        string memory _name,
        string memory _symbol,
        address _owner,
        address _token,
        uint64 _minPeriod,
        uint64 _maxPeriod,
        uint256 _maxMulitplier
    )
        internal
        onlyInitializing
    {
        __ERC721_init_unchained(_name, _symbol);
        __Ownable_init_unchained(_owner);
        __EnsoStaking_init_unchained(_token, _minPeriod, _maxPeriod, _maxMulitplier);
    }

    /// forge-lint: disable-next-item(mixed-case-function)
    function __EnsoStaking_init_unchained(
        address _token,
        uint64 _minPeriod,
        uint64 _maxPeriod,
        uint256 _maxMulitplier
    )
        internal
        onlyInitializing
    {
        // @dev Prevent zero MaxRange
        if (_minPeriod >= _maxPeriod) {
            revert MinPeriodIsGteMaxPeriod(_minPeriod, _maxPeriod);
        }
        EnsoStakingStorage storage $ = _getEnsoStakingStorage();
        $.token = IERC20(_token);
        $.minPeriod = _minPeriod;
        $.maxPeriod = _maxPeriod;
        $.maxRange = _maxPeriod - _minPeriod;
        $.maxMultiplier = _maxMulitplier;
    }

    function addValidator(bytes32 validatorId, address validator) external onlyOwner {
        if (validatorId == bytes32(0)) {
            revert NullValidatorId();
        }
        if (isValidator(validatorId)) {
            revert ValidatorAlreadyAdded(validatorId);
        }
        EnsoStakingStorage storage $ = _getEnsoStakingStorage();
        $.validators[validatorId] = ValidatorData(validator, DEFAULT_FEE);
        emit ValidatorAdded(validatorId);
        emit ValidatorAddressUpdated(validatorId, validator, address(0));
        emit ValidatorFeeUpdated(validatorId, DEFAULT_FEE, 0);
    }

    function removeValidator(bytes32 validatorId) external onlyOwner checkValidator(validatorId) {
        // validator can no longer be delegated to or receive new rewards but all
        // reward information is kept so users can collect previous rewards.
        // position holders need to delegate to a new validator.
        EnsoStakingStorage storage $ = _getEnsoStakingStorage();
        delete $.validators[validatorId];
        delete $.pendingValidators[validatorId];
        delete $.pendingFees[validatorId];
        emit ValidatorRemoved(validatorId);
    }

    function createPosition(
        uint256 amount,
        uint64 period,
        address receiver,
        bytes32 validatorId
    )
        external
        checkValidator(validatorId)
        returns (uint256 positionId)
    {
        if (amount == 0) revert InvalidAmount(amount);
        if (period > maxPeriod() || period < minPeriod()) revert InvalidStakingPeriod(period);

        token().safeTransferFrom(msg.sender, address(this), amount);
        uint256 stake = calculateStake(amount, period);
        uint64 expiry = uint64(block.timestamp) + period;
        positionId = nextPositionId();

        EnsoStakingStorage storage $ = _getEnsoStakingStorage();
        $.nextPositionId = positionId + 1;
        $.positions[positionId] = Position(validatorId, amount, stake, expiry, $.rewardsPerStake[validatorId]);
        unchecked {
            $.depositorStakes[receiver] += stake;
            $.delegateStakes[validatorId] += stake;
        }
        _safeMint(receiver, positionId);
        emit PositionCreated(positionId, expiry, validatorId);
        emit FundsDeposited(positionId, amount, stake);
    }

    function deposit(uint256 positionId, uint256 amount) public {
        if (amount == 0) revert InvalidAmount(amount);
        Position storage position = _getPosition(positionId);
        address account = _getAccount(positionId, false);

        // collect previous delegates rewards
        _collectRewards(account, positionId, position);

        token().safeTransferFrom(msg.sender, address(this), amount);

        _deposit(account, amount, positionId, position);
    }

    function reinvest(uint256 positionId, uint256 limit) public {
        Position storage position = _getPosition(positionId);
        address account = _getAccount(positionId, true);

        // collect previous delegates rewards
        _collectRewards(account, positionId, position);

        EnsoStakingStorage storage $ = _getEnsoStakingStorage();
        uint256 rewards = $.collectedRewards[account];
        uint256 amount = limit > 0 && rewards > limit ? limit : rewards;

        unchecked {
            $.collectedRewards[account] -= amount;
        }

        _deposit(account, amount, positionId, position);
    }

    function withdraw(uint256 positionId, uint256 amount, address receiver) external {
        if (amount == 0) revert InvalidAmount(amount);
        Position storage position = _getPosition(positionId);
        address account = _getAccount(positionId, true);

        if (block.timestamp < position.expiry) revert PositionNotExpired(position.expiry, uint64(block.timestamp));
        if (amount > position.deposit) revert InsufficientDeposit(position.deposit, amount);

        // collect previous delegates rewards
        _collectRewards(account, positionId, position);

        // deposit and stake set to be equal after expiry, which is the only time they can be withdrawn
        // no point to calculate the relative stake
        position.stake -= amount;
        position.deposit -= amount;

        EnsoStakingStorage storage $ = _getEnsoStakingStorage();
        unchecked {
            $.depositorStakes[account] -= amount;
            $.delegateStakes[position.delegate] -= amount;
        }
        $.token.safeTransfer(receiver, amount);
        emit FundsWithdrawn(positionId, amount);
    }

    function updateDelegate(uint256 positionId, bytes32 validatorId) external checkValidator(validatorId) {
        Position storage position = _getPosition(positionId);
        address account = _getAccount(positionId, true);

        // collect previous delegates rewards
        _collectRewards(account, positionId, position);

        // update delegate stakes
        bytes32 prevDelegate = position.delegate;
        EnsoStakingStorage storage $ = _getEnsoStakingStorage();
        unchecked {
            $.delegateStakes[prevDelegate] -= position.stake;
            $.delegateStakes[validatorId] += position.stake;
        }
        // update position
        position.delegate = validatorId;
        position.rewardsCheckpoint = $.rewardsPerStake[validatorId];
        emit DelegateUpdated(positionId, validatorId, prevDelegate);
    }

    function updateExpiry(uint256 positionId, uint64 expiry) external {
        Position storage position = _getPosition(positionId);
        address account = _getAccount(positionId, true);

        uint64 prevExpiry = position.expiry;
        uint64 blockTimestamp = uint64(block.timestamp);
        if (expiry <= prevExpiry || expiry < blockTimestamp) revert InvalidExpiry(expiry);
        uint64 period = expiry - blockTimestamp;
        if (period > maxPeriod() || period < minPeriod()) revert InvalidStakingPeriod(period);

        // collect previous delegates rewards
        _collectRewards(account, positionId, position);

        // calculate stake and update
        uint256 stake = calculateStake(position.deposit, period);
        if (stake <= position.stake) revert InvalidExpiry(expiry); // extending expiry does not improve stake
        EnsoStakingStorage storage $ = _getEnsoStakingStorage();
        unchecked {
            uint256 diff = stake - position.stake;
            $.depositorStakes[account] += diff;
            $.delegateStakes[position.delegate] += diff;
        }
        position.expiry = expiry;
        position.stake = stake;
        emit ExpiryUpdated(positionId, expiry, prevExpiry);
    }

    function updateValidatorAddress(bytes32 validatorId, address newValidator) external checkValidator(validatorId) {
        if (newValidator == address(0)) {
            revert NullAddress();
        }
        address validator = validatorAddress(validatorId);
        if (msg.sender != validator) {
            revert InvalidSender(validator, msg.sender);
        }
        EnsoStakingStorage storage $ = _getEnsoStakingStorage();
        $.pendingValidators[validatorId] = newValidator;
        emit NewValidatorAddressPending(validatorId, newValidator);
    }

    function finalizeValidatorAddress(bytes32 validatorId) external checkValidator(validatorId) {
        EnsoStakingStorage storage $ = _getEnsoStakingStorage();
        address pendingValidatorAddr = $.pendingValidators[validatorId];
        if (pendingValidatorAddr == address(0)) {
            revert NoPendingValidator();
        }
        if (msg.sender != pendingValidatorAddr) {
            revert InvalidSender(pendingValidatorAddr, msg.sender);
        }

        address prevValidator = validatorAddress(validatorId);
        delete $.pendingValidators[validatorId];
        $.validators[validatorId].validator = pendingValidatorAddr;
        emit ValidatorAddressUpdated(validatorId, pendingValidatorAddr, prevValidator);
    }

    function updateValidatorFee(bytes32 validatorId, uint256 fee) external checkValidator(validatorId) {
        address validator = validatorAddress(validatorId);
        if (msg.sender != validator) {
            revert InvalidSender(validator, msg.sender);
        }
        if (fee == 0 || fee >= PRECISION) revert InvalidFee(fee);
        uint64 applyAfter = uint64(block.timestamp) + VALIDATOR_FEE_UPDATE_DELAY;
        EnsoStakingStorage storage $ = _getEnsoStakingStorage();
        $.pendingFees[validatorId] = PendingValidatorFee(uint96(fee), applyAfter);
        emit NewValidatorFeePending(validatorId, fee, applyAfter);
    }

    function finalizeValidatorFee(bytes32 validatorId) external checkValidator(validatorId) {
        ValidatorData memory data = validatorData(validatorId);
        address validator = data.validator;
        if (msg.sender != validator) {
            revert InvalidSender(validator, msg.sender);
        }

        EnsoStakingStorage storage $ = _getEnsoStakingStorage();
        PendingValidatorFee memory pending = $.pendingFees[validatorId];
        if (pending.fee == 0) revert NoPendingFee();
        if (block.timestamp < pending.applyAfter) {
            revert PendingFeeDelayNotExpired(pending.applyAfter, block.timestamp);
        }
        uint96 fee = pending.fee;
        uint256 prevFee = data.fee;
        delete $.pendingFees[validatorId];
        $.validators[validatorId].fee = fee;
        emit ValidatorFeeUpdated(validatorId, fee, prevFee);
    }

    function issueRewards(bytes32 validatorId, uint256 amount) external checkValidator(validatorId) {
        EnsoStakingStorage storage $ = _getEnsoStakingStorage();
        if ($.delegateStakes[validatorId] == 0) revert DelegateHasNoStake(validatorId);
        $.token.safeTransferFrom(msg.sender, address(this), amount);
        $.rewardsPerStake[validatorId] += Math.mulDiv(amount, PRECISION, $.delegateStakes[validatorId]);
        $.totalRewards[validatorId] += amount;
        emit RewardsIssued(validatorId, amount);
    }

    function withdrawRewards(uint256[] calldata positionIds) external {
        collectRewards(positionIds);
        // only rewards collected for msg.sender will be sent
        EnsoStakingStorage storage $ = _getEnsoStakingStorage();
        uint256 rewards = $.collectedRewards[msg.sender];
        delete $.collectedRewards[msg.sender];
        $.token.safeTransfer(msg.sender, rewards);
        emit RewardsWithdrawn(msg.sender, rewards);
    }

    function collectRewards(uint256[] calldata positionIds) public {
        // rewards can be collected even if msg.sender does not control the positions
        uint256 positionId;
        address account;
        for (uint256 i; i < positionIds.length; i++) {
            positionId = positionIds[i];
            Position storage position = _getPosition(positionId);
            account = _ownerOf(positionId);
            _collectRewards(account, positionId, position);
        }
    }

    function getPosition(uint256 positionId) external view returns (Position memory position) {
        position = _getPosition(positionId);
    }

    function availableRewards(uint256 positionId) external view returns (uint256 rewards) {
        EnsoStakingStorage storage $ = _getEnsoStakingStorage();
        Position memory position = $.positions[positionId];
        rewards = _availableRewards(position);
    }

    function token() public view returns (IERC20) {
        EnsoStakingStorage storage $ = _getEnsoStakingStorage();
        return $.token;
    }

    function maxMultiplier() public view returns (uint256) {
        EnsoStakingStorage storage $ = _getEnsoStakingStorage();
        return $.maxMultiplier;
    }

    function maxRange() public view returns (uint256) {
        EnsoStakingStorage storage $ = _getEnsoStakingStorage();
        return $.maxRange;
    }

    function maxPeriod() public view returns (uint64) {
        EnsoStakingStorage storage $ = _getEnsoStakingStorage();
        return $.maxPeriod;
    }

    function minPeriod() public view returns (uint64) {
        EnsoStakingStorage storage $ = _getEnsoStakingStorage();
        return $.minPeriod;
    }

    function nextPositionId() public view returns (uint256) {
        EnsoStakingStorage storage $ = _getEnsoStakingStorage();
        return $.nextPositionId;
    }

    function validatorData(bytes32 validatorId) public view returns (ValidatorData memory) {
        EnsoStakingStorage storage $ = _getEnsoStakingStorage();
        return $.validators[validatorId];
    }

    function validatorDataAndStake(bytes32 validatorId)
        external
        view
        returns (ValidatorData memory data, uint256 stake)
    {
        data = validatorData(validatorId);
        stake = delegateStake(validatorId);
    }

    function validatorAddress(bytes32 validatorId) public view returns (address) {
        return validatorData(validatorId).validator;
    }

    function validatorFee(bytes32 validatorId) public view returns (uint96) {
        return validatorData(validatorId).fee;
    }

    function isValidator(bytes32 validatorId) public view returns (bool) {
        return validatorAddress(validatorId) != address(0);
    }

    function pendingFee(bytes32 validatorId) public view returns (uint96 fee, uint64 applyAfter) {
        EnsoStakingStorage storage $ = _getEnsoStakingStorage();
        PendingValidatorFee memory pending = $.pendingFees[validatorId];
        fee = pending.fee;
        applyAfter = pending.applyAfter;
    }

    function pendingValidator(bytes32 validatorId) public view returns (address validator) {
        EnsoStakingStorage storage $ = _getEnsoStakingStorage();
        validator = $.pendingValidators[validatorId];
    }

    function depositorStake(address depositor) public view returns (uint256) {
        EnsoStakingStorage storage $ = _getEnsoStakingStorage();
        return $.depositorStakes[depositor];
    }

    function delegateStake(bytes32 validatorId) public view returns (uint256) {
        EnsoStakingStorage storage $ = _getEnsoStakingStorage();
        return $.delegateStakes[validatorId];
    }

    function rewardsPerStake(bytes32 validatorId) public view returns (uint256) {
        EnsoStakingStorage storage $ = _getEnsoStakingStorage();
        return $.rewardsPerStake[validatorId];
    }

    function totalRewards(bytes32 validatorId) public view returns (uint256) {
        EnsoStakingStorage storage $ = _getEnsoStakingStorage();
        return $.totalRewards[validatorId];
    }

    function collectedRewards(address depositor) public view returns (uint256) {
        EnsoStakingStorage storage $ = _getEnsoStakingStorage();
        return $.collectedRewards[depositor];
    }

    function calculateStake(uint256 amount, uint64 period) public view returns (uint256 stake) {
        EnsoStakingStorage storage $ = _getEnsoStakingStorage();
        uint256 range = period - $.minPeriod;
        stake = amount + Math.mulDiv(amount, $.maxMultiplier * range, $.maxRange * PRECISION);
    }

    function _getAccount(uint256 positionId, bool checkSender) internal view returns (address account) {
        account = _ownerOf(positionId);
        if (checkSender && msg.sender != account) revert InvalidSender(account, msg.sender);
    }

    function _getPosition(uint256 positionId) internal view returns (Position storage position) {
        EnsoStakingStorage storage $ = _getEnsoStakingStorage();
        if (positionId >= $.nextPositionId) revert InvalidPositionId(positionId);
        position = $.positions[positionId];
    }

    function _deposit(address account, uint256 amount, uint256 positionId, Position storage position) internal {
        uint64 blockTimestamp = uint64(block.timestamp);
        // @dev prevent underflow when `blockTimestamp > position.expiry`
        uint64 remainingPeriod = position.expiry > blockTimestamp ? position.expiry - blockTimestamp : 0;
        if (remainingPeriod <= minPeriod()) revert InvalidStakingPeriod(remainingPeriod);
        uint256 stake = calculateStake(amount, remainingPeriod);
        EnsoStakingStorage storage $ = _getEnsoStakingStorage();
        unchecked {
            position.stake += stake;
            position.deposit += amount;
            $.depositorStakes[account] += stake;
            $.delegateStakes[position.delegate] += stake;
        }
        emit FundsDeposited(positionId, amount, stake);
    }

    function _collectRewards(
        address account,
        uint256 positionId,
        Position storage position
    )
        internal
        returns (uint256 rewards)
    {
        EnsoStakingStorage storage $ = _getEnsoStakingStorage();
        rewards = _availableRewards(position);
        position.rewardsCheckpoint = $.rewardsPerStake[position.delegate];
        unchecked {
            $.collectedRewards[account] += rewards;
        }
        emit RewardsCollected(positionId, account, rewards);

        // reset position if expired
        if (position.expiry <= block.timestamp) {
            uint256 diff = position.stake - position.deposit;
            if (diff > 0) {
                unchecked {
                    $.depositorStakes[account] -= diff;
                    $.delegateStakes[position.delegate] -= diff;
                }
                position.stake = position.deposit;
            }
        }
    }

    function _availableRewards(Position memory position) internal view returns (uint256 rewards) {
        uint256 rewardDiff = rewardsPerStake(position.delegate) - position.rewardsCheckpoint;
        rewards = Math.mulDiv(rewardDiff, position.stake, PRECISION);
    }

    // override ERC721 transfer
    function _update(address to, uint256 tokenId, address auth) internal override returns (address) {
        address from = super._update(to, tokenId, auth);
        // only update on transfers (if address is zero its a mint)
        // our createPosition function can initially set the stake at mint time,
        // so we don't have to query the positions mapping a second time to get the stake value
        if (from != address(0)) {
            EnsoStakingStorage storage $ = _getEnsoStakingStorage();
            Position storage position = $.positions[tokenId];
            // collect rewards for the previous owner
            _collectRewards(from, tokenId, position);
            // handle changes to user stake
            unchecked {
                $.depositorStakes[from] -= position.stake;
                $.depositorStakes[to] += position.stake;
            }
        }
        return from;
    }

    modifier checkValidator(bytes32 validatorId) {
        if (!isValidator(validatorId)) revert NotValidator(validatorId);
        _;
    }
}
"
    },
    "dependencies/@openzeppelin-contracts-upgradeable-5.3.0/contracts/proxy/utils/UUPSUpgradeable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (proxy/utils/UUPSUpgradeable.sol)

pragma solidity ^0.8.22;

import {IERC1822Proxiable} from "@openzeppelin/contracts/interfaces/draft-IERC1822.sol";
import {ERC1967Utils} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol";
import {Initializable} from "./Initializable.sol";

/**
 * @dev An upgradeability mechanism designed for UUPS proxies. The functions included here can perform an upgrade of an
 * {ERC1967Proxy}, when this contract is set as the implementation behind such a proxy.
 *
 * A security mechanism ensures that an upgrade does not turn off upgradeability accidentally, although this risk is
 * reinstated if the upgrade retains upgradeability but removes the security mechanism, e.g. by replacing
 * `UUPSUpgradeable` with a custom implementation of upgrades.
 *
 * The {_authorizeUpgrade} function must be overridden to include access restriction to the upgrade mechanism.
 */
abstract contract UUPSUpgradeable is Initializable, IERC1822Proxiable {
    /// @custom:oz-upgrades-unsafe-allow state-variable-immutable
    address private immutable __self = address(this);

    /**
     * @dev The version of the upgrade interface of the contract. If this getter is missing, both `upgradeTo(address)`
     * and `upgradeToAndCall(address,bytes)` are present, and `upgradeTo` must be used if no function should be called,
     * while `upgradeToAndCall` will invoke the `receive` function if the second argument is the empty byte string.
     * If the getter returns `"5.0.0"`, only `upgradeToAndCall(address,bytes)` is present, and the second argument must
     * be the empty byte string if no function should be called, making it impossible to invoke the `receive` function
     * during an upgrade.
     */
    string public constant UPGRADE_INTERFACE_VERSION = "5.0.0";

    /**
     * @dev The call is from an unauthorized context.
     */
    error UUPSUnauthorizedCallContext();

    /**
     * @dev The storage `slot` is unsupported as a UUID.
     */
    error UUPSUnsupportedProxiableUUID(bytes32 slot);

    /**
     * @dev Check that the execution is being performed through a delegatecall call and that the execution context is
     * a proxy contract with an implementation (as defined in ERC-1967) pointing to self. This should only be the case
     * for UUPS and transparent proxies that are using the current contract as their implementation. Execution of a
     * function through ERC-1167 minimal proxies (clones) would not normally pass this test, but is not guaranteed to
     * fail.
     */
    modifier onlyProxy() {
        _checkProxy();
        _;
    }

    /**
     * @dev Check that the execution is not being performed through a delegate call. This allows a function to be
     * callable on the implementing contract but not through proxies.
     */
    modifier notDelegated() {
        _checkNotDelegated();
        _;
    }

    function __UUPSUpgradeable_init() internal onlyInitializing {
    }

    function __UUPSUpgradeable_init_unchained() internal onlyInitializing {
    }
    /**
     * @dev Implementation of the ERC-1822 {proxiableUUID} function. This returns the storage slot used by the
     * implementation. It is used to validate the implementation's compatibility when performing an upgrade.
     *
     * IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks
     * bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this
     * function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier.
     */
    function proxiableUUID() external view virtual notDelegated returns (bytes32) {
        return ERC1967Utils.IMPLEMENTATION_SLOT;
    }

    /**
     * @dev Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call
     * encoded in `data`.
     *
     * Calls {_authorizeUpgrade}.
     *
     * Emits an {Upgraded} event.
     *
     * @custom:oz-upgrades-unsafe-allow-reachable delegatecall
     */
    function upgradeToAndCall(address newImplementation, bytes memory data) public payable virtual onlyProxy {
        _authorizeUpgrade(newImplementation);
        _upgradeToAndCallUUPS(newImplementation, data);
    }

    /**
     * @dev Reverts if the execution is not performed via delegatecall or the execution
     * context is not of a proxy with an ERC-1967 compliant implementation pointing to self.
     */
    function _checkProxy() internal view virtual {
        if (
            address(this) == __self || // Must be called through delegatecall
            ERC1967Utils.getImplementation() != __self // Must be called through an active proxy
        ) {
            revert UUPSUnauthorizedCallContext();
        }
    }

    /**
     * @dev Reverts if the execution is performed via delegatecall.
     * See {notDelegated}.
     */
    function _checkNotDelegated() internal view virtual {
        if (address(this) != __self) {
            // Must not be called through delegatecall
            revert UUPSUnauthorizedCallContext();
        }
    }

    /**
     * @dev Function that should revert when `msg.sender` is not authorized to upgrade the contract. Called by
     * {upgradeToAndCall}.
     *
     * Normally, this function will use an xref:access.adoc[access control] modifier such as {Ownable-onlyOwner}.
     *
     * ```solidity
     * function _authorizeUpgrade(address) internal onlyOwner {}
     * ```
     */
    function _authorizeUpgrade(address newImplementation) internal virtual;

    /**
     * @dev Performs an implementation upgrade with a security check for UUPS proxies, and additional setup call.
     *
     * As a security check, {proxiableUUID} is invoked in the new implementation, and the return value
     * is expected to be the implementation slot in ERC-1967.
     *
     * Emits an {IERC1967-Upgraded} event.
     */
    function _upgradeToAndCallUUPS(address newImplementation, bytes memory data) private {
        try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) {
            if (slot != ERC1967Utils.IMPLEMENTATION_SLOT) {
                revert UUPSUnsupportedProxiableUUID(slot);
            }
            ERC1967Utils.upgradeToAndCall(newImplementation, data);
        } catch {
            // The implementation is not UUPS
            revert ERC1967Utils.ERC1967InvalidImplementation(newImplementation);
        }
    }
}
"
    },
    "src/interfaces/IEnsoStaking.sol": {
      "content": "// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;

interface IEnsoStaking {
    function createPosition(
        uint256 amount,
        uint64 period,
        address receiver,
        bytes32 validatorId
    )
        external
        returns (uint256 positionId);

    function issueRewards(bytes32 validatorId, uint256 amount) external;

    function validatorData(bytes32 validatorId) external view returns (ValidatorData memory);

    function validatorDataAndStake(bytes32 validatorId) external view returns (ValidatorData memory, uint256);

    function validatorAddress(bytes32 validatorId) external view returns (address);

    function validatorFee(bytes32 validatorId) external view returns (uint96);

    function isValidator(bytes32 validatorId) external view returns (bool);

    function delegateStake(bytes32 validatorId) external view returns (uint256);

    event PositionCreated(uint256 indexed positionId, uint64 expiry, bytes32 indexed validatorId);
    event FundsDeposited(uint256 indexed positionId, uint256 fundsAdded, uint256 stakeAdded);
    event FundsWithdrawn(uint256 indexed positionId, uint256 fundsRemoved);
    event RewardsCollected(uint256 indexed positionId, address account, uint256 rewards);
    event RewardsIssued(bytes32 indexed validatorId, uint256 amount);
    event RewardsWithdrawn(address indexed to, uint256 rewards);
    event DelegateUpdated(uint256 indexed positionId, bytes32 delegate, bytes32 previousDelegate);
    event ExpiryUpdated(uint256 indexed positionId, uint64 expiry, uint64 previousExpiry);
    event ValidatorAdded(bytes32 indexed validatorId);
    event ValidatorRemoved(bytes32 indexed validatorId);
    event ValidatorAddressUpdated(bytes32 indexed validatorId, address validator, address previousValidator);
    event ValidatorFeeUpdated(bytes32 indexed validatorId, uint256 fee, uint256 previousFee);
    event NewValidatorAddressPending(bytes32 indexed validatorId, address validator);
    event NewValidatorFeePending(bytes32 indexed validatorId, uint256 fee, uint64 applyAfter);

    error InvalidStakingPeriod(uint64 period);
    error InvalidSender(address expected, address actual);
    error InvalidPositionId(uint256 id);
    error InvalidAmount(uint256 amount);
    error InvalidExpiry(uint64 expiry);
    error InvalidFee(uint256 fee);
    error MinPeriodIsGteMaxPeriod(uint64 minPeriod, uint64 maxPeriod);
    error NoPendingFee();
    error NoPendingValidator();
    error NotValidator(bytes32 validatorId);
    error ValidatorAlreadyAdded(bytes32 validatorId);
    error PositionNotExpired(uint64 expiry, uint64 timestamp);
    error PendingFeeDelayNotExpired(uint256 expiry, uint256 timestamp);
    error InsufficientDeposit(uint256 stake, uint256 amount);
    error DelegateHasNoStake(bytes32 validatorId);
    error NullAddress();
    error NullValidatorId();

    struct Position {
        bytes32 delegate;
        uint256 deposit;
        uint256 stake;
        uint64 expiry;
        uint256 rewardsCheckpoint;
    }

    struct PendingValidatorFee {
        uint96 fee;
        uint64 applyAfter;
    }

    struct ValidatorData {
        address validator;
        uint96 fee;
    }
}
"
    },
    "dependencies/@openzeppelin-contracts-upgradeable-5.3.0/contracts/access/Ownable2StepUpgradeable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (access/Ownable2Step.sol)

pragma solidity ^0.8.20;

import {OwnableUpgradeable} from "./OwnableUpgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.sol";

/**
 * @dev Contract module which provides access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * This extension of the {Ownable} contract includes a two-step mechanism to transfer
 * ownership, where the new owner must call {acceptOwnership} in order to replace the
 * old one. This can help prevent common mistakes, such as transfers of ownership to
 * incorrect accounts, or to contracts that are unable to interact with the
 * permission system.
 *
 * The initial owner is specified at deployment time in the constructor for `Ownable`. This
 * can later be changed with {transferOwnership} and {acceptOwnership}.
 *
 * This module is used through inheritance. It will make available all functions
 * from parent (Ownable).
 */
abstract contract Ownable2StepUpgradeable is Initializable, OwnableUpgradeable {
    /// @custom:storage-location erc7201:openzeppelin.storage.Ownable2Step
    struct Ownable2StepStorage {
        address _pendingOwner;
    }

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Ownable2Step")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant Ownable2StepStorageLocation = 0x237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c00;

    function _getOwnable2StepStorage() private pure returns (Ownable2StepStorage storage $) {
        assembly {
            $.slot := Ownable2StepStorageLocation
        }
    }

    event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);

    function __Ownable2Step_init() internal onlyInitializing {
    }

    function __Ownable2Step_init_unchained() internal onlyInitializing {
    }
    /**
     * @dev Returns the address of the pending owner.
     */
    function pendingOwner() public view virtual returns (address) {
        Ownable2StepStorage storage $ = _getOwnable2StepStorage();
        return $._pendingOwner;
    }

    /**
     * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.
     * Can only be called by the current owner.
     *
     * Setting `newOwner` to the zero address is allowed; this can be used to cancel an initiated ownership transfer.
     */
    function transferOwnership(address newOwner) public virtual override onlyOwner {
        Ownable2StepStorage storage $ = _getOwnable2StepStorage();
        $._pendingOwner = newOwner;
        emit OwnershipTransferStarted(owner(), newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual override {
        Ownable2StepStorage storage $ = _getOwnable2StepStorage();
        delete $._pendingOwner;
        super._transferOwnership(newOwner);
    }

    /**
     * @dev The new owner accepts the ownership transfer.
     */
    function acceptOwnership() public virtual {
        address sender = _msgSender();
        if (pendingOwner() != sender) {
            revert OwnableUnauthorizedAccount(sender);
        }
        _transferOwnership(sender);
    }
}
"
    },
    "dependencies/@openzeppelin-contracts-upgradeable-5.3.0/contracts/proxy/utils/Initializable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (proxy/utils/Initializable.sol)

pragma solidity ^0.8.20;

/**
 * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
 * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
 * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
 * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
 *
 * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
 * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
 * case an upgrade adds a module that needs to be initialized.
 *
 * For example:
 *
 * [.hljs-theme-light.nopadding]
 * ```solidity
 * contract MyToken is ERC20Upgradeable {
 *     function initialize() initializer public {
 *         __ERC20_init("MyToken", "MTK");
 *     }
 * }
 *
 * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
 *     function initializeV2() reinitializer(2) public {
 *         __ERC20Permit_init("MyToken");
 *     }
 * }
 * ```
 *
 * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
 * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
 *
 * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
 * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
 *
 * [CAUTION]
 * ====
 * Avoid leaving a contract uninitialized.
 *
 * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
 * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
 * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
 *
 * [.hljs-theme-light.nopadding]
 * ```
 * /// @custom:oz-upgrades-unsafe-allow constructor
 * constructor() {
 *     _disableInitializers();
 * }
 * ```
 * ====
 */
abstract contract Initializable {
    /**
     * @dev Storage of the initializable contract.
     *
     * It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions
     * when using with upgradeable contracts.
     *
     * @custom:storage-location erc7201:openzeppelin.storage.Initializable
     */
    struct InitializableStorage {
        /**
         * @dev Indicates that the contract has been initialized.
         */
        uint64 _initialized;
        /**
         * @dev Indicates that the contract is in the process of being initialized.
         */
        bool _initializing;
    }

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Initializable")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant INITIALIZABLE_STORAGE = 0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00;

    /**
     * @dev The contract is already initialized.
     */
    error InvalidInitialization();

    /**
     * @dev The contract is not initializing.
     */
    error NotInitializing();

    /**
     * @dev Triggered when the contract has been initialized or reinitialized.
     */
    event Initialized(uint64 version);

    /**
     * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
     * `onlyInitializing` functions can be used to initialize parent contracts.
     *
     * Similar to `reinitializer(1)`, except that in the context of a constructor an `initializer` may be invoked any
     * number of times. This behavior in the constructor can be useful during testing and is not expected to be used in
     * production.
     *
     * Emits an {Initialized} event.
     */
    modifier initializer() {
        // solhint-disable-next-line var-name-mixedcase
        InitializableStorage storage $ = _getInitializableStorage();

        // Cache values to avoid duplicated sloads
        bool isTopLevelCall = !$._initializing;
        uint64 initialized = $._initialized;

        // Allowed calls:
        // - initialSetup: the contract is not in the initializing state and no previous version was
        //                 initialized
        // - construction: the contract is initialized at version 1 (no reinitialization) and the
        //                 current contract is just being deployed
        bool initialSetup = initialized == 0 && isTopLevelCall;
        bool construction = initialized == 1 && address(this).code.length == 0;

        if (!initialSetup && !construction) {
            revert InvalidInitialization();
        }
        $._initialized = 1;
        if (isTopLevelCall) {
            $._initializing = true;
        }
        _;
        if (isTopLevelCall) {
            $._initializing = false;
            emit Initialized(1);
        }
    }

    /**
     * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
     * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
     * used to initialize parent contracts.
     *
     * A reinitializer may be used after the original initialization step. This is essential to configure modules that
     * are added through upgrades and that require initialization.
     *
     * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
     * cannot be nested. If one is invoked in the context of another, execution will revert.
     *
     * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
     * a contract, executing them in the right order is up to the developer or operator.
     *
     * WARNING: Setting the version to 2**64 - 1 will prevent any future reinitialization.
     *
     * Emits an {Initialized} event.
     */
    modifier reinitializer(uint64 version) {
        // solhint-disable-next-line var-name-mixedcase
        InitializableStorage storage $ = _getInitializableStorage();

        if ($._initializing || $._initialized >= version) {
            revert InvalidInitialization();
        }
        $._initialized = version;
        $._initializing = true;
        _;
        $._initializing = false;
        emit Initialized(version);
    }

    /**
     * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
     * {initializer} and {reinitializer} modifiers, directly or indirectly.
     */
    modifier onlyInitializing() {
        _checkInitializing();
        _;
    }

    /**
     * @dev Reverts if the contract is not in an initializing state. See {onlyInitializing}.
     */
    function _checkInitializing() internal view virtual {
        if (!_isInitializing()) {
            revert NotInitializing();
        }
    }

    /**
     * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
     * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
     * to any version. It is recommended to use this to lock implementation contracts that are designed to be called
     * through proxies.
     *
     * Emits an {Initialized} event the first time it is successfully executed.
     */
    function _disableInitializers() internal virtual {
        // solhint-disable-next-line var-name-mixedcase
        InitializableStorage storage $ = _getInitializableStorage();

        if ($._initializing) {
            revert InvalidInitialization();
        }
        if ($._initialized != type(uint64).max) {
            $._initialized = type(uint64).max;
            emit Initialized(type(uint64).max);
        }
    }

    /**
     * @dev Returns the highest version that has been initialized. See {reinitializer}.
     */
    function _getInitializedVersion() internal view returns (uint64) {
        return _getInitializableStorage()._initialized;
    }

    /**
     * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
     */
    function _isInitializing() internal view returns (bool) {
        return _getInitializableStorage()._initializing;
    }

    /**
     * @dev Pointer to storage slot. Allows integrators to override it with a custom storage location.
     *
     * NOTE: Consider following the ERC-7201 formula to derive storage locations.
     */
    function _initializableStorageSlot() internal pure virtual returns (bytes32) {
        return INITIALIZABLE_STORAGE;
    }

    /**
     * @dev Returns a pointer to the storage namespace.
     */
    // solhint-disable-next-line var-name-mixedcase
    function _getInitializableStorage() private pure returns (InitializableStorage storage $) {
        bytes32 slot = _initializableStorageSlot();
        assembly {
            $.slot := slot
        }
    }
}
"
    },
    "dependencies/@openzeppelin-contracts-upgradeable-5.3.0/contracts/token/ERC721/ERC721Upgradeable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC721/ERC721.sol)

pragma solidity ^0.8.20;

import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {IERC721Metadata} from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol";
import {ERC721Utils} from "@openzeppelin/contracts/token/ERC721/utils/ERC721Utils.sol";
import {ContextUpgradeable} from "../../utils/ContextUpgradeable.sol";
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {ERC165Upgradeable} from "../../utils/introspection/ERC165Upgradeable.sol";
import {IERC721Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol";
import {Initializable} from "../../proxy/utils/Initializable.sol";

/**
 * @dev Implementation of https://eips.ethereum.org/EIPS/eip-721[ERC-721] Non-Fungible Token Standard, including
 * the Metadata extension, but not including the Enumerable extension, which is available separately as
 * {ERC721Enumerable}.
 */
abstract contract ERC721Upgradeable is Initializable, ContextUpgradeable, ERC165Upgradeable, IERC721, IERC721Metadata, IERC721Errors {
    using Strings for uint256;

    /// @custom:storage-location erc7201:openzeppelin.storage.ERC721
    struct ERC721Storage {
        // Token name
        string _name;

        // Token symbol
        string _symbol;

        mapping(uint256 tokenId => address) _owners;

        mapping(address owner => uint256) _balances;

        mapping(uint256 tokenId => address) _tokenApprovals;

        mapping(address owner => mapping(address operator => bool)) _operatorApprovals;
    }

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ERC721")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant ERC721StorageLocation = 0x80bb2b638cc20bc4d0a60d66940f3ab4a00c1d7b313497ca82fb0b4ab0079300;

    function _getERC721Storage() private pure returns (ERC721Storage storage $) {
        assembly {
            $.slot := ERC721StorageLocation
        }
    }

    /**
     * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection.
     */
    function __ERC721_init(string memory name_, string memory symbol_) internal onlyInitializing {
        __ERC721_init_unchained(name_, symbol_);
    }

    function __ERC721_init_unchained(string memory name_, string memory symbol_) internal onlyInitializing {
        ERC721Storage storage $ = _getERC721Storage();
        $._name = name_;
        $._symbol = symbol_;
    }

    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165Upgradeable, IERC165) returns (bool) {
        return
            interfaceId == type(IERC721).interfaceId ||
            interfaceId == type(IERC721Metadata).interfaceId ||
            super.supportsInterface(interfaceId);
    }

    /**
     * @dev See {IERC721-balanceOf}.
     */
    function balanceOf(address owner) public view virtual returns (uint256) {
        ERC721Storage storage $ = _getERC721Storage();
        if (owner == address(0)) {
            revert ERC721InvalidOwner(address(0));
        }
        return $._balances[owner];
    }

    /**
     * @dev See {IERC721-ownerOf}.
     */
    function ownerOf(uint256 tokenId) public view virtual returns (address) {
        return _requireOwned(tokenId);
    }

    /**
     * @dev See {IERC721Metadata-name}.
     */
    function name() public view virtual returns (string memory) {
        ERC721Storage storage $ = _getERC721Storage();
        return $._name;
    }

    /**
     * @dev See {IERC721Metadata-symbol}.
     */
    function symbol() public view virtual returns (string memory) {
        ERC721Storage storage $ = _getERC721Storage();
        return $._symbol;
    }

    /**
     * @dev See {IERC721Metadata-tokenURI}.
     */
    function tokenURI(uint256 tokenId) public view virtual returns (string memory) {
        _requireOwned(tokenId);

        string memory baseURI = _baseURI();
        return bytes(baseURI).length > 0 ? string.concat(baseURI, tokenId.toString()) : "";
    }

    /**
     * @dev Base URI for computing {tokenURI}. If set, the resulting URI for each
     * token will be the concatenation of the `baseURI` and the `tokenId`. Empty
     * by default, can be overridden in child contracts.
     */
    function _baseURI() internal view virtual returns (string memory) {
        return "";
    }

    /**
     * @dev See {IERC721-approve}.
     */
    function approve(address to, uint256 tokenId) public virtual {
        _approve(to, tokenId, _msgSender());
    }

    /**
     * @dev See {IERC721-getApproved}.
     */
    function getApproved(uint256 tokenId) public view virtual returns (address) {
        _requireOwned(tokenId);

        return _getApproved(tokenId);
    }

    /**
     * @dev See {IERC721-setApprovalForAll}.
     */
    function setApprovalForAll(address operator, bool approved) public virtual {
        _setApprovalForAll(_msgSender(), operator, approved);
    }

    /**
     * @dev See {IERC721-isApprovedForAll}.
     */
    function isApprovedForAll(address owner, address operator) public view virtual returns (bool) {
        ERC721Storage storage $ = _getERC721Storage();
        return $._operatorApprovals[owner][operator];
    }

    /**
     * @dev See {IERC721-transferFrom}.
     */
    function transferFrom(address from, address to, uint256 tokenId) public virtual {
        if (to == address(0)) {
            revert ERC721InvalidReceiver(address(0));
        }
        // Setting an "auth" arguments enables the `_isAuthorized` check which verifies that the token exists
        // (from != 0). Therefore, it is not needed to verify that the return value is not 0 here.
        address previousOwner = _update(to, tokenId, _msgSender());
        if (previousOwner != from) {
            revert ERC721IncorrectOwner(from, tokenId, previousOwner);
        }
    }

    /**
     * @dev See {IERC721-safeTransferFrom}.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId) public {
        safeTransferFrom(from, to, tokenId, "");
    }

    /**
     * @dev See {IERC721-safeTransferFrom}.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public virtual {
        transferFrom(from, to, tokenId);
        ERC721Utils.checkOnERC721Received(_msgSender(), from, to, tokenId, data);
    }

    /**
     * @dev Returns the owner of the `tokenId`. Does NOT revert if token doesn't exist
     *
     * IMPORTANT: Any overrides to this function that add ownership of tokens not tracked by the
     * core ERC-721 logic MUST be matched with the use of {_increaseBalance} to keep balances
     * consistent with ownership. The invariant to preserve is that for any address `a` the value returned by
     * `balanceOf(a)` must be equal to the number of tokens such that `_ownerOf(tokenId)` is `a`.
     */
    function _ownerOf(uint256 tokenId) internal view virtual returns (address) {
        ERC721Storage storage $ = _getERC721Storage();
        return $._owners[tokenId];
    }

    /**
     * @dev Returns the approved address for `tokenId`. Returns 0 if `tokenId` is not minted.
     */
    function _getApproved(uint256 tokenId) internal view virtual returns (address) {
        ERC721Storage storage $ = _getERC721Storage();
        return $._tokenApprovals[tokenId];
    }

    /**
     * @dev Returns whether `spender` is allowed to manage `owner`'s tokens, or `tokenId` in
     * particular (ignoring whether it is owned by `owner`).
     *
     * WARNING: This function assumes that `owner` is the actual owner of `tokenId` and does not verify this
     * assumption.
     */
    function _isAuthorized(address owner, address spender, uint256 tokenId) internal view virtual returns (bool) {
        return
            spender != address(0) &&
            (owner == spender || isApprovedForAll(owner, spender) || _getApproved(tokenId) == spender);
    }

    /**
     * @dev Checks if `spender` can operate on `tokenId`, assuming the provided `owner` is the actual owner.
     * Reverts if:
     * - `spender` does not have approval from `owner` for `tokenId`.
     * - `spender` does not have approval to manage all of `owner`'s assets.
     *
     * WARNING: This function assumes that `owner` is the actual owner of `tokenId` and does not verify this
     * assumption.
     */
    function _checkAuthorized(address owner, address spender, uint256 tokenId) internal view virtual {
        if (!_isAuthorized(owner, spender, tokenId)) {
            if (owner == address(0)) {
                revert ERC721NonexistentToken(tokenId);
            } else {
                revert ERC721InsufficientApproval(spender, tokenId);
            }
        }
    }

    /**
     * @dev Unsafe write access to the balances, used by extensions that "mint" tokens using an {ownerOf} override.
     *
     * NOTE: the value is limited to type(uint128).max. This protect against _balance overflow. It is unrealistic that
     * a uint256 would ever overflow from increments when these increments are bounded to uint128 values.
     *
     * WARNING: Increasing an account's balance using this function tends to be paired with an override of the
     * {_ownerOf} function to resolve the ownership of the corresponding tokens so that balances and ownership
     * remain consistent with one another.
     */
    function _increaseBalance(address account, uint128 value) internal virtual {
        ERC721Storage storage $ = _getERC721Storage();
        unchecked {
            $._balances[account] += value;
        }
    }

    /**
     * @dev Transfers `tokenId` from its current owner to `to`, or alternatively mints (or burns) if the current owner
     * (or `to`) is the zero address. Returns the owner of the `tokenId` before the update.
     *
     * The `auth` argument is optional. If the value passed is non 0, then this function will check that
     * `auth` is either the owner of the token, or approved to operate on the token (by the owner).
     *
     * Emits a {Transfer} event.
     *
     * NOTE: If overriding this function in a way that tracks balances, see also {_increaseBalance}.
     */
    function _update(address to, uint256 tokenId, address auth) internal virtual returns (address) {
        ERC721Storage storage $ = _getERC721Storage();
        address from = _ownerOf(tokenId);

        // Perform (optional) operator check
        if (auth != address(0)) {
            _checkAuthorized(from, auth, tokenId);
        }

        // Execute the update
        if (from != address(0)) {
            // Clear approval. No need to re-authorize or emit the Approval event
            _approve(address(0), tokenId, address(0), false);

            unchecked {
                $._balances[from] -= 1;
            }
        }

        if (to != address(0)) {
            unchecked {
                $._balances[to] += 1;
            }
        }

        $._owners[tokenId] = to;

        emit Transfer(from, to, tokenId);

        return from;
    }

    /**
     * @dev Mints `tokenId` and transfers it to `to`.
     *
     * WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible
     *
     * Requirements:
     *
     * - `tokenId` must not exist.
     * - `to` cannot be the zero address.
     *
     * Emits a {Transfer} event.
     */
    function _mint(address to, uint256 tokenId) internal {
        if (to == address(0)) {
            revert ERC721InvalidReceiver(address(0));
        }
        address previousOwner = _update(to, tokenId, address(0));
        if (previousOwner != address(0)) {
            revert ERC721InvalidSender(address(0));
        }
    }

    /**
     * @dev Mints `tokenId`, transfers it to `to` and checks for `to` acceptance.
     *
     * Requirements:
     *
     * - `tokenId` must not exist.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function _safeMint(address to, uint256 tokenId) internal {
        _safeMint(to, tokenId, "");
    }

    /**
     * @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is
     * forwarded in {IERC721Receiver-onERC721Received} to contract recipients.
     */
    function _safeMint(address to, uint256 tokenId, bytes memory data) internal virtual {
        _mint(to, tokenId);
        ERC721Utils.checkOnERC721Received(_msgSender(), address(0), to, tokenId, data);
    }

    /**
     * @dev Destroys `tokenId`.
     * The approval is cleared when the token is burned.
     * This is an internal function that does not check if the sender is authorized to operate on the token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     *
     * Emits a {Transfer} event.
     */
    function _burn(uint256 tokenId) internal {
        address previousOwner = _update(address(0), tokenId, address(0));
        if (previousOwner == address(0)) {
            revert ERC721NonexistentToken(tokenId);
        }
    }

    /**
     * @dev Transfers `tokenId` from `from` to `to`.
     *  As opposed to {transferFrom}, this imposes no restrictions on msg.sender.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - `tokenId` token must be owned by `from`.
     *
     * Emits a {Transfer} event.
     */
    function _transfer(address from, address to, uint256 tokenId) internal {
        if (to == address(0)) {
            revert ERC721InvalidReceiver(address(0));
        }
        address previousOwner = _update(to, tokenId, address(0));
        if (previousOwner == address(0)) {
            revert ERC721NonexistentToken(tokenId);
        } else if (previousOwner != from) {
            revert ERC721IncorrectOwner(from, tokenId, previousOwner);
        }
    }

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`, checking that contract recipients
     * are aware of the ERC-721 standard to prevent tokens from being forever locked.
     *
     * `data` is additional data, it has no specified format and it is sent in call to `to`.
     *
     * This internal function is like {safeTransferFrom} in the sense that it invokes
     * {IERC721Receiver-onERC721Received} on the receiver, and can be used to e.g.
     * implement alternative mechanisms to perform token transfer, such as signature-based.
     *
     * Requirements:
     *
     * - `tokenId` token must exist and be owned by `from`.
     * - `to` cannot be the zero address.
     * - `from` cannot be the zero address.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function _safeTransfer(address from, address to, uint256 tokenId) internal {
        _safeTransfer(from, to, tokenId, "");
    }

    /**
     * @dev Same as {xref-ERC721-_safeTransfer-address-address-uint256-}[`_safeTransfer`], with an additional `data` parameter which is
     * forwarded in {IERC721Receiver-onERC721Received} to contract recipients.
     */
    function _safeTransfer(address from, address to, uint256 tokenId, bytes memory data) internal virtual {
        _transfer(from, to, tokenId);
        ERC721Utils.checkOnERC721Received(_msgSender(), from, to, tokenId, data);
    }

    /**
     * @dev Approve `to` to operate on `tokenId`
     *
     * The `auth` argument is optional. If the value passed is non 0, then this function will check that `auth` is
     * either the owner of the token, or approved to operate on all tokens held by this owner.
     *
     * Emits an {Approval} event.
     *
     * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.
     */
    function _approve(address to, uint256 tokenId, address auth) internal {
        _approve(to, tokenId, auth, true);
    }

    /**
     * @dev Variant of `_approve` with an optional flag to enable or disable the {Approval} event. The event is not
     * emitted in the context of transfers.
     */
    function _approve(address to, uint256 tokenId, address auth, bool emitEvent) internal virtual {
        ERC721Storage storage $ = _getERC721Storage();
        // Avoid reading the owner unless necessary
        if (emitEvent || auth != address(0)) {
            address owner = _requireOwned(tokenId);

            // We do not use _isAuthorized because single-token approvals should not be able to call approve
            if (auth != address(0) && owner != auth && !isApprovedForAll(owner, auth)) {
                revert ERC721InvalidApprover(auth);
            }

            if (emitEvent) {
                emit Approval(owner, to, tokenId);
            }
        }

        $._tokenApprovals[tokenId] = to;
    }

    /**
     * @dev Approve `operator` to operate on all of `owner` tokens
     *
     * Requirements:
     * - operator can't be the address zero.
     *
     * Emits an {ApprovalForAll} event.
     */
    function _setApprovalForAll(address owner, address operator, bool approved

Tags:
ERC20, ERC721, ERC165, Multisig, Non-Fungible, Upgradeable, Multi-Signature, Factory|addr:0x1cc2f2cfa360b7e12a1d77f66a7c6860dfc6be46|verified:true|block:23570869|tx:0xe7e6d4198176a851dc569f9d6dd0433e4c42f07e396ed3d9ffb4c831f42bc1b8|first_check:1760426246

Submitted on: 2025-10-14 09:17:26

Comments

Log in to comment.

No comments yet.