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/contracts/intent/EverclearSpokeV6.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
/*
Coded for Everclear with ♥ by
░██╗░░░░░░░██╗░█████╗░███╗░░██╗██████╗░███████╗██████╗░██╗░░░░░░█████╗░███╗░░██╗██████╗░
░██║░░██╗░░██║██╔══██╗████╗░██║██╔══██╗██╔════╝██╔══██╗██║░░░░░██╔══██╗████╗░██║██╔══██╗
░╚██╗████╗██╔╝██║░░██║██╔██╗██║██║░░██║█████╗░░██████╔╝██║░░░░░███████║██╔██╗██║██║░░██║
░░████╔═████║░██║░░██║██║╚████║██║░░██║██╔══╝░░██╔══██╗██║░░░░░██╔══██║██║╚████║██║░░██║
░░╚██╔╝░╚██╔╝░╚█████╔╝██║░╚███║██████╔╝███████╗██║░░██║███████╗██║░░██║██║░╚███║██████╔╝
░░░╚═╝░░░╚═╝░░░╚════╝░╚═╝░░╚══╝╚═════╝░╚══════╝╚═╝░░╚═╝╚══════╝╚═╝░░╚═╝╚═╝░░╚══╝╚═════╝░
https://defi.sucks
*/
import {OwnableUpgradeable} from '@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol';
import {UUPSUpgradeable} from '@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol';
import {NoncesUpgradeable} from '@openzeppelin/contracts-upgradeable/utils/NoncesUpgradeable.sol';
import {ERC20} from '@openzeppelin/contracts/token/ERC20/ERC20.sol';
import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import {SafeERC20} from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';
import {ECDSA} from '@openzeppelin/contracts/utils/cryptography/ECDSA.sol';
import {MessageHashUtils} from '@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol';
import {AssetUtils} from 'contracts/common/AssetUtils.sol';
import {Constants as Common} from 'contracts/common/Constants.sol';
import {MessageLibV2} from 'contracts/common/MessageLibV2.sol';
import {QueueLibV2} from 'contracts/common/QueueLibV2.sol';
import {TypeCasts} from 'contracts/common/TypeCasts.sol';
import {Constants} from 'contracts/intent/lib/Constants.sol';
import {IEverclearV2} from 'interfaces/common/IEverclearV2.sol';
import {IMessageReceiver} from 'interfaces/common/IMessageReceiver.sol';
import {IPermit2} from 'interfaces/common/IPermit2.sol';
import {ISettlementModule} from 'interfaces/common/ISettlementModule.sol';
import {IEverclearSpokeV6} from 'interfaces/intent/IEverclearSpokeV6.sol';
import {ISpokeGateway} from 'interfaces/intent/ISpokeGateway.sol';
import {SpokeStorageV6} from 'contracts/intent/SpokeStorageV6.sol';
/**
* @title EverclearSpoke
* @notice Spoke contract for Everclear
*/
contract EverclearSpokeV6 is
SpokeStorageV6,
UUPSUpgradeable,
OwnableUpgradeable,
NoncesUpgradeable,
IEverclearSpokeV6,
IMessageReceiver
{
using QueueLibV2 for QueueLibV2.IntentQueue;
using QueueLibV2 for QueueLibV2.FillQueue;
using SafeERC20 for IERC20;
using TypeCasts for address;
using TypeCasts for bytes32;
constructor() {
_disableInitializers();
}
/*///////////////////////////////////////////////////////////////
EXTERNAL FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @inheritdoc IEverclearSpokeV6
function pause() external hasPauseAccess {
paused = true;
emit Paused();
}
/// @inheritdoc IEverclearSpokeV6
function unpause() external hasPauseAccess {
paused = false;
emit Unpaused();
}
/// @inheritdoc IEverclearSpokeV6
function setStrategyForAsset(
address _asset,
IEverclearV2.Strategy _strategy
) external onlyOwner {
strategies[_asset] = _strategy;
emit StrategySetForAsset(_asset, _strategy);
}
/// @inheritdoc IEverclearSpokeV6
function setModuleForStrategy(
IEverclearV2.Strategy _strategy,
ISettlementModule _module
) external onlyOwner {
modules[_strategy] = _module;
emit ModuleSetForStrategy(_strategy, _module);
}
/// @inheritdoc IEverclearSpokeV6
function updateSecurityModule(
address _newSecurityModule
) external onlyOwner {
gateway.updateSecurityModule(_newSecurityModule);
}
/// @inheritdoc IEverclearSpokeV6
function updateFeeAdapter(
address _feeAdapter
) external onlyOwner {
feeAdapter = _feeAdapter;
emit FeeAdapterUpdated(_feeAdapter);
}
/// @inheritdoc IMessageReceiver
function receiveMessage(
bytes calldata
) external {
_delegate(messageReceiver);
}
/// @inheritdoc IEverclearSpokeV6
function newIntent(
uint32[] memory _destinations,
bytes32 _receiver,
address _inputAsset,
bytes32 _outputAsset,
uint256 _amount,
uint256 _amountOutMin,
uint48 _ttl,
bytes calldata _data
) external whenNotPaused onlyFeeAdapter returns (bytes32 _intentId, Intent memory _intent) {
if (_destinations.length > 10) revert EverclearSpoke_NewIntent_InvalidIntent();
(_intentId, _intent) = _newIntent({
_destinations: _destinations,
_receiver: _receiver,
_inputAsset: _inputAsset,
_outputAsset: _outputAsset,
_amount: _amount,
_amountOutMin: _amountOutMin,
_ttl: _ttl,
_data: _data,
_usesPermit2: false
});
}
/// @inheritdoc IEverclearSpokeV6
function newIntent(
uint32[] memory _destinations,
address _receiver,
address _inputAsset,
address _outputAsset,
uint256 _amount,
uint256 _amountOutMin,
uint48 _ttl,
bytes calldata _data
) external whenNotPaused onlyFeeAdapter returns (bytes32 _intentId, Intent memory _intent) {
if (_destinations.length > 10) revert EverclearSpoke_NewIntent_InvalidIntent();
(_intentId, _intent) = _newIntent({
_destinations: _destinations,
_receiver: _receiver.toBytes32(),
_inputAsset: _inputAsset,
_outputAsset: _outputAsset.toBytes32(),
_amount: _amount,
_amountOutMin: _amountOutMin,
_ttl: _ttl,
_data: _data,
_usesPermit2: false
});
}
/// @inheritdoc IEverclearSpokeV6
function batchFillIntent(
Intent[] calldata _intents,
uint256[] calldata _amountOut,
bytes32[] calldata _receivers,
uint32[][] calldata _destinations,
bytes calldata _signature,
bool _pullFunds
) external whenNotPaused returns (FillMessage[] memory _fillMessages) {
if (
_intents.length != _amountOut.length || _intents.length != _receivers.length
|| _intents.length != _destinations.length
) {
revert EverclearSpoke_FillIntent_InvalidArrayLengths();
}
bytes memory _data = abi.encode(
BATCH_FILL_INTENT_TYPEHASH,
keccak256(abi.encode(block.chainid, address(this))),
msg.sender,
_intents,
_amountOut,
_receivers,
_destinations
);
_verifySignature(fillSigner, _data, _signature);
_fillMessages = new FillMessage[](_intents.length);
for (uint256 i; i < _intents.length; i++) {
_fillMessages[i] =
_fillIntent(_intents[i], msg.sender, _receivers[i], _amountOut[i], _destinations[i], _pullFunds);
}
}
/// @inheritdoc IEverclearSpokeV6
function fillIntent(
Intent calldata _intent,
uint256 _amountOut,
bytes32 _receiver,
uint32[] memory _destinations,
bytes calldata _signature,
bool _pullFunds
) external whenNotPaused returns (FillMessage memory _fillMessage) {
bytes32 _domain = keccak256(abi.encode(block.chainid, address(this)));
bytes memory _data =
abi.encode(FILL_INTENT_TYPEHASH, _domain, msg.sender, _intent, _amountOut, _receiver, _destinations);
_verifySignature(fillSigner, _data, _signature);
_fillMessage = _fillIntent(_intent, msg.sender, _receiver, _amountOut, _destinations, _pullFunds);
}
/// @inheritdoc IEverclearSpokeV6
function processIntentQueue(
Intent[] calldata _intents,
uint256 _dynamicGasLimit
) external payable whenNotPaused {
_checkDynamicGasLimit(_dynamicGasLimit);
(bytes memory _batchIntentmessage, uint256 _firstIdx) = _processIntentQueue(_intents);
(bytes32 _messageId, uint256 _feeSpent) =
gateway.sendMessage{value: msg.value}(EVERCLEAR, _batchIntentmessage, _dynamicGasLimit);
emit IntentQueueProcessed(_messageId, _firstIdx, _firstIdx + _intents.length, _feeSpent);
}
/// @inheritdoc IEverclearSpokeV6
function processFillQueue(
uint32 _amount,
uint256 _dynamicGasLimit
) external payable whenNotPaused {
_checkDynamicGasLimit(_dynamicGasLimit);
(bytes memory _batchFillMessage, uint256 _firstIdx) = _processFillQueue(_amount);
(bytes32 _messageId, uint256 _feeSpent) =
gateway.sendMessage{value: msg.value}(EVERCLEAR, _batchFillMessage, _dynamicGasLimit);
emit FillQueueProcessed(_messageId, _firstIdx, _firstIdx + _amount, _feeSpent);
}
/// @inheritdoc IEverclearSpokeV6
function processIntentQueueViaRelayer(
uint32 _domain,
Intent[] calldata _intents,
address _relayer,
uint256 _ttl,
uint256 _nonce,
uint256 _dynamicGasLimit,
bytes calldata _signature
) external whenNotPaused {
_checkDynamicGasLimit(_dynamicGasLimit);
uint32 _amount = uint32(_intents.length);
bytes memory _data =
abi.encode(PROCESS_INTENT_QUEUE_VIA_RELAYER_TYPEHASH, _domain, _amount, _relayer, _ttl, _nonce, _dynamicGasLimit);
_verifySignature(lighthouse, _data, _nonce, _signature);
_processQueueChecks(_domain, _relayer, _ttl);
(bytes memory _batchIntentmessage, uint256 _firstIdx) = _processIntentQueue(_intents);
uint256 _fee = gateway.quoteMessage(EVERCLEAR, _batchIntentmessage, _dynamicGasLimit);
(bytes32 _messageId, uint256 _feeSpent) =
gateway.sendMessage(EVERCLEAR, _batchIntentmessage, _fee, _dynamicGasLimit);
emit IntentQueueProcessed(_messageId, _firstIdx, _firstIdx + _amount, _feeSpent);
}
/// @inheritdoc IEverclearSpokeV6
function processFillQueueViaRelayer(
uint32 _domain,
uint32 _amount,
address _relayer,
uint256 _ttl,
uint256 _nonce,
uint256 _dynamicGasLimit,
bytes calldata _signature
) external whenNotPaused {
_checkDynamicGasLimit(_dynamicGasLimit);
bytes memory _data =
abi.encode(PROCESS_FILL_QUEUE_VIA_RELAYER_TYPEHASH, _domain, _amount, _relayer, _ttl, _nonce, _dynamicGasLimit);
_verifySignature(lighthouse, _data, _nonce, _signature);
_processQueueChecks(_domain, _relayer, _ttl);
(bytes memory _batchFillMessage, uint256 _firstIdx) = _processFillQueue(_amount);
uint256 _fee = gateway.quoteMessage(EVERCLEAR, _batchFillMessage, _dynamicGasLimit);
(bytes32 _messageId, uint256 _feeSpent) = gateway.sendMessage(EVERCLEAR, _batchFillMessage, _fee, _dynamicGasLimit);
emit FillQueueProcessed(_messageId, _firstIdx, _firstIdx + _amount, _feeSpent);
}
/// @inheritdoc IEverclearSpokeV6
function deposit(
address _asset,
uint256 _amount
) external whenNotPaused {
_pullTokens(msg.sender, _asset, _amount);
balances[_asset.toBytes32()][msg.sender.toBytes32()] += _amount;
emit Deposited(msg.sender, _asset, _amount);
}
/// @inheritdoc IEverclearSpokeV6
function withdraw(
address _asset,
uint256 _amount
) external whenNotPaused {
balances[_asset.toBytes32()][msg.sender.toBytes32()] -= _amount;
_pushTokens(msg.sender, _asset, _amount);
emit Withdrawn(msg.sender, _asset, _amount);
}
/// @inheritdoc IEverclearSpokeV6
function updateGateway(
address _newGateway
) external onlyOwner {
address _oldGateway = address(gateway);
gateway = ISpokeGateway(_newGateway);
emit GatewayUpdated(_oldGateway, _newGateway);
}
/// @inheritdoc IEverclearSpokeV6
function updateMessageReceiver(
address _newMessageReceiver
) external onlyOwner {
address _oldMessageReceiver = messageReceiver;
messageReceiver = _newMessageReceiver;
emit MessageReceiverUpdated(_oldMessageReceiver, _newMessageReceiver);
}
/// @inheritdoc IEverclearSpokeV6
function updateMessageGasLimit(
uint256 _newGasLimit
) external onlyOwner {
uint256 _oldGasLimit = messageGasLimit;
messageGasLimit = _newGasLimit;
emit MessageGasLimitUpdated(_oldGasLimit, _newGasLimit);
}
/// @inheritdoc IEverclearSpokeV6
function updateFillSigner(
address _feeSigner
) external onlyOwner {
address _oldFillSigner = fillSigner;
fillSigner = _feeSigner;
emit FillSignerUpdated(_oldFillSigner, _feeSigner);
}
/// @inheritdoc IEverclearSpokeV6
function executeIntentCalldata(
Intent calldata _intent
) external whenNotPaused validDestination(_intent) {
bytes32 _intentId = keccak256(abi.encode(_intent));
if (status[_intentId] != IntentStatus.SETTLED) {
revert EverclearSpoke_ExecuteIntentCalldata_InvalidStatus(_intentId);
}
// internal method will revert if it fails
_executeCalldata(_intentId, _intent.data);
status[_intentId] = IntentStatus.SETTLED_AND_MANUALLY_EXECUTED;
}
/*///////////////////////////////////////////////////////////////
INITIALIZER
//////////////////////////////////////////////////////////////*/
/// @inheritdoc IEverclearSpokeV6
function initialize(
address _feeAdapter,
address _messageReceiver,
address _fillSigner
) public reinitializer(4) {
if (!_isEmpty(deprecated_intentQueue.first, deprecated_intentQueue.last)) {
revert EverclearSpoke_Initialize_IntentQueueNotEmpty();
}
if (!_isEmpty(deprecated_fillQueue.first, deprecated_fillQueue.last)) {
revert EverclearSpoke_Initialize_FillQueueNotEmpty();
}
// Intialize the queues for new storage vars
intentQueue.first = 1;
fillQueue.first = 1;
// Updating to feeAdapterV2 and messageReceiverV2
feeAdapter = _feeAdapter;
messageReceiver = _messageReceiver;
fillSigner = _fillSigner;
}
/*///////////////////////////////////////////////////////////////
INTERNAL FUNCTIONS
//////////////////////////////////////////////////////////////*/
/**
* @notice Creates a new intent
* @param _destinations The destination chains of the intent
* @param _receiver The destination address of the intent
* @param _inputAsset The asset address on origin
* @param _outputAsset The asset address on destination
* @param _amount The amount of the asset
* @param _amountOutMin The minimum amount out expected from the solver
* @param _ttl The time to live of the intent
* @param _data The data of the intent
* @param _usesPermit2 If the intent uses permit2
* @return _intentId The ID of the intent
* @return _intent The intent structure
*/
function _newIntent(
uint32[] memory _destinations,
bytes32 _receiver,
address _inputAsset,
bytes32 _outputAsset,
uint256 _amount,
uint256 _amountOutMin,
uint48 _ttl,
bytes calldata _data,
bool _usesPermit2
) internal returns (bytes32 _intentId, Intent memory _intent) {
if (_destinations.length == 1) {
// output asset should not be null if the intent has a single destination and ttl != 0
if (_ttl != 0 && _outputAsset == 0) revert EverclearSpoke_NewIntent_OutputAssetNull();
} else {
// output asset should be null if the intent has multiple destinations
// ttl should be 0 if the intent has multiple destinations
if (_ttl != 0 || _outputAsset != 0) revert EverclearSpoke_NewIntent_OutputAssetNotNull();
}
if (_data.length > Common.MAX_CALLDATA_SIZE) {
revert EverclearSpoke_NewIntent_CalldataExceedsLimit();
}
uint256 _normalizedAmount =
AssetUtils.normalizeDecimals(ERC20(_inputAsset).decimals(), Common.DEFAULT_NORMALIZED_DECIMALS, _amount);
// check normalized amount before pulling tokens
if (_normalizedAmount == 0) {
revert EverclearSpoke_NewIntent_ZeroAmount();
}
if (!_usesPermit2) {
Strategy _strategy = strategies[_inputAsset];
if (_strategy == Strategy.DEFAULT) {
_pullTokens(msg.sender, _inputAsset, _amount);
} else {
ISettlementModule _module = modules[_strategy];
_module.handleBurnStrategy(_inputAsset, msg.sender, _amount, '');
}
}
_intent = Intent({
initiator: msg.sender.toBytes32(),
receiver: _receiver,
inputAsset: _inputAsset.toBytes32(),
outputAsset: _outputAsset,
origin: DOMAIN,
nonce: ++nonce,
timestamp: uint48(block.timestamp),
ttl: _ttl,
amount: _normalizedAmount,
amountOutMin: _amountOutMin,
destinations: _destinations,
data: _data
});
_intentId = keccak256(abi.encode(_intent));
intentQueue.enqueueIntent(_intentId);
status[_intentId] = IntentStatus.ADDED;
emit IntentAdded(_intentId, intentQueue.last, _intent);
}
/**
* @notice Fills an intent
* @param _intent The intent structure
* @param _solver The solver address
* @param _amountOut The amount out being sent
* @param _pull Should pull from wallet or use deposited balance
* @return _fillMessage The fill message
*/
function _fillIntent(
Intent calldata _intent,
address _solver,
bytes32 _receiver,
uint256 _amountOut,
uint32[] memory _destinations,
bool _pull
) internal validDestination(_intent) returns (FillMessage memory _fillMessage) {
bytes32 _intentId = keccak256(abi.encode(_intent));
if (block.timestamp >= _intent.timestamp + _intent.ttl) {
revert EverclearSpoke_FillIntent_IntentExpired(_intentId);
}
if (_amountOut < _intent.amountOutMin) {
revert EverclearSpoke_FillIntent_AmountOutInvalid(_amountOut, _intent.amountOutMin);
}
if (_destinations.length == 0 || _destinations.length > 10) {
revert EverclearSpoke_FillIntent_InvalidDestinationArray();
}
if (status[_intentId] != IntentStatus.NONE && status[_intentId] != IntentStatus.ADDED) {
revert EverclearSpoke_FillIntent_InvalidStatus(_intentId);
}
if (!_pull) {
if (balances[_intent.outputAsset][_solver.toBytes32()] < _amountOut) {
revert EverclearSpoke_FillIntent_InsufficientFunds(
_amountOut, balances[_intent.outputAsset][_solver.toBytes32()]
);
}
balances[_intent.outputAsset][_solver.toBytes32()] -= _amountOut;
}
status[_intentId] = IntentStatus.FILLED;
if (_intent.receiver != 0 && _intent.outputAsset != 0 && _amountOut != 0) {
_pull
? IERC20(_intent.outputAsset.toAddress()).safeTransferFrom(_solver, _intent.receiver.toAddress(), _amountOut)
: _pushTokens(_intent.receiver.toAddress(), _intent.outputAsset.toAddress(), _amountOut);
}
if (keccak256(_intent.data) != Constants.EMPTY_HASH) {
_executeCalldata(_intentId, _intent.data);
}
_fillMessage = FillMessage({
intentId: _intentId,
receiver: _receiver,
intentInputAsset: _intent.inputAsset,
intentOrigin: _intent.origin,
amountOut: _amountOut,
destinations: _destinations,
executionTimestamp: uint48(block.timestamp)
});
fillQueue.enqueueFill(_fillMessage);
emit IntentFilled(_intentId, _solver, _receiver, _amountOut, fillQueue.last, _intent);
}
/**
* @notice Verifies a signature
* @param _signer The signer of the message
* @param _data The data of the message
* @param _nonce The nonce of the message
* @param _signature The signature of the message
*/
function _verifySignature(
address _signer,
bytes memory _data,
uint256 _nonce,
bytes calldata _signature
) internal {
bytes32 _hash = keccak256(_data);
address _recoveredSigner = ECDSA.recover(MessageHashUtils.toEthSignedMessageHash(_hash), _signature);
if (_recoveredSigner != _signer) {
revert EverclearSpoke_InvalidSignature();
}
_useCheckedNonce(_recoveredSigner, _nonce);
}
/**
* @notice Verifies a signature
* @param _signer The signer of the message
* @param _data The data of the message
* @param _signature The signature of the message
*/
function _verifySignature(
address _signer,
bytes memory _data,
bytes calldata _signature
) internal {
bytes32 _hash = keccak256(_data);
address _recoveredSigner = ECDSA.recover(MessageHashUtils.toEthSignedMessageHash(_hash), _signature);
if (_recoveredSigner != _signer) {
revert EverclearSpoke_InvalidFillSignature();
}
}
/**
* @notice Process the intent queue messages to send a batching message to the transport layer
* @param _intents The intents to process, the order of the intents must match the order in the queue
* @return _batchIntentmessage The batched intent message
* @return _firstIdx The first index of the intents processed
*/
function _processIntentQueue(
Intent[] calldata _intents
)
internal
validQueueAmount(intentQueue.first, intentQueue.last, _intents.length)
returns (bytes memory _batchIntentmessage, uint256 _firstIdx)
{
_firstIdx = intentQueue.first;
for (uint32 _i; _i < _intents.length; _i++) {
bytes32 _queueIntentId = intentQueue.dequeueIntent();
bytes32 _intentHash = keccak256(abi.encode(_intents[_i]));
// verify the intent and its position in the queue
if (_queueIntentId != _intentHash) {
revert EverclearSpoke_ProcessIntentQueue_NotFound(_intentHash, _i);
}
}
_batchIntentmessage = MessageLibV2.formatIntentMessageBatch(_intents);
}
/**
* @notice Process the fill queue messages to send a batching message to the transport layer
* @param _amount The amount of messages to process
* @return _batchFillMessage The batched fill message
* @return _firstIdx The first index of the fills processed
*/
function _processFillQueue(
uint32 _amount
)
internal
validQueueAmount(fillQueue.first, fillQueue.last, _amount)
returns (bytes memory _batchFillMessage, uint256 _firstIdx)
{
_firstIdx = fillQueue.first;
FillMessage[] memory _fillMessages = new FillMessage[](_amount);
for (uint32 _i; _i < _amount; _i++) {
_fillMessages[_i] = fillQueue.dequeueFill();
}
_batchFillMessage = MessageLibV2.formatFillMessageBatch(_fillMessages);
}
/**
* @notice Executes the calldata of an intent
* @param _intentId The intent ID
* @param _data The calldata of the intent
*/
function _executeCalldata(
bytes32 _intentId,
bytes memory _data
) internal {
(address _target, bytes memory _calldata) = abi.decode(_data, (address, bytes));
(bool _success, bytes memory _returnData) = callExecutor.excessivelySafeCall(
_target, gasleft() - Constants.EXECUTE_CALLDATA_RESERVE_GAS, 0, Constants.DEFAULT_COPY_BYTES, _calldata
);
if (_success) {
emit ExternalCalldataExecuted(_intentId, _returnData);
} else {
revert EverclearSpoke_ExecuteIntentCalldata_ExternalCallFailed();
}
}
/**
* @notice Pull tokens from the sender to the spoke contract
* @param _sender The address of the sender
* @param _asset The address of the asset
* @param _amount The amount of the asset
*/
function _pullTokens(
address _sender,
address _asset,
uint256 _amount
) internal {
IERC20(_asset).safeTransferFrom(_sender, address(this), _amount);
}
/**
* @notice Push tokens from the spoke contract to the recipient
* @param _recipient The address of the recipient
* @param _asset The address of the asset
* @param _amount The amount of the asset
*/
function _pushTokens(
address _recipient,
address _asset,
uint256 _amount
) internal {
IERC20(_asset).safeTransfer(_recipient, _amount);
}
/**
* @notice Perform a `delegatcall`
* @param _delegatee The address of the delegatee
*/
function _delegate(
address _delegatee
) internal {
assembly {
// Copy msg.data. We take full control of memory in this inline assembly
// block because it will not return to Solidity code. We overwrite the
// Solidity scratch pad at memory position 0.
calldatacopy(0, 0, calldatasize())
// Call the implementation.
// out and outsize are 0 because we don't know the size yet.
let result := delegatecall(gas(), _delegatee, 0, calldatasize(), 0, 0)
// Copy the returned data.
returndatacopy(0, 0, returndatasize())
switch result
// delegatecall returns 0 on error.
case 0 { revert(0, returndatasize()) }
default { return(0, returndatasize()) }
}
}
/**
* @notice Checks that the upgrade function is called by the owner
*/
function _authorizeUpgrade(
address
) internal override onlyOwner {}
/**
* @notice Process queue checks (applied when a relayer tries to process a queue)
* @param _domain The domain of the queue
* @param _relayer The relayer address
* @param _ttl The time to live of the message
*/
function _processQueueChecks(
uint32 _domain,
address _relayer,
uint256 _ttl
) internal view {
if (_domain != DOMAIN) {
revert EverclearSpoke_ProcessFillViaRelayer_WrongDomain();
}
if (_relayer != msg.sender) {
revert EverclearSpoke_ProcessFillViaRelayer_NotRelayer();
}
if (block.timestamp > _ttl) {
revert EverclearSpoke_ProcessFillViaRelayer_TTLExpired();
}
}
function _isEmpty(
uint256 first,
uint256 last
) internal pure returns (bool) {
return (last < first) || (first == 0 && last == 0);
}
/**
* @notice Checks the dynamic gas limit does not exceed the maxGasLimit (messageGasLimit) when processing queues
* @param _dynamicGasLimit The dynamic gas limit to check
*/
function _checkDynamicGasLimit(
uint256 _dynamicGasLimit
) internal view {
if (_dynamicGasLimit > messageGasLimit) {
revert EverclearSpoke_ProcessQueue_ExceedsGasLimit(_dynamicGasLimit);
}
}
}
"
},
"../../node_modules/@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.20;
import {ContextUpgradeable} from "../utils/ContextUpgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is set to the address provided by the deployer. This can
* later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract OwnableUpgradeable is Initializable, ContextUpgradeable {
/// @custom:storage-location erc7201:openzeppelin.storage.Ownable
struct OwnableStorage {
address _owner;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Ownable")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant OwnableStorageLocation = 0x9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300;
function _getOwnableStorage() private pure returns (OwnableStorage storage $) {
assembly {
$.slot := OwnableStorageLocation
}
}
/**
* @dev The caller account is not authorized to perform an operation.
*/
error OwnableUnauthorizedAccount(address account);
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/
function __Ownable_init(address initialOwner) internal onlyInitializing {
__Ownable_init_unchained(initialOwner);
}
function __Ownable_init_unchained(address initialOwner) internal onlyInitializing {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
OwnableStorage storage $ = _getOwnableStorage();
return $._owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
OwnableStorage storage $ = _getOwnableStorage();
address oldOwner = $._owner;
$._owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
"
},
"../../node_modules/@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/utils/UUPSUpgradeable.sol)
pragma solidity ^0.8.20;
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 ERC1967) 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 ERC1167 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 ERC1822 {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 ERC1967-compliant implementation pointing to self.
* See {_onlyProxy}.
*/
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 ERC1967.
*
* 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);
}
}
}
"
},
"../../node_modules/@openzeppelin/contracts-upgradeable/utils/NoncesUpgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Nonces.sol)
pragma solidity ^0.8.20;
import {Initializable} from "../proxy/utils/Initializable.sol";
/**
* @dev Provides tracking nonces for addresses. Nonces will only increment.
*/
abstract contract NoncesUpgradeable is Initializable {
/**
* @dev The nonce used for an `account` is not the expected current nonce.
*/
error InvalidAccountNonce(address account, uint256 currentNonce);
/// @custom:storage-location erc7201:openzeppelin.storage.Nonces
struct NoncesStorage {
mapping(address account => uint256) _nonces;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Nonces")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant NoncesStorageLocation = 0x5ab42ced628888259c08ac98db1eb0cf702fc1501344311d8b100cd1bfe4bb00;
function _getNoncesStorage() private pure returns (NoncesStorage storage $) {
assembly {
$.slot := NoncesStorageLocation
}
}
function __Nonces_init() internal onlyInitializing {
}
function __Nonces_init_unchained() internal onlyInitializing {
}
/**
* @dev Returns the next unused nonce for an address.
*/
function nonces(address owner) public view virtual returns (uint256) {
NoncesStorage storage $ = _getNoncesStorage();
return $._nonces[owner];
}
/**
* @dev Consumes a nonce.
*
* Returns the current value and increments nonce.
*/
function _useNonce(address owner) internal virtual returns (uint256) {
NoncesStorage storage $ = _getNoncesStorage();
// For each account, the nonce has an initial value of 0, can only be incremented by one, and cannot be
// decremented or reset. This guarantees that the nonce never overflows.
unchecked {
// It is important to do x++ and not ++x here.
return $._nonces[owner]++;
}
}
/**
* @dev Same as {_useNonce} but checking that `nonce` is the next valid for `owner`.
*/
function _useCheckedNonce(address owner, uint256 nonce) internal virtual {
uint256 current = _useNonce(owner);
if (nonce != current) {
revert InvalidAccountNonce(owner, current);
}
}
}
"
},
"../../node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/ERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "./IERC20.sol";
import {IERC20Metadata} from "./extensions/IERC20Metadata.sol";
import {Context} from "../../utils/Context.sol";
import {IERC20Errors} from "../../interfaces/draft-IERC6093.sol";
/**
* @dev Implementation of the {IERC20} interface.
*
* This implementation is agnostic to the way tokens are created. This means
* that a supply mechanism has to be added in a derived contract using {_mint}.
*
* TIP: For a detailed writeup see our guide
* https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
* to implement supply mechanisms].
*
* The default value of {decimals} is 18. To change this, you should override
* this function so it returns a different value.
*
* We have followed general OpenZeppelin Contracts guidelines: functions revert
* instead returning `false` on failure. This behavior is nonetheless
* conventional and does not conflict with the expectations of ERC20
* applications.
*
* Additionally, an {Approval} event is emitted on calls to {transferFrom}.
* This allows applications to reconstruct the allowance for all accounts just
* by listening to said events. Other implementations of the EIP may not emit
* these events, as it isn't required by the specification.
*/
abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {
mapping(address account => uint256) private _balances;
mapping(address account => mapping(address spender => uint256)) private _allowances;
uint256 private _totalSupply;
string private _name;
string private _symbol;
/**
* @dev Sets the values for {name} and {symbol}.
*
* All two of these values are immutable: they can only be set once during
* construction.
*/
constructor(string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
}
/**
* @dev Returns the name of the token.
*/
function name() public view virtual returns (string memory) {
return _name;
}
/**
* @dev Returns the symbol of the token, usually a shorter version of the
* name.
*/
function symbol() public view virtual returns (string memory) {
return _symbol;
}
/**
* @dev Returns the number of decimals used to get its user representation.
* For example, if `decimals` equals `2`, a balance of `505` tokens should
* be displayed to a user as `5.05` (`505 / 10 ** 2`).
*
* Tokens usually opt for a value of 18, imitating the relationship between
* Ether and Wei. This is the default value returned by this function, unless
* it's overridden.
*
* NOTE: This information is only used for _display_ purposes: it in
* no way affects any of the arithmetic of the contract, including
* {IERC20-balanceOf} and {IERC20-transfer}.
*/
function decimals() public view virtual returns (uint8) {
return 18;
}
/**
* @dev See {IERC20-totalSupply}.
*/
function totalSupply() public view virtual returns (uint256) {
return _totalSupply;
}
/**
* @dev See {IERC20-balanceOf}.
*/
function balanceOf(address account) public view virtual returns (uint256) {
return _balances[account];
}
/**
* @dev See {IERC20-transfer}.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - the caller must have a balance of at least `value`.
*/
function transfer(address to, uint256 value) public virtual returns (bool) {
address owner = _msgSender();
_transfer(owner, to, value);
return true;
}
/**
* @dev See {IERC20-allowance}.
*/
function allowance(address owner, address spender) public view virtual returns (uint256) {
return _allowances[owner][spender];
}
/**
* @dev See {IERC20-approve}.
*
* NOTE: If `value` is the maximum `uint256`, the allowance is not updated on
* `transferFrom`. This is semantically equivalent to an infinite approval.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function approve(address spender, uint256 value) public virtual returns (bool) {
address owner = _msgSender();
_approve(owner, spender, value);
return true;
}
/**
* @dev See {IERC20-transferFrom}.
*
* Emits an {Approval} event indicating the updated allowance. This is not
* required by the EIP. See the note at the beginning of {ERC20}.
*
* NOTE: Does not update the allowance if the current allowance
* is the maximum `uint256`.
*
* Requirements:
*
* - `from` and `to` cannot be the zero address.
* - `from` must have a balance of at least `value`.
* - the caller must have allowance for ``from``'s tokens of at least
* `value`.
*/
function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {
address spender = _msgSender();
_spendAllowance(from, spender, value);
_transfer(from, to, value);
return true;
}
/**
* @dev Moves a `value` amount of tokens from `from` to `to`.
*
* This internal function is equivalent to {transfer}, and can be used to
* e.g. implement automatic token fees, slashing mechanisms, etc.
*
* Emits a {Transfer} event.
*
* NOTE: This function is not virtual, {_update} should be overridden instead.
*/
function _transfer(address from, address to, uint256 value) internal {
if (from == address(0)) {
revert ERC20InvalidSender(address(0));
}
if (to == address(0)) {
revert ERC20InvalidReceiver(address(0));
}
_update(from, to, value);
}
/**
* @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`
* (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding
* this function.
*
* Emits a {Transfer} event.
*/
function _update(address from, address to, uint256 value) internal virtual {
if (from == address(0)) {
// Overflow check required: The rest of the code assumes that totalSupply never overflows
_totalSupply += value;
} else {
uint256 fromBalance = _balances[from];
if (fromBalance < value) {
revert ERC20InsufficientBalance(from, fromBalance, value);
}
unchecked {
// Overflow not possible: value <= fromBalance <= totalSupply.
_balances[from] = fromBalance - value;
}
}
if (to == address(0)) {
unchecked {
// Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.
_totalSupply -= value;
}
} else {
unchecked {
// Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.
_balances[to] += value;
}
}
emit Transfer(from, to, value);
}
/**
* @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).
* Relies on the `_update` mechanism
*
* Emits a {Transfer} event with `from` set to the zero address.
*
* NOTE: This function is not virtual, {_update} should be overridden instead.
*/
function _mint(address account, uint256 value) internal {
if (account == address(0)) {
revert ERC20InvalidReceiver(address(0));
}
_update(address(0), account, value);
}
/**
* @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.
* Relies on the `_update` mechanism.
*
* Emits a {Transfer} event with `to` set to the zero address.
*
* NOTE: This function is not virtual, {_update} should be overridden instead
*/
function _burn(address account, uint256 value) internal {
if (account == address(0)) {
revert ERC20InvalidSender(address(0));
}
_update(account, address(0), value);
}
/**
* @dev Sets `value` as the allowance of `spender` over the `owner` s tokens.
*
* This internal function is equivalent to `approve`, and can be used to
* e.g. set automatic allowances for certain subsystems, etc.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `owner` cannot be the zero address.
* - `spender` cannot be the zero address.
*
* Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.
*/
function _approve(address owner, address spender, uint256 value) internal {
_approve(owner, spender, value, true);
}
/**
* @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.
*
* By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by
* `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any
* `Approval` event during `transferFrom` operations.
*
* Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to
* true using the following override:
* ```
* function _approve(address owner, address spender, uint256 value, bool) internal virtual override {
* super._approve(owner, spender, value, true);
* }
* ```
*
* Requirements are the same as {_approve}.
*/
function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {
if (owner == address(0)) {
revert ERC20InvalidApprover(address(0));
}
if (spender == address(0)) {
revert ERC20InvalidSpender(address(0));
}
_allowances[owner][spender] = value;
if (emitEvent) {
emit Approval(owner, spender, value);
}
}
/**
* @dev Updates `owner` s allowance for `spender` based on spent `value`.
*
* Does not update the allowance value in case of infinite allowance.
* Revert if not enough allowance is available.
*
* Does not emit an {Approval} event.
*/
function _spendAllowance(address owner, address spender, uint256 value) internal virtual {
uint256 currentAllowance = allowance(owner, spender);
if (currentAllowance != type(uint256).max) {
if (currentAllowance < value) {
revert ERC20InsufficientAllowance(spender, currentAllowance, value);
}
unchecked {
_approve(owner, spender, currentAllowance - value, false);
}
}
}
}
"
},
"../../node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 value) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 value) external returns (bool);
}
"
},
"../../node_modules/@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
import {IERC20Permit} from "../extensions/IERC20Permit.sol";
import {Address} from "../../../utils/Address.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using Address for address;
/**
* @dev An operation with an ERC20 token failed.
*/
error SafeERC20FailedOperation(address token);
/**
* @dev Indicates a failed `decreaseAllowance` request.
*/
error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
forceApprove(token, spender, oldAllowance + value);
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
* value, non-reverting calls are assumed to be successful.
*/
function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
unchecked {
uint256 currentAllowance = token.allowance(address(this), spender);
if (currentAllowance < requestedDecrease) {
revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
}
forceApprove(token, spender, currentAllowance - requestedDecrease);
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
* to be set to zero before setting it to a non-zero value, such as USDT.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(data);
if (returndata.length != 0 && !abi.decode(returndata, (bool))) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
// and not revert is the subcall reverts.
(bool success, bytes memory returndata) = address(token).call(data);
return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0;
}
}
"
},
"../../node_modules/@openzeppelin/contracts/utils/cryptography/ECDSA.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/ECDSA.sol)
pragma solidity ^0.8.20;
/**
* @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
*
* These functions can be used to verify that a message was signed by the holder
* of the private keys of a given address.
*/
library ECDSA {
enum RecoverError {
NoError,
InvalidSignature,
InvalidSignatureLength,
InvalidSignatureS
}
/**
* @dev The signature derives the `address(0)`.
*/
error ECDSAInvalidSignature();
/**
* @dev The signature has an invalid length.
*/
error ECDSAInvalidSignatureLength(uint256 length);
/**
* @dev The signature has an S value that is in the upper half order.
*/
error ECDSAInvalidSignatureS(bytes32 s);
/**
* @dev Returns the address that signed a hashed message (`hash`) with `signature` or an error. This will not
* return address(0) without also returning an error description. Errors are documented using an enum (error type)
* and a bytes32 providing additional information about the error.
*
* If no error is returned, then the address can be used for verification purposes.
*
* The `ecrecover` EVM precompile allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it.
*
* Documentation for signature generation:
* - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
* - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
*/
function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError, bytes32) {
if (signature.length == 65) {
bytes32 r;
bytes32 s;
uint8 v;
// ecrecover takes the signature parameters, and the only way to get them
// currently is to use assembly.
/// @solidity memory-safe-assembly
assembly {
r := mload(add(signature, 0x20))
s := mload(add(signature, 0x40))
v := byte(0, mload(add(signature, 0x60)))
}
return tryRecover(hash, v, r, s);
} else {
return (address(0), RecoverError.InvalidSignatureLength, bytes32(signature.length));
}
}
/**
* @dev Returns the address that signed a hashed message (`hash`) with
* `signature`. This address can then be used for verification purposes.
*
* The `ecrecover` EVM precompile allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {MessageHashUtils-toEthSignedMessag
Submitted on: 2025-10-29 16:31:52
Comments
Log in to comment.
No comments yet.