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/Vault.sol": {
"content": "// SPDX-License-Identifier: LicenseRef-CICADA-Proprietary
// SPDX-FileCopyrightText: (c) 2024 Cicada Software, CICADA DMCC. All rights reserved.
pragma solidity ^0.8.29;
import {SafeERC20} from "@openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";
import {Address} from "@openzeppelin-contracts/utils/Address.sol";
import {IUniswapV2Router02} from "@uniswap-v2-periphery/contracts/interfaces/IUniswapV2Router02.sol";
import {AccessControl} from "@openzeppelin-contracts/access/AccessControl.sol";
import {Math} from "@openzeppelin-contracts/utils/math/Math.sol";
import {IERC20} from "@openzeppelin-contracts/token/ERC20/IERC20.sol";
/**
* @title Vault
* @notice Manages funds and executes arbitrage operations on behalf of the DeGate arbitrage engine.
* Handles asset approvals, delegates execution to the strategy, and enforces a USD TVL loss threshold to safeguard funds.
* @dev The Vault interacts with a strategy contract to perform swaps and can be upgraded to integrate new strategies and DeFi protocols.
*/
contract Vault is AccessControl {
using SafeERC20 for IERC20;
using Address for address;
struct VaultReserves {
uint256 baseTokenBalance;
uint256 baseTokenUsdValue;
uint256 quoteTokenBalance;
uint256 quoteTokenUsdValue;
uint256 totalUsdValue;
}
// MANAGER_ROLE: Responsible for managing and upgrading the strategy contract.
// Can grant MANAGER_ROLE and GATEWAY_EXECUTOR_ROLE roles.
// Typically held by the platform staff via a multisignature wallet. This role allows
// for the introduction of new methods and integrations with DeFi protocols.
bytes32 public constant MANAGER_ROLE = keccak256("MANAGER_ROLE");
// GATEWAY_EXECUTOR_ROLE: Held by the arbitrage engine gateway process, this EOA is responsible
// for triggering arbitrage operations and covering the gas costs associated with these transactions.
bytes32 public constant GATEWAY_EXECUTOR_ROLE = keccak256("GATEWAY_EXECUTOR_ROLE");
// FINANCIER_ROLE: Held by the owner of the funds, which could be an externally owned
// account (EOA) or a multisignature wallet. The holder of this role is responsible for
// providing the funding for operations and has the exclusive ability to withdraw funds
// from the vault.
// Can grant FINANCIER_ROLE to other accounts.
bytes32 public constant FINANCIER_ROLE = keccak256("FINANCIER_ROLE");
// MAX_ALLOWED_LOSS_BPS represents the maximum allowed loss in basis points (bps).
// A value of 1000 bps is equivalent to a 10% maximum loss allowed per single action
uint256 public constant MAX_ALLOWED_SINGLE_LOSS_BPS = 100;
uint256 public constant MAX_ALLOWED_CUM_LOSS_BPS = 1000;
address public strategy;
IERC20 public baseToken;
IERC20 public quoteToken;
address[] public reservesEvaluationPath;
address[] public gatewayExecutors;
IUniswapV2Router02 public router;
string public constant version = "v0.1.3-flex";
uint256 public allowedSingleLossBps;
uint256 public allowedCumLossBps;
uint256 public currentCumLossBps;
uint256 public expectedGasBurn;
bool public isFlex;
// These events are emitted during the arbitrage execution process to track the state and results of operations.
// allowing the arbitrage engine to index, fetch, and analyze the outcomes.
// `PreExecState` captures the Vault's initial balances and USD TVL before execution.
// `PostExecState` records the final balances and resulting USD TVL
// `ExecResult` provides the net changes in balances and USD value.
event PreExecState(VaultReserves);
event PostExecState(VaultReserves);
event ExecResult(int256 baseTokenBalanceChange, int256 quoteTokenBalanceChange, int256 totalUsdValueChange);
event AllowedLossUpdated(uint256 allowedSingleLossBps, uint256 allowedCumLossBps);
event StrategyUpdated(address previousStrategy, address newStrategy);
event GatewayExecutorAdded(address executor);
event GatewayExecutorRemoved(address executor);
event ExpectedGasBurnUpdated(uint256 newExpectedGasBurn);
/**
* @dev Initializes the Vault with the provided parameters.
* @param _baseToken Address of the base token (first token in the pair).
* @param _quoteToken Address of the quote token (second token in the pair).
* @param _reservesEvaluationPath Route to the USD stablecoin token (for USD valuation).
* @param _router Address of the Uniswap V2 router (for price impact and USD value calculations).
* @param _allowedSingleLossBps Allowed single-tx loss in 1/10000 basis points (modifiable by FINANCIER_ROLE).
* @param _allowedCumLossBps Allowed cumulative loss loss in 1/10000 basis points (modifiable by FINANCIER_ROLE).
* @param _strategy Address of the strategy contract (modifiable by MANAGER_ROLE).
* @param _financier Address granted the FINANCIER_ROLE (responsible for funding operations).
* @param _gatewayExecutors Addresses granted the GATEWAY_EXECUTOR_ROLE (able to initiate buy/sell operations).
* @param _manager Address granted the MANAGER_ROLE (manages strategy upgrades).
* @param _isFlex Flag to enable/disable flex mode
*/
constructor(
IERC20 _baseToken,
IERC20 _quoteToken,
address[] memory _reservesEvaluationPath,
IUniswapV2Router02 _router,
uint256 _allowedSingleLossBps,
uint256 _allowedCumLossBps,
uint256 _expectedGasBurn,
address _strategy,
address _financier,
address[] memory _gatewayExecutors,
address _manager,
bool _isFlex
) {
isFlex = _isFlex;
_validateTokensAndRouter(address(_baseToken), address(_quoteToken), address(_router));
baseToken = _baseToken;
quoteToken = _quoteToken;
router = _router;
_validateEvaluationPath(_reservesEvaluationPath);
reservesEvaluationPath = _reservesEvaluationPath;
require(_allowedSingleLossBps <= MAX_ALLOWED_SINGLE_LOSS_BPS, "ALLOWED_SINGLE_LOSS_OVER_MAX");
require(_allowedCumLossBps <= MAX_ALLOWED_CUM_LOSS_BPS, "ALLOWED_CUM_LOSS_OVER_MAX");
allowedSingleLossBps = _allowedSingleLossBps;
allowedCumLossBps = _allowedCumLossBps;
strategy = _strategy;
require(_expectedGasBurn > 200_000, "EXPECTED_GAS_BURN_TOO_LOW");
expectedGasBurn = _expectedGasBurn;
_setRoleAdmin(GATEWAY_EXECUTOR_ROLE, MANAGER_ROLE);
_setRoleAdmin(MANAGER_ROLE, MANAGER_ROLE);
_setRoleAdmin(FINANCIER_ROLE, FINANCIER_ROLE);
_grantRole(FINANCIER_ROLE, _financier);
for (uint256 i = 0; i < _gatewayExecutors.length; i++) {
_addGatewayExecutor(_gatewayExecutors[i]);
}
_grantRole(MANAGER_ROLE, _manager);
}
function updateConfig(
IERC20 _baseToken,
IERC20 _quoteToken,
address[] memory _reservesEvaluationPath,
IUniswapV2Router02 _router,
bool _isFlex
) external onlyRole(MANAGER_ROLE) {
_validateTokensAndRouter(address(_baseToken), address(_quoteToken), address(_router));
baseToken = _baseToken;
quoteToken = _quoteToken;
router = _router;
_validateEvaluationPath(_reservesEvaluationPath);
reservesEvaluationPath = _reservesEvaluationPath;
isFlex = _isFlex;
}
receive() external payable {}
/**
* @notice Sets the expected gas burn value.
* @param _expectedGasBurn The new expected gas burn value.
*/
function setExpectedGasBurn(uint256 _expectedGasBurn) external onlyRole(MANAGER_ROLE) {
require(_expectedGasBurn > 200_000, "EXPECTED_GAS_BURN_TOO_LOW");
expectedGasBurn = _expectedGasBurn;
emit ExpectedGasBurnUpdated(_expectedGasBurn);
}
/**
* @notice Updates the allowed USD loss thresholds in basis points (bps).
* @param _allowedSingleLossBps The new allowed single-operation loss in bps (1 bps = 0.01%).
* @param _allowedCumLossBps The new allowed cumulative loss in bps (1 bps = 0.01%).
*
* Example: `setAllowedLoss(100);` sets the allowed loss to 1%.
*/
function setAllowedLoss(uint256 _allowedSingleLossBps, uint256 _allowedCumLossBps)
external
onlyRole(FINANCIER_ROLE)
{
require(_allowedSingleLossBps <= MAX_ALLOWED_SINGLE_LOSS_BPS, "ALLOWED_SINGLE_LOSS_OVER_MAX");
require(_allowedCumLossBps <= MAX_ALLOWED_CUM_LOSS_BPS, "ALLOWED_CUM_LOSS_OVER_MAX");
allowedSingleLossBps = _allowedSingleLossBps;
allowedCumLossBps = _allowedCumLossBps;
currentCumLossBps = 0;
emit AllowedLossUpdated(allowedSingleLossBps, allowedCumLossBps);
}
/**
* @notice Updates the strategy contract used by the Vault.
* @dev This function is used to update the strategy contract that the Vault interacts with for executing arbitrage operations.
* Can only be called by an account with the MANAGER_ROLE (Typically held by the platform staff via a multisignature wallet)
* @param _strategy The address of the new strategy contract.
*/
function setStrategy(address _strategy) external onlyRole(MANAGER_ROLE) {
require(_strategy != address(0), "STRATEGY_NOTSET");
require(_strategy != strategy, "SAME_STRATEGY");
// Revoke approvals from previous strategy for safety
if (strategy != address(0)) {
baseToken.forceApprove(strategy, 0);
quoteToken.forceApprove(strategy, 0);
}
strategy = _strategy;
emit StrategyUpdated(strategy, _strategy);
}
/**
* @dev Adds a new gateway executor.
* @param _executor The address of the executor to add.
*/
function addGatewayExecutor(address _executor) external onlyRole(MANAGER_ROLE) {
_addGatewayExecutor(_executor);
}
/**
* @dev Removes an existing gateway executor.
* @param _executor The address of the executor to remove.
*/
function removeGatewayExecutor(address _executor) external onlyRole(MANAGER_ROLE) {
_removeGatewayExecutor(_executor);
}
/**
* @dev Ensures each gateway executor has at least _minWei wei on its baance.
* @param _minWei The minimum balance units each executor should have.
*/
function replenishExecutorsWei(uint256 _minWei) external onlyRole(MANAGER_ROLE) {
_replenishExecutorsWei(_minWei);
}
/**
* @dev Executes swap operations initiated by an external DeGate process with GATEWAY_EXECUTOR_ROLE.
* Approves and flash-loans specified amounts of base and quote tokens from the Vault to the Strategy contract,
* delegating control for execution. The Strategy completes the operation and returns the funds to the Vault.
* While temporary USD losses due to slippage and DEX fees are expected, the modifier ensures losses
* stay within the allowed threshold, mitigating potential mistakes.
* @param _baseTokenAmount The amount of base tokens to approve and flash-loan to the Strategy.
* @param _quoteTokenAmount The amount of quote tokens to approve and flash-loan to the Strategy.
* @param _params Encoded function selector and parameters for the Strategy contract's execution.
*/
function execute(uint256 _baseTokenAmount, uint256 _quoteTokenAmount, bytes calldata _params)
external
onlyRole(GATEWAY_EXECUTOR_ROLE)
{
baseToken.forceApprove(strategy, _baseTokenAmount);
quoteToken.forceApprove(strategy, _quoteTokenAmount);
VaultReserves memory vaultReservesBefore = getVaultReserves();
emit PreExecState(vaultReservesBefore);
strategy.functionCall(_params);
VaultReserves memory vaultReservesAfter = getVaultReserves();
if (!isFlex) {
_trackAndEnforceLossLimits(vaultReservesBefore, vaultReservesAfter);
}
emit PostExecState(vaultReservesAfter);
emit ExecResult(
int256(vaultReservesAfter.baseTokenBalance) - int256(vaultReservesBefore.baseTokenBalance),
int256(vaultReservesAfter.quoteTokenBalance) - int256(vaultReservesBefore.quoteTokenBalance),
int256(vaultReservesAfter.totalUsdValue) - int256(vaultReservesBefore.totalUsdValue)
);
_replenishExecutorsWei(0);
}
/**
* @notice Withdraws a specified amount of a given token from the Vault.
* @dev Allows the funds' owner to withdraw baseToken, quoteToken, or recover any other tokens (e.g., mistakenly sent) from the Vault.
* This function can only be called by an account with the FINANCIER_ROLE, representing the owner of the funds.
* @param token The ERC20 token to withdraw. If the address is zero, withdraw native token (ETH).
* @param amount The amount of the token to withdraw.
*/
function withdraw(address token, uint256 amount) external onlyRole(FINANCIER_ROLE) {
if (token == address(0)) {
payable(msg.sender).transfer(amount);
} else {
IERC20(token).safeTransfer(msg.sender, amount);
}
}
/**
* @notice Calculates and returns the total USD value of the vault's reserves.
* @dev This function retrieves the balances of two tokens (baseToken and quoteToken),
* converts their respective balances into USD value using a Uniswap-like router,
* and sums the USD values to return the total value of the reserves.
*
* @return usdValue The total USD value of the vault's reserves.
*/
function getVaultReserves() public view returns (VaultReserves memory) {
uint256 baseTokenBalance = baseToken.balanceOf(address(this));
uint256 quoteTokenBalance = quoteToken.balanceOf(address(this));
uint256 baseTokenUsdValue;
uint256 quoteTokenUsdValue;
if (!isFlex) {
// If the route is not explicitly specified, evaluate baseToken by the direct route to quoteToken
// and use theraw quote token balance as USD value
if (reservesEvaluationPath.length == 0) {
address[] memory fullEvaluationPath = new address[](2);
fullEvaluationPath[0] = address(baseToken);
fullEvaluationPath[1] = address(quoteToken);
if (baseTokenBalance > 0) {
fullEvaluationPath[0] = address(baseToken);
baseTokenUsdValue = router.getAmountsOut(baseTokenBalance, fullEvaluationPath)[1];
}
quoteTokenUsdValue = quoteTokenBalance;
} else {
address[] memory fullEvaluationPath = new address[](reservesEvaluationPath.length + 1);
for (uint256 i = 0; i < reservesEvaluationPath.length; i++) {
fullEvaluationPath[i + 1] = reservesEvaluationPath[i];
}
if (baseTokenBalance > 0) {
fullEvaluationPath[0] = address(baseToken);
baseTokenUsdValue = router.getAmountsOut(baseTokenBalance, fullEvaluationPath)[1];
}
if (quoteTokenBalance > 0) {
fullEvaluationPath[0] = address(quoteToken);
quoteTokenUsdValue = router.getAmountsOut(quoteTokenBalance, fullEvaluationPath)[1];
}
}
}
uint256 totalUsdValue = baseTokenUsdValue + quoteTokenUsdValue;
VaultReserves memory reserves = VaultReserves({
baseTokenBalance: baseTokenBalance,
baseTokenUsdValue: baseTokenUsdValue,
quoteTokenBalance: quoteTokenBalance,
quoteTokenUsdValue: quoteTokenUsdValue,
totalUsdValue: totalUsdValue
});
return reserves;
}
/**
* @dev Adds a new gateway executor.
* @param _executor The address of the executor to add.
*/
function _addGatewayExecutor(address _executor) internal {
require(_executor != address(0), "INVALID_EXECUTOR_ADDRESS");
for (uint256 i = 0; i < gatewayExecutors.length; i++) {
require(gatewayExecutors[i] != _executor, "EXECUTOR_ALREADY_EXISTS");
}
_grantRole(GATEWAY_EXECUTOR_ROLE, _executor);
gatewayExecutors.push(_executor);
emit GatewayExecutorAdded(_executor);
}
/**
* @dev Removes an existing gateway executor.
* @param _executor The address of the executor to remove.
*/
function _removeGatewayExecutor(address _executor) internal {
require(_executor != address(0), "INVALID_EXECUTOR_ADDRESS");
_revokeRole(GATEWAY_EXECUTOR_ROLE, _executor);
bool removed = false;
// Remove executor from the array
for (uint256 i = 0; i < gatewayExecutors.length; i++) {
if (gatewayExecutors[i] == _executor) {
gatewayExecutors[i] = gatewayExecutors[gatewayExecutors.length - 1];
gatewayExecutors.pop();
removed = true;
break;
}
}
require(removed, "EXECUTOR_NOT_FOUND");
emit GatewayExecutorRemoved(_executor);
}
/**
* @dev Ensures each gateway executor has at least _minWei.
* @param _minWei The minimum balance each executor should have. If it's 0, it's auto-calculated
*/
function _replenishExecutorsWei(uint256 _minWei) internal {
if (_minWei == 0) {
_minWei = expectedGasBurn * tx.gasprice;
}
for (uint256 i = 0; i < gatewayExecutors.length; i++) {
address executor = gatewayExecutors[i];
uint256 balance = executor.balance;
if (balance < _minWei) {
uint256 amountToSend = _minWei - balance;
if (address(this).balance < amountToSend) {
// Exit the loop and function if the vault's balance is not enough
break;
}
payable(executor).transfer(amountToSend);
}
}
}
/**
* @dev Tracks and enforces loss limits based on the change in reserves.
* This function calculates the loss in USD value between the reserves before and after an operation.
* It then converts this loss to basis points (bps) and ensures that the loss does not exceed the allowed single loss
* and cumulative loss limits. If the loss exceeds these limits, the function reverts.
*
* @param reservesBefore The reserves before the operation.
* @param reservesAfter The reserves after the operation.
*/
function _trackAndEnforceLossLimits(VaultReserves memory reservesBefore, VaultReserves memory reservesAfter)
internal
{
if (isFlex) return;
if (reservesBefore.totalUsdValue > reservesAfter.totalUsdValue) {
uint256 singleLossUsd = reservesBefore.totalUsdValue - reservesAfter.totalUsdValue;
// Ensure singleLossBps is at least 1 if a loss occurred to prevent exploiting rounding errors
uint256 singleLossBps = Math.max((singleLossUsd * 10000) / reservesBefore.totalUsdValue, 1);
currentCumLossBps += singleLossBps;
require(singleLossBps <= allowedSingleLossBps, "SINGLE_LOSS_EXCEEDS_ALLOWED");
require(currentCumLossBps <= allowedCumLossBps, "CUM_LOSS_EXCEEDS_ALLOWED");
}
}
function _validateTokensAndRouter(address _baseToken, address _quoteToken, address _router) internal pure {
require(_baseToken != address(0), "BASE_TOKEN_NOT_SET");
require(_quoteToken != address(0), "QUOTE_TOKEN_NOT_SET");
require(_router != address(0), "ROUTER_NOT_SET");
require(_baseToken != _quoteToken, "BASE_AND_QUOTE_EQUAL");
}
function _validateEvaluationPath(address[] memory _reservesEvaluationPath) internal view {
if (isFlex) return;
for (uint256 i = 0; i < _reservesEvaluationPath.length; i++) {
require(_reservesEvaluationPath[i] != address(0), "EVALUATION_PATH_CONTAINS_ZERO");
require(_reservesEvaluationPath[i] != address(baseToken), "EVALUATION_PATH_CONTAINS_BASE");
require(_reservesEvaluationPath[i] != address(quoteToken), "EVALUATION_PATH_CONTAINS_QUOTE");
}
}
}
"
},
"dependencies/@openzeppelin-contracts-5.2.0/token/ERC20/utils/SafeERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.2.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
import {IERC1363} from "../../../interfaces/IERC1363.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC-20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
/**
* @dev An operation with an ERC-20 token failed.
*/
error SafeERC20FailedOperation(address token);
/**
* @dev Indicates a failed `decreaseAllowance` request.
*/
error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*
* IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
* smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
* this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
* that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
*/
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
forceApprove(token, spender, oldAllowance + value);
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
* value, non-reverting calls are assumed to be successful.
*
* IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
* smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
* this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
* that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
*/
function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
unchecked {
uint256 currentAllowance = token.allowance(address(this), spender);
if (currentAllowance < requestedDecrease) {
revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
}
forceApprove(token, spender, currentAllowance - requestedDecrease);
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
* to be set to zero before setting it to a non-zero value, such as USDT.
*
* NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function
* only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being
* set here.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no
* code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* Reverts if the returned value is other than `true`.
*/
function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
if (to.code.length == 0) {
safeTransfer(token, to, value);
} else if (!token.transferAndCall(to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target
* has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* Reverts if the returned value is other than `true`.
*/
function transferFromAndCallRelaxed(
IERC1363 token,
address from,
address to,
uint256 value,
bytes memory data
) internal {
if (to.code.length == 0) {
safeTransferFrom(token, from, to, value);
} else if (!token.transferFromAndCall(from, to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no
* code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.
* Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}
* once without retrying, and relies on the returned value to be true.
*
* Reverts if the returned value is other than `true`.
*/
function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
if (to.code.length == 0) {
forceApprove(token, to, value);
} else if (!token.approveAndCall(to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements.
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
uint256 returnSize;
uint256 returnValue;
assembly ("memory-safe") {
let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
// bubble errors
if iszero(success) {
let ptr := mload(0x40)
returndatacopy(ptr, 0, returndatasize())
revert(ptr, returndatasize())
}
returnSize := returndatasize()
returnValue := mload(0)
}
if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
bool success;
uint256 returnSize;
uint256 returnValue;
assembly ("memory-safe") {
success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
returnSize := returndatasize()
returnValue := mload(0)
}
return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);
}
}
"
},
"dependencies/@openzeppelin-contracts-5.2.0/utils/Address.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.2.0) (utils/Address.sol)
pragma solidity ^0.8.20;
import {Errors} from "./Errors.sol";
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev There's no code at `target` (it is not a contract).
*/
error AddressEmptyCode(address target);
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
if (address(this).balance < amount) {
revert Errors.InsufficientBalance(address(this).balance, amount);
}
(bool success, bytes memory returndata) = recipient.call{value: amount}("");
if (!success) {
_revert(returndata);
}
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason or custom error, it is bubbled
* up by this function (like regular Solidity function calls). However, if
* the call reverted with no returned reason, this function reverts with a
* {Errors.FailedCall} error.
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
if (address(this).balance < value) {
revert Errors.InsufficientBalance(address(this).balance, value);
}
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
* was not a contract or bubbling up the revert reason (falling back to {Errors.FailedCall}) in case
* of an unsuccessful call.
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata
) internal view returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
// only check if target is a contract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
if (returndata.length == 0 && target.code.length == 0) {
revert AddressEmptyCode(target);
}
return returndata;
}
}
/**
* @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
* revert reason or with a default {Errors.FailedCall} error.
*/
function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
return returndata;
}
}
/**
* @dev Reverts with returndata if present. Otherwise reverts with {Errors.FailedCall}.
*/
function _revert(bytes memory returndata) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
assembly ("memory-safe") {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert Errors.FailedCall();
}
}
}
"
},
"dependencies/@uniswap-v2-periphery-1.1.0-beta.0/contracts/interfaces/IUniswapV2Router02.sol": {
"content": "pragma solidity >=0.6.2;
import './IUniswapV2Router01.sol';
interface IUniswapV2Router02 is IUniswapV2Router01 {
function removeLiquidityETHSupportingFeeOnTransferTokens(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) external returns (uint amountETH);
function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline,
bool approveMax, uint8 v, bytes32 r, bytes32 s
) external returns (uint amountETH);
function swapExactTokensForTokensSupportingFeeOnTransferTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external;
function swapExactETHForTokensSupportingFeeOnTransferTokens(
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external payable;
function swapExactTokensForETHSupportingFeeOnTransferTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external;
}
"
},
"dependencies/@openzeppelin-contracts-5.2.0/access/AccessControl.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/AccessControl.sol)
pragma solidity ^0.8.20;
import {IAccessControl} from "./IAccessControl.sol";
import {Context} from "../utils/Context.sol";
import {ERC165} from "../utils/introspection/ERC165.sol";
/**
* @dev Contract module that allows children to implement role-based access
* control mechanisms. This is a lightweight version that doesn't allow enumerating role
* members except through off-chain means by accessing the contract event logs. Some
* applications may benefit from on-chain enumerability, for those cases see
* {AccessControlEnumerable}.
*
* Roles are referred to by their `bytes32` identifier. These should be exposed
* in the external API and be unique. The best way to achieve this is by
* using `public constant` hash digests:
*
* ```solidity
* bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
* ```
*
* Roles can be used to represent a set of permissions. To restrict access to a
* function call, use {hasRole}:
*
* ```solidity
* function foo() public {
* require(hasRole(MY_ROLE, msg.sender));
* ...
* }
* ```
*
* Roles can be granted and revoked dynamically via the {grantRole} and
* {revokeRole} functions. Each role has an associated admin role, and only
* accounts that have a role's admin role can call {grantRole} and {revokeRole}.
*
* By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
* that only accounts with this role will be able to grant or revoke other
* roles. More complex role relationships can be created by using
* {_setRoleAdmin}.
*
* WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
* grant and revoke this role. Extra precautions should be taken to secure
* accounts that have been granted it. We recommend using {AccessControlDefaultAdminRules}
* to enforce additional security measures for this role.
*/
abstract contract AccessControl is Context, IAccessControl, ERC165 {
struct RoleData {
mapping(address account => bool) hasRole;
bytes32 adminRole;
}
mapping(bytes32 role => RoleData) private _roles;
bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
/**
* @dev Modifier that checks that an account has a specific role. Reverts
* with an {AccessControlUnauthorizedAccount} error including the required role.
*/
modifier onlyRole(bytes32 role) {
_checkRole(role);
_;
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);
}
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) public view virtual returns (bool) {
return _roles[role].hasRole[account];
}
/**
* @dev Reverts with an {AccessControlUnauthorizedAccount} error if `_msgSender()`
* is missing `role`. Overriding this function changes the behavior of the {onlyRole} modifier.
*/
function _checkRole(bytes32 role) internal view virtual {
_checkRole(role, _msgSender());
}
/**
* @dev Reverts with an {AccessControlUnauthorizedAccount} error if `account`
* is missing `role`.
*/
function _checkRole(bytes32 role, address account) internal view virtual {
if (!hasRole(role, account)) {
revert AccessControlUnauthorizedAccount(account, role);
}
}
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
*
* To change a role's admin, use {_setRoleAdmin}.
*/
function getRoleAdmin(bytes32 role) public view virtual returns (bytes32) {
return _roles[role].adminRole;
}
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*
* May emit a {RoleGranted} event.
*/
function grantRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) {
_grantRole(role, account);
}
/**
* @dev Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*
* May emit a {RoleRevoked} event.
*/
function revokeRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) {
_revokeRole(role, account);
}
/**
* @dev Revokes `role` from the calling account.
*
* Roles are often managed via {grantRole} and {revokeRole}: this function's
* purpose is to provide a mechanism for accounts to lose their privileges
* if they are compromised (such as when a trusted device is misplaced).
*
* If the calling account had been revoked `role`, emits a {RoleRevoked}
* event.
*
* Requirements:
*
* - the caller must be `callerConfirmation`.
*
* May emit a {RoleRevoked} event.
*/
function renounceRole(bytes32 role, address callerConfirmation) public virtual {
if (callerConfirmation != _msgSender()) {
revert AccessControlBadConfirmation();
}
_revokeRole(role, callerConfirmation);
}
/**
* @dev Sets `adminRole` as ``role``'s admin role.
*
* Emits a {RoleAdminChanged} event.
*/
function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
bytes32 previousAdminRole = getRoleAdmin(role);
_roles[role].adminRole = adminRole;
emit RoleAdminChanged(role, previousAdminRole, adminRole);
}
/**
* @dev Attempts to grant `role` to `account` and returns a boolean indicating if `role` was granted.
*
* Internal function without access restriction.
*
* May emit a {RoleGranted} event.
*/
function _grantRole(bytes32 role, address account) internal virtual returns (bool) {
if (!hasRole(role, account)) {
_roles[role].hasRole[account] = true;
emit RoleGranted(role, account, _msgSender());
return true;
} else {
return false;
}
}
/**
* @dev Attempts to revoke `role` to `account` and returns a boolean indicating if `role` was revoked.
*
* Internal function without access restriction.
*
* May emit a {RoleRevoked} event.
*/
function _revokeRole(bytes32 role, address account) internal virtual returns (bool) {
if (hasRole(role, account)) {
_roles[role].hasRole[account] = false;
emit RoleRevoked(role, account, _msgSender());
return true;
} else {
return false;
}
}
}
"
},
"dependencies/@openzeppelin-contracts-5.2.0/utils/math/Math.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/math/Math.sol)
pragma solidity ^0.8.20;
import {Panic} from "../Panic.sol";
import {SafeCast} from "./SafeCast.sol";
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library Math {
enum Rounding {
Floor, // Toward negative infinity
Ceil, // Toward positive infinity
Trunc, // Toward zero
Expand // Away from zero
}
/**
* @dev Returns the addition of two unsigned integers, with an success flag (no overflow).
*/
function tryAdd(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
unchecked {
uint256 c = a + b;
if (c < a) return (false, 0);
return (true, c);
}
}
/**
* @dev Returns the subtraction of two unsigned integers, with an success flag (no overflow).
*/
function trySub(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
unchecked {
if (b > a) return (false, 0);
return (true, a - b);
}
}
/**
* @dev Returns the multiplication of two unsigned integers, with an success flag (no overflow).
*/
function tryMul(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
unchecked {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
if (a == 0) return (true, 0);
uint256 c = a * b;
if (c / a != b) return (false, 0);
return (true, c);
}
}
/**
* @dev Returns the division of two unsigned integers, with a success flag (no division by zero).
*/
function tryDiv(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
unchecked {
if (b == 0) return (false, 0);
return (true, a / b);
}
}
/**
* @dev Returns the remainder of dividing two unsigned integers, with a success flag (no division by zero).
*/
function tryMod(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
unchecked {
if (b == 0) return (false, 0);
return (true, a % b);
}
}
/**
* @dev Branchless ternary evaluation for `a ? b : c`. Gas costs are constant.
*
* IMPORTANT: This function may reduce bytecode size and consume less gas when used standalone.
* However, the compiler may optimize Solidity ternary operations (i.e. `a ? b : c`) to only compute
* one branch when needed, making this function more expensive.
*/
function ternary(bool condition, uint256 a, uint256 b) internal pure returns (uint256) {
unchecked {
// branchless ternary works because:
// b ^ (a ^ b) == a
// b ^ 0 == b
return b ^ ((a ^ b) * SafeCast.toUint(condition));
}
}
/**
* @dev Returns the largest of two numbers.
*/
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return ternary(a > b, a, b);
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return ternary(a < b, a, b);
}
/**
* @dev Returns the average of two numbers. The result is rounded towards
* zero.
*/
function average(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b) / 2 can overflow.
return (a & b) + (a ^ b) / 2;
}
/**
* @dev Returns the ceiling of the division of two numbers.
*
* This differs from standard division with `/` in that it rounds towards infinity instead
* of rounding towards zero.
*/
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
if (b == 0) {
// Guarantee the same behavior as in a regular Solidity division.
Panic.panic(Panic.DIVISION_BY_ZERO);
}
// The following calculation ensures accurate ceiling division without overflow.
// Since a is non-zero, (a - 1) / b will not overflow.
// The largest possible result occurs when (a - 1) / b is type(uint256).max,
// but the largest value we can obtain is type(uint256).max - 1, which happens
// when a = type(uint256).max and b = 1.
unchecked {
return SafeCast.toUint(a > 0) * ((a - 1) / b + 1);
}
}
/**
* @dev Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or
* denominator == 0.
*
* Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by
* Uniswap Labs also under MIT license.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
unchecked {
// 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2²⁵⁶ and mod 2²⁵⁶ - 1, then use
// the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
// variables such that product = prod1 * 2²⁵⁶ + prod0.
uint256 prod0 = x * y; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly {
let mm := mulmod(x, y, not(0))
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
// Handle non-overflow cases, 256 by 256 division.
if (prod1 == 0) {
// Solidity will revert if denominator == 0, unlike the div opcode on its own.
// The surrounding unchecked block does not change this fact.
// See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
return prod0 / denominator;
}
// Make sure the result is less than 2²⁵⁶. Also prevents denominator == 0.
if (denominator <= prod1) {
Panic.panic(ternary(denominator == 0, Panic.DIVISION_BY_ZERO, Panic.UNDER_OVERFLOW));
}
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from [prod1 prod0].
uint256 remainder;
assembly {
// Compute remainder using mulmod.
remainder := mulmod(x, y, denominator)
// Subtract 256 bit number from 512 bit number.
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
// Factor powers of two out of denominator and compute largest power of two divisor of denominator.
// Always >= 1. See https://cs.stackexchange.com/q/138556/92363.
uint256 twos = denominator & (0 - denominator);
assembly {
// Divide denominator by twos.
denominator := div(denominator, twos)
// Divide [prod1 prod0] by twos.
prod0 := div(prod0, twos)
// Flip twos such that it is 2²⁵⁶ / twos. If twos is zero, then it becomes one.
twos := add(div(sub(0, twos), twos), 1)
}
// Shift in bits from prod1 into prod0.
prod0 |= prod1 * twos;
// Invert denominator mod 2²⁵⁶. Now that denominator is an odd number, it has an inverse modulo 2²⁵⁶ such
// that denominator * inv ≡ 1 mod 2²⁵⁶. Compute the inverse by starting with a seed that is correct for
// four bits. That is, denominator * inv ≡ 1 mod 2⁴.
uint256 inverse = (3 * denominator) ^ 2;
// Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also
// works in modular arithmetic, doubling the correct bits in each step.
inverse *= 2 - denominator * inverse; // inverse mod 2⁸
inverse *= 2 - denominator * inverse; // inverse mod 2¹⁶
inverse *= 2 - denominator * inverse; // inverse mod 2³²
inverse *= 2 - denominator * inverse; // inverse mod 2⁶⁴
inverse *= 2 - denominator * inverse; // inverse mod 2¹²⁸
inverse *= 2 - denominator * inverse; // inverse mod 2²⁵⁶
// Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
// This will give us the correct result modulo 2²⁵⁶. Since the preconditions guarantee that the outcome is
// less than 2²⁵⁶, this is the final result. We don't need to compute the high bits of the result and prod1
// is no longer required.
result = prod0 * inverse;
return result;
}
}
/**
* @dev Calculates x * y / denominator with full precision, following the selected rounding direction.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
return mulDiv(x, y, denominator) + SafeCast.toUint(unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0);
}
/**
* @dev Calculate the modular multiplicative inverse of a number in Z/nZ.
*
* If n is a prime, then Z/nZ is a field. In that case all elements are inversible, except 0.
* If n is not a prime, then Z/nZ is not a field, and some elements might not be inversible.
*
* If the input value is not inversible, 0 is returned.
*
* NOTE: If you know for sure that n is (big) a prime, it may be cheaper to use Fermat's little theorem and get the
* inverse using `Math.modExp(a, n - 2, n)`. See {invModPrime}.
*/
function invMod(uint256 a, uint256 n) internal pure returns (uint256) {
unchecked {
if (n == 0) return 0;
// The inverse modulo is calculated using the Extended Euclidean Algorithm (iterative version)
// Used to compute integers x and y such that: ax + ny = gcd(a, n).
// When the gcd is 1, then the inverse of a modulo n exists and it's x.
// ax + ny = 1
// ax = 1 + (-y)n
// ax ≡ 1 (mod n) # x is the inverse of a modulo n
// If the remainder is 0 the gcd is n right away.
uint256 remainder = a % n;
uint256 gcd = n;
// Therefore the initial coefficients are:
// ax + ny = gcd(a, n) = n
// 0a + 1n = n
int256 x = 0;
int256 y = 1;
while (remainder != 0) {
uint256 quotient = gcd / remainder;
(gcd, remainder) = (
// The old remainder is the next gcd to try.
remainder,
// Compute the next remainder.
// Can't overflow given that (a % gcd) * (gcd // (a % gcd)) <= gcd
// where gcd is at most n (capped to type(uint256).max)
gcd - remainder * quotient
);
(x, y) = (
// Increment the coefficient of a.
y,
// Decrement the coefficient of n.
// Can overflow, but the result is casted to uint256 so that the
// next value of y is "wrapped around" to a value between 0 and n - 1.
x - y * int256(quotient)
);
}
if (gcd != 1) return 0; // No inverse exists.
return ternary(x < 0, n - uint256(-x), uint256(x)); // Wrap the result if it's negative.
}
}
/**
* @dev Variant of {invMod}. More efficient, but only works if `p` is known to be a prime greater than `2`.
*
* From https://en.wikipedia.org/wiki/Fermat%27s_little_theorem[Fermat's little theorem], we know that if p is
* prime, then `a**(p-1) ≡ 1 mod p`. As a consequence, we have `a * a**(p-2) ≡ 1 mod p`, which means that
* `a**(p-2)` is the modular multiplicative inverse of a in Fp.
*
* NOTE: this function does NOT check that `p` is a prime greater than `2`.
*/
function invModPrime(uint256 a, uint256 p) internal view returns (uint256) {
unchecked {
return Math.modExp(a, p - 2, p);
}
}
/**
* @dev Returns the modular exponentiation of the specified base, exponent and modulus (b ** e % m)
*
* Requirements:
* - modulus can't be zero
* - underlying staticcall to precompile must succeed
*
* IMPORTANT: The result is only valid if the underlying call succeeds. When using this function, make
* sure the chain you're using it on supports the precompiled contract for modular exponentiation
* at address 0x05 as specified in https://eips.ethereum.org/EIPS/eip-198[EIP-198]. Otherwise,
* the underlying function will succeed given the lack of a revert, but the result may be incorrectly
* interpreted as 0.
*/
function modExp(uint256 b, uint256 e, uint256 m) internal view returns (uint256) {
(bool success, uint256 result) = tryModExp(b, e, m);
if (!success) {
Panic.panic(Panic.DIVISION_BY_ZERO);
}
return result;
}
/**
* @dev Returns the modular exponentiation of the specified base, exponent and modulus (b ** e % m).
* It includes a success flag indicating if the operation succeeded. Operation will be marked as failed if trying
* to operate modulo 0 or if the underlying precompile reverted.
*
* IMPORTANT: The result is only valid if the success flag is true. When using this function, make sure the chain
* you're using it on supports the precompiled contract for modular exponentiation at address 0x05 as specified in
* https://eips.ethereum.org/EIPS/eip-198[EIP-198]. Otherwise, the underlying function will succeed given the lack
* of a revert, but the result may be incorrectly interpreted as 0.
*/
function tryModExp(uint256 b, uint256 e, uint256 m) internal view returns (bool success, uint256 result) {
if (m == 0) return (false, 0);
assembly ("memory-safe") {
let ptr := mload(0x40)
// | Offset | Content | Content (Hex) |
// |-----------|------------|--------------------------------------------------------------------|
// | 0x00:0x1f | size of b | 0x0000000000000000000000000000000000000000000000000000000000000020 |
// | 0x20:0x3f | size of e | 0x0000000000000000000000000000000000000000000000000000000000000020 |
// | 0x40:0x5f | size of m | 0x0000000000000000000000000000000000000000000000000000000000000020 |
// | 0x60:0x7f | value of b | 0x<.............................................................b> |
// | 0x80:0x9f | value of e | 0x<.............................................................e> |
// | 0xa0:0xbf | value of m | 0x<.............................................................m> |
mstore(ptr, 0x20)
mstore(add(ptr, 0x20), 0x20)
mstore(add(ptr, 0x40), 0x20)
mstore(add(ptr, 0x60), b)
mstore(add(ptr, 0x80), e)
mstore(add(ptr, 0xa0), m)
// Given the result < m, it's guaranteed to fit in 32 bytes,
// so we can use the memory scratch space located at offset 0.
success := staticcall(gas(), 0x05, ptr, 0xc0, 0x00, 0x20)
result := mload(0x00)
}
}
/**
* @dev Variant of {modExp} that supports inputs of arbitrary length.
*/
function modExp(bytes memory b, bytes memory e, bytes memory m) internal view returns (bytes memory) {
(bool success, bytes memory result) = tryModExp(b, e, m);
if (!success) {
Panic.panic(Panic.DIVISION_BY_ZERO);
}
return result;
}
/**
* @dev Variant of {tryModExp} that supports inputs of arbitrary length.
*/
function tryModExp(
bytes memory b,
bytes memory e,
bytes memory m
) internal view returns (bool success, bytes memory result) {
if (_zeroBytes(m)) return (false, new bytes(0));
uint256 mLen = m.length;
// Encode call args in result and move the free memory pointer
result = abi.encodePacked(b.length, e.length, mLen, b, e, m);
assembly ("memory-safe") {
let dataPtr := add(result, 0x20)
// Write result on top of args to avoid allocating extra memory.
success := staticcall(gas(), 0x05, dataPtr, mload(result), dataPtr, mLen)
// Overwrite the length.
// result.length > returndatasize() is guaranteed because returndatasize() == m.length
mstore(result, mLen)
// Set the memory pointer after the returned data.
mstore(0x40, add(dataPtr, mLen))
}
}
/**
* @dev Returns whether the provided byte array is zero.
*/
function _zeroBytes(bytes memory byteArray) private pure returns (bool) {
for (uint256 i = 0; i < byteArray.length; ++i) {
if (byteArray[i] != 0) {
return false;
}
}
return true;
}
/**
* @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded
* towards zero.
*
* This method is based on Newton's method for computing square roots; the algorithm is restricted to only
* using integer operations.
*/
function sqrt(uint256 a) internal pure returns (uint256) {
unchecked {
// Take care of easy edge cases when a == 0 or a == 1
if (a <= 1) {
return a;
}
// In this function, we use Newton's method to get a root of `f(x) := x² - a`. It involves building a
// sequence x_n that converges toward sqrt(a). For each iteration x_n, we also define the error between
// the current value as `ε_n = | x_n - sqrt(a) |`.
//
// For our first estimation, we consider `e` the smallest power of 2 which is bigger than the square root
// of the target. (i.e. `2**(e-1) ≤ sqrt(a) < 2**e`). We know that `e ≤ 128` because `(2¹²⁸)² = 2²⁵⁶` is
// bigger than any uint256.
//
// By noticing that
// `2**(e-1) ≤ sqrt(a) < 2**e → (2**(e-1))² ≤ a < (2**e)² → 2**(2*e-2) ≤ a < 2**(2*e)`
// we can deduce that `e - 1` is `log2(a) / 2`. We can thus compute `x_n = 2**(e-1)` using a method similar
// to the msb function.
uint256 aa = a;
uint256 xn = 1;\
Submitted on: 2025-09-19 10:41:18
Comments
Log in to comment.
No comments yet.