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
Submitted on: 2025-10-14 09:17:26
Comments
Log in to comment.
No comments yet.