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/daoCollateral/DaoCollateral.sol": {
"content": "// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.20;
import {SafeERC20} from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";
import {EIP712Upgradeable} from
"openzeppelin-contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol";
import {IERC20Permit} from "openzeppelin-contracts/token/ERC20/extensions/IERC20Permit.sol";
import {IERC20} from "openzeppelin-contracts/token/ERC20/IERC20.sol";
import {SignatureChecker} from "openzeppelin-contracts/utils/cryptography/SignatureChecker.sol";
import {IERC20Metadata} from "openzeppelin-contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {Math} from "openzeppelin-contracts/utils/math/Math.sol";
import {ReentrancyGuardUpgradeable} from
"openzeppelin-contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
import {PausableUpgradeable} from "openzeppelin-contracts-upgradeable/utils/PausableUpgradeable.sol";
import {NoncesUpgradeable} from "src/utils/NoncesUpgradeable.sol";
import {IRegistryAccess} from "src/interfaces/registry/IRegistryAccess.sol";
import {IEur0} from "src/interfaces/token/IEur0.sol";
import {IRegistryContract} from "src/interfaces/registry/IRegistryContract.sol";
import {ITokenMapping} from "src/interfaces/tokenManager/ITokenMapping.sol";
import {IOracle} from "src/interfaces/oracles/IOracle.sol";
import {IDaoCollateral, Approval, Intent} from "src/interfaces/IDaoCollateral.sol";
import {ISwapperEngine} from "src/interfaces/swapperEngine/ISwapperEngine.sol";
import {CheckAccessControl} from "src/utils/CheckAccessControl.sol";
import {Normalize} from "src/utils/normalize.sol";
import {
SCALAR_ONE,
MIN_REDEEM_AMOUNT_IN_EUR0,
DEFAULT_ADMIN_ROLE,
MAX_REDEEM_FEE,
BASIS_POINT_BASE,
CONTRACT_YIELD_TREASURY,
PAUSING_CONTRACTS_ROLE,
DAO_REDEMPTION_ROLE,
UNPAUSING_CONTRACTS_ROLE,
CONTRACT_REGISTRY_ACCESS,
CONTRACT_TREASURY,
CONTRACT_TOKEN_MAPPING,
CONTRACT_EUR0,
CONTRACT_SWAPPER_ENGINE,
CONTRACT_ORACLE,
NONCE_THRESHOLD_SETTER_ROLE,
INTENT_MATCHING_ROLE,
INTENT_TYPE_HASH,
MIN_REDEEM_AMOUNT_SET_ROLE
} from "src/constants.sol";
import {
InvalidToken,
AmountIsZero,
AmountTooLow,
AmountTooBig,
ApprovalFailed,
RedeemMustNotBePaused,
RedeemMustBePaused,
SwapMustNotBePaused,
SwapMustBePaused,
SameValue,
CBRIsTooHigh,
CBRIsNull,
RedeemFeeTooBig,
RedeemFeeCannotBeZero,
NullContract,
InvalidSigner,
InvalidDeadline,
ExpiredSignature,
NoOrdersIdsProvided,
InvalidOrderAmount
} from "src/errors.sol";
/// @title DaoCollateral Contract
/// @notice Manages the swapping of Euro collateral tokens for EUR0, with functionalities for swap (direct mint) and redeeming tokens
/// @dev Provides mechanisms for token swap operations, fee management, called Dao Collateral for historical reasons
/// @author Usual Tech team
contract DaoCollateral is
ReentrancyGuardUpgradeable,
PausableUpgradeable,
NoncesUpgradeable,
EIP712Upgradeable,
IDaoCollateral
{
using SafeERC20 for IERC20Metadata;
using CheckAccessControl for IRegistryAccess;
using Normalize for uint256;
struct DaoCollateralStorageV0 {
/// @notice Indicates if the redeem functionality is paused.
bool _redeemPaused;
/// @notice Indicates if the swap functionality is paused.
bool _swapPaused;
/// @notice Indicates if the Counter Bank Run (CBR) functionality is active.
bool isCBROn;
/// @notice The fee for redeeming tokens, in basis points.
uint256 redeemFee;
/// @notice The coefficient for calculating the returned collateralToken amount when CBR is active.
uint256 cbrCoef;
/// @notice The RegistryAccess contract instance for role checks.
IRegistryAccess registryAccess;
/// @notice The TokenMapping contract instance for managing token mappings.
ITokenMapping tokenMapping;
/// @notice The EUR0 token contract instance.
IEur0 eur0;
/// @notice The Oracle contract instance for price feeds.
IOracle oracle;
/// @notice The address of treasury holding collateral tokens.
address treasury;
/// @notice The SwapperEngine contract instance for managing token swaps.
ISwapperEngine swapperEngine;
/// @notice the threshold for intents to be considered used in EUR0 swapCollateralTokenToEurcIntent
uint256 nonceThreshold;
/// @notice The mapping of the amount of the order taken that matches up to current nonce for each account
mapping(address account => uint256) _orderAmountTaken;
/// @notice The address of treasury holding fee tokens.
address treasuryYield;
/// @notice The minimum amount of EUR0 that can be redeemed.
uint256 minimumRedeemAmount;
}
// keccak256(abi.encode(uint256(keccak256("daoCollateral.storage.v0")) - 1)) & ~bytes32(uint256(0xff))
// solhint-disable-next-line
bytes32 public constant DaoCollateralStorageV0Location =
0xb6b5806749b83e5a37ff64f3aa7a7ce3ac6e8a80a998e853c1d3efe545237c00;
/*//////////////////////////////////////////////////////////////
Constructor
//////////////////////////////////////////////////////////////*/
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
/// @notice Initializes the DaoCollateral contract with registry information and initial configuration.
/// @param _registryContract The address of the registry contract.
/// @param _redeemFee The initial redeem fee, in basis points.
function initialize(address _registryContract, uint256 _redeemFee) public initializer {
// can't have a redeem fee greater than 25%
if (_redeemFee > MAX_REDEEM_FEE) {
revert RedeemFeeTooBig();
}
if (_redeemFee == 0) {
revert RedeemFeeCannotBeZero();
}
if (_registryContract == address(0)) {
revert NullContract();
}
__ReentrancyGuard_init_unchained();
__Pausable_init_unchained();
__EIP712_init_unchained("DaoCollateral", "1");
DaoCollateralStorageV0 storage $ = _daoCollateralStorageV0();
$.redeemFee = _redeemFee;
emit RedeemFeeUpdated(_redeemFee);
IRegistryContract registryContract = IRegistryContract(_registryContract);
$.registryAccess = IRegistryAccess(registryContract.getContract(CONTRACT_REGISTRY_ACCESS));
$.treasury = address(registryContract.getContract(CONTRACT_TREASURY));
$.tokenMapping = ITokenMapping(registryContract.getContract(CONTRACT_TOKEN_MAPPING));
$.eur0 = IEur0(registryContract.getContract(CONTRACT_EUR0));
$.oracle = IOracle(registryContract.getContract(CONTRACT_ORACLE));
$.swapperEngine = ISwapperEngine(registryContract.getContract(CONTRACT_SWAPPER_ENGINE));
$.minimumRedeemAmount = MIN_REDEEM_AMOUNT_IN_EUR0;
emit MinimumRedeemAmountUpdated(MIN_REDEEM_AMOUNT_IN_EUR0);
$.treasuryYield = registryContract.getContract(CONTRACT_YIELD_TREASURY);
}
/// @notice Returns the storage struct of the contract.
/// @return $ The pointer to the storage struct of the contract.
function _daoCollateralStorageV0() internal pure returns (DaoCollateralStorageV0 storage $) {
bytes32 position = DaoCollateralStorageV0Location;
// solhint-disable-next-line no-inline-assembly
assembly {
$.slot := position
}
}
/*//////////////////////////////////////////////////////////////
Setters
//////////////////////////////////////////////////////////////*/
/// @inheritdoc IDaoCollateral
function activateCBR(uint256 coefficient) external {
// we should revert if the coef is greater than 1
if (coefficient > SCALAR_ONE) {
revert CBRIsTooHigh();
} else if (coefficient == 0) {
revert CBRIsNull();
}
DaoCollateralStorageV0 storage $ = _daoCollateralStorageV0();
$.registryAccess.onlyMatchingRole(DEFAULT_ADMIN_ROLE);
$.isCBROn = true;
$._swapPaused = true;
$.cbrCoef = coefficient;
emit CBRActivated($.cbrCoef);
emit SwapPaused();
}
/// @inheritdoc IDaoCollateral
function deactivateCBR() external {
DaoCollateralStorageV0 storage $ = _daoCollateralStorageV0();
$.registryAccess.onlyMatchingRole(DEFAULT_ADMIN_ROLE);
if ($.isCBROn == false) revert SameValue();
$.isCBROn = false;
$.cbrCoef = 0;
emit CBRDeactivated();
}
/// @inheritdoc IDaoCollateral
function setMinimumRedeemAmount(uint256 _minimumRedeemAmount) external {
DaoCollateralStorageV0 storage $ = _daoCollateralStorageV0();
$.registryAccess.onlyMatchingRole(MIN_REDEEM_AMOUNT_SET_ROLE);
if (_minimumRedeemAmount == 0) revert AmountTooLow();
if ($.minimumRedeemAmount == _minimumRedeemAmount) revert SameValue();
$.minimumRedeemAmount = _minimumRedeemAmount;
emit MinimumRedeemAmountUpdated(_minimumRedeemAmount);
}
/// @inheritdoc IDaoCollateral
function setRedeemFee(uint256 _redeemFee) external {
if (_redeemFee > MAX_REDEEM_FEE) revert RedeemFeeTooBig();
if (_redeemFee == 0) revert RedeemFeeCannotBeZero();
DaoCollateralStorageV0 storage $ = _daoCollateralStorageV0();
$.registryAccess.onlyMatchingRole(DEFAULT_ADMIN_ROLE);
if ($.redeemFee == _redeemFee) revert SameValue();
$.redeemFee = _redeemFee;
emit RedeemFeeUpdated(_redeemFee);
}
/// @inheritdoc IDaoCollateral
function setNonceThreshold(uint256 threshold) external {
DaoCollateralStorageV0 storage $ = _daoCollateralStorageV0();
$.registryAccess.onlyMatchingRole(NONCE_THRESHOLD_SETTER_ROLE);
$.nonceThreshold = threshold;
emit NonceThresholdSet(threshold);
}
/// @inheritdoc IDaoCollateral
function pauseRedeem() external {
DaoCollateralStorageV0 storage $ = _daoCollateralStorageV0();
$.registryAccess.onlyMatchingRole(PAUSING_CONTRACTS_ROLE);
if ($._redeemPaused) {
revert RedeemMustNotBePaused();
}
$._redeemPaused = true;
emit RedeemPaused();
}
/// @inheritdoc IDaoCollateral
function unpauseRedeem() external {
DaoCollateralStorageV0 storage $ = _daoCollateralStorageV0();
$.registryAccess.onlyMatchingRole(UNPAUSING_CONTRACTS_ROLE);
if (!$._redeemPaused) {
revert RedeemMustBePaused();
}
$._redeemPaused = false;
emit RedeemUnPaused();
}
/// @inheritdoc IDaoCollateral
function pauseSwap() external {
DaoCollateralStorageV0 storage $ = _daoCollateralStorageV0();
$.registryAccess.onlyMatchingRole(PAUSING_CONTRACTS_ROLE);
if ($._swapPaused) {
revert SwapMustNotBePaused();
}
$._swapPaused = true;
emit SwapPaused();
}
/// @inheritdoc IDaoCollateral
function unpauseSwap() external {
DaoCollateralStorageV0 storage $ = _daoCollateralStorageV0();
$.registryAccess.onlyMatchingRole(UNPAUSING_CONTRACTS_ROLE);
if (!$._swapPaused) {
revert SwapMustBePaused();
}
$._swapPaused = false;
emit SwapUnPaused();
}
/// @inheritdoc IDaoCollateral
function pause() external {
DaoCollateralStorageV0 storage $ = _daoCollateralStorageV0();
$.registryAccess.onlyMatchingRole(PAUSING_CONTRACTS_ROLE);
_pause();
}
/// @inheritdoc IDaoCollateral
function unpause() external {
DaoCollateralStorageV0 storage $ = _daoCollateralStorageV0();
$.registryAccess.onlyMatchingRole(UNPAUSING_CONTRACTS_ROLE);
_unpause();
}
/*//////////////////////////////////////////////////////////////
Internal
//////////////////////////////////////////////////////////////*/
/// @notice _swapCheckAndGetEURQuote method will check if the token is a EUR0-supported collateral token and if the amount is not 0
/// @dev Function that do sanity check on the inputs
/// @dev and return the normalized EUR quoted price of collateral tokens for the given amount
/// @param collateralToken address of the token to swap MUST be a collateral token.
/// @param amountInToken amount of collateral token to swap.
/// @return wadQuoteInEUR The quoted amount in EUR with 18 decimals for the specified token and amount.
function _swapCheckAndGetEURQuote(address collateralToken, uint256 amountInToken)
internal
view
returns (uint256 wadQuoteInEUR)
{
if (amountInToken == 0) {
revert AmountIsZero();
}
// Amount can't be greater than uint128
if (amountInToken > type(uint128).max) {
revert AmountTooBig();
}
DaoCollateralStorageV0 storage $ = _daoCollateralStorageV0();
if (!$.tokenMapping.isEur0Collateral(collateralToken)) {
revert InvalidToken();
}
wadQuoteInEUR = _getQuoteInEUR(amountInToken, collateralToken);
//slither-disable-next-line incorrect-equality
if (wadQuoteInEUR == 0) {
revert AmountTooLow();
}
}
/// @notice transfers Collateral Token And Mint EUR0
/// @dev will transfer the collateral to the treasury and mints the corresponding stableAmount in EUR0
/// @param collateralToken address of the token to swap MUST be a collateral token.
/// @param amount amount of collateral token to swap.
/// @param wadAmountInEUR0 amount of EUR0 to mint.
function _transferCollateralTokenAndMintEur0(
address collateralToken,
uint256 amount,
uint256 wadAmountInEUR0
) internal {
DaoCollateralStorageV0 storage $ = _daoCollateralStorageV0();
// Should revert if balance is insufficient
IERC20Metadata(address(collateralToken)).safeTransferFrom(msg.sender, $.treasury, amount);
// Mint some EUR0
$.eur0.mint(msg.sender, wadAmountInEUR0);
}
/// @dev call the oracle to get the price in EUR
/// @param collateralToken the collateral token address
/// @return wadPriceInEUR the price in EUR with 18 decimals
/// @return decimals number of decimals of the token
function _getPriceAndDecimals(address collateralToken)
internal
view
returns (uint256 wadPriceInEUR, uint8 decimals)
{
DaoCollateralStorageV0 storage $ = _daoCollateralStorageV0();
wadPriceInEUR = uint256($.oracle.getPrice(collateralToken));
decimals = uint8(IERC20Metadata(collateralToken).decimals());
}
/// @notice get the price in EUR of an `tokenAmount` of `collateralToken`
/// @dev call the oracle to get the price in EUR of `tokenAmount` of token with 18 decimals
/// @param tokenAmount the amount of token to convert in EUR with 18 decimals
/// @param collateralToken the collateral token address
/// @return wadAmountInEUR the amount in EUR with 18 decimals
function _getQuoteInEUR(uint256 tokenAmount, address collateralToken)
internal
view
returns (uint256 wadAmountInEUR)
{
(uint256 wadPriceInEUR, uint8 decimals) = _getPriceAndDecimals(collateralToken);
uint256 wadAmount = tokenAmount.tokenAmountToWad(decimals);
wadAmountInEUR = Math.mulDiv(wadAmount, wadPriceInEUR, SCALAR_ONE, Math.Rounding.Floor);
}
/// @notice get the amount of token for an amount of EUR
/// @dev call the oracle to get the price in EUR of `amount` of token with 18 decimals
/// @param wadStableAmount the amount of EUR with 18 decimals
/// @param collateralToken the collateral token address
/// @return amountInToken the amount in token corresponding to the amount of EUR
function _getQuoteInTokenFromEUR(uint256 wadStableAmount, address collateralToken)
internal
view
returns (uint256 amountInToken)
{
(uint256 wadPriceInEUR, uint8 decimals) = _getPriceAndDecimals(collateralToken);
// will result in an amount with the same 'decimals' as the token
amountInToken = wadStableAmount.wadTokenAmountForPrice(wadPriceInEUR, decimals);
}
/// @notice Calculates the returned amount of collateralToken give an amount of EUR
/// @dev return the amountInToken of token for `wadStableAmount` of EUR at the current price
/// @param wadStableAmount the amount of EUR
/// @param collateralToken the collateral token address
/// @return amountInToken the amount of token that is worth `wadStableAmount` of EUR with 18 decimals
function _getTokenAmountForAmountInEUR(uint256 wadStableAmount, address collateralToken)
internal
view
returns (uint256 amountInToken)
{
DaoCollateralStorageV0 storage $ = _daoCollateralStorageV0();
amountInToken = _getQuoteInTokenFromEUR(wadStableAmount, collateralToken);
// if cbr is on we need to apply the coef to the collateral price
// cbrCoef should be less than 1e18
if ($.isCBROn) {
amountInToken = Math.mulDiv(amountInToken, $.cbrCoef, SCALAR_ONE, Math.Rounding.Floor);
}
}
/// @notice _calculateFee method will calculate the EUR0 redeem fee
/// @dev Function that transfer the fee to the treasury
/// @dev The fee is calculated as a percentage of the amount of EUR0 to redeem
/// @dev The fee is minted to avoid transfer and allowance as the whole EUR0 amount is burnt afterwards
/// @param eur0Amount Amount of EUR0 to transfer to treasury.
/// @param collateralToken address of the collateral token to swap should be a collateral token.
/// @return stableFee The amount of EUR0 minted as fees for the treasury.
function _calculateFee(uint256 eur0Amount, address collateralToken)
internal
view
returns (uint256 stableFee)
{
DaoCollateralStorageV0 storage $ = _daoCollateralStorageV0();
stableFee = Math.mulDiv(eur0Amount, $.redeemFee, BASIS_POINT_BASE, Math.Rounding.Floor);
uint8 tokenDecimals = IERC20Metadata(collateralToken).decimals();
// if the token has less decimals than EUR0 we need to normalize the fee
if (tokenDecimals < 18) {
// we scale down the fee to the token decimals
// and we scale it up to 18 decimals
stableFee = Normalize.tokenAmountToWad(
Normalize.wadAmountToDecimals(stableFee, tokenDecimals), tokenDecimals
);
}
}
/// @notice _burnEur0TokenAndTransferCollateral method will burn the EUR0 token and transfer the collateral token
/// @param collateralToken address of the token to swap should be a collateral token.
/// @param eur0Amount amount of EUR0 to swap.
/// @param stableFee amount of fee in EUR0.
/// @return returnedCollateral The amount of collateral token returned.
function _burnEur0TokenAndTransferCollateral(
address collateralToken,
uint256 eur0Amount,
uint256 stableFee
) internal returns (uint256 returnedCollateral) {
DaoCollateralStorageV0 storage $ = _daoCollateralStorageV0();
// we burn the remaining EUR0 token
uint256 burnedEur0 = eur0Amount - stableFee;
// we burn all the EUR0 token
$.eur0.burnFrom(msg.sender, eur0Amount);
// get the amount of collateral token for the amount of EUR0 burned by calling the oracle
returnedCollateral = _getTokenAmountForAmountInEUR(burnedEur0, collateralToken);
if (returnedCollateral == 0) {
revert AmountTooLow();
}
// we distribute the collateral token from the treasury to the user
// slither-disable-next-line arbitrary-send-erc20
IERC20Metadata(collateralToken).safeTransferFrom($.treasury, msg.sender, returnedCollateral);
// If the CBR is on, the fees are forfeited from the yield treasury to favor the collateralization ratio
if (stableFee > 0 && !$.isCBROn) {
$.eur0.mint($.treasuryYield, stableFee);
}
}
/// @notice Partially or fully consumes an amount of an intent
/// @dev Function should be called internally after verifying the intent is valid
/// @param amount The amount to consume from the remaining reusable intent
/// @param intent Intent data and signature of data
function _useIntentAmount(uint256 amount, Intent memory intent) internal virtual {
DaoCollateralStorageV0 storage $ = _daoCollateralStorageV0();
// check that the amount they want to use is less than the remaining amount unmatched in the smart order
uint256 remainingAmountUnmatched =
(intent.amountInTokenDecimals - $._orderAmountTaken[intent.recipient]);
if (amount > remainingAmountUnmatched) {
revert InvalidOrderAmount(intent.recipient, amount);
} else if ((remainingAmountUnmatched - amount) <= $.nonceThreshold) {
emit IntentConsumed(
intent.recipient,
nonces(intent.recipient),
intent.rwaToken,
intent.amountInTokenDecimals
);
_useNonce(intent.recipient);
//reset the intent amount taken for the next nonce
$._orderAmountTaken[intent.recipient] = 0;
} else {
$._orderAmountTaken[intent.recipient] += amount;
}
}
/// @notice Checks if an Intent is valid by verifying its signature and returns the remaining amount unmatched
/// @dev Function should be called internally before any fields from intent are used in contract logic
/// @param intent Intent data and signature of data
/// @return remainingAmountUnmatched Amount left in the current intent for swap
/// @return nonce The current nonce for this reusable intent
function _isValidIntent(Intent calldata intent)
internal
virtual
returns (uint256 remainingAmountUnmatched, uint256 nonce)
{
DaoCollateralStorageV0 storage $ = _daoCollateralStorageV0();
//if we increment the nonce this wont match so the user can cancel the order essentially
uint256 currentOrderNonce = nonces(intent.recipient);
// check the signature w/ nonce
bytes32 structHash = keccak256(
abi.encode(
INTENT_TYPE_HASH,
intent.recipient,
intent.rwaToken,
intent.amountInTokenDecimals,
currentOrderNonce,
intent.deadline
)
);
bytes32 hash = _hashTypedDataV4(structHash);
if (!SignatureChecker.isValidSignatureNow(intent.recipient, hash, intent.signature)) {
revert InvalidSigner(intent.recipient);
}
if ($._orderAmountTaken[intent.recipient] > intent.amountInTokenDecimals) {
revert InvalidOrderAmount(intent.recipient, intent.amountInTokenDecimals);
}
// return the amount left to be filled
remainingAmountUnmatched =
(intent.amountInTokenDecimals - $._orderAmountTaken[intent.recipient]);
return (remainingAmountUnmatched, currentOrderNonce);
}
/// @notice Swap Collateral Token for EURC through offers on the SwapperContract
/// @dev Takes Collateral Token, mints EUR0 and provides it to the Swapper Contract directly
/// Sends EUR0 to the offer's creator and sends EURC to the recipient
/// @dev the recipient Address to receive the EURC is msg.sender
/// @param caller Address of the caller (msg.sender or intent recipient)
/// @param collateralToken Address of the Collateral Token to swap for EURC
/// @param amountInTokenDecimals Amount of the Collateral Token to swap for EURC
/// @param partialMatching flag to allow partial matching
/// @param orderIdsToTake orderIds to be taken
/// @param approval ERC20Permit approval data and signature of data
/// @return matchedAmountInTokenDecimals The amount of collateral tokens which have been matched.
/// @return matchedAmountInEUR The net amount of EUR0 tokens minted.
// solhint-disable-next-line code-complexity
function _swapCollateralTokenToEurc(
address caller,
address collateralToken,
uint256 amountInTokenDecimals,
bool partialMatching,
uint256[] calldata orderIdsToTake,
Approval calldata approval
) internal returns (uint256 matchedAmountInTokenDecimals, uint256 matchedAmountInEUR) {
if (amountInTokenDecimals == 0) {
revert AmountIsZero();
}
if (amountInTokenDecimals > type(uint128).max) {
revert AmountTooBig();
}
if (orderIdsToTake.length == 0) {
revert NoOrdersIdsProvided();
}
DaoCollateralStorageV0 storage $ = _daoCollateralStorageV0();
if (!$.tokenMapping.isEur0Collateral(collateralToken)) {
revert InvalidToken();
}
// Check if the approval isn't null, if it isn't, use it for the permit
if (approval.deadline != 0 && approval.v != 0 && approval.r != 0 && approval.s != 0) {
// Authorization transfer
try IERC20Permit(collateralToken).permit( //NOTE: this will fail if permit already used but that's ok as long as there is enough allowance
caller,
address(this),
type(uint256).max,
approval.deadline,
approval.v,
approval.r,
approval.s
) {} catch {} // solhint-disable-line no-empty-blocks
}
// Take the collateral token from the recipient
IERC20Metadata(collateralToken).safeTransferFrom(caller, $.treasury, amountInTokenDecimals);
// Get the price quote of the collateral token to mint EUR0
uint256 wadCollateralQuoteInEUR = _getQuoteInEUR(amountInTokenDecimals, collateralToken);
// Mint the corresponding amount of EUR0 stablecoin
$.eur0.mint(address(this), wadCollateralQuoteInEUR);
if (!IERC20($.eur0).approve(address($.swapperEngine), wadCollateralQuoteInEUR)) {
revert ApprovalFailed();
}
// Provide the EUR0 to the SwapperEngine and receive EURC for the caller
uint256 wadCollateralNotTakenInEUR = $.swapperEngine.swapEur0(
caller, wadCollateralQuoteInEUR, orderIdsToTake, partialMatching
);
// Burn any unmatched EUR0 and return the collateral token
if (wadCollateralNotTakenInEUR > 0) {
if (!IERC20($.eur0).approve(address($.swapperEngine), 0)) {
revert ApprovalFailed();
}
$.eur0.burnFrom(address(this), wadCollateralNotTakenInEUR);
// Get amount of collateral token for the wadCollateralNotTakenInEUR pricing
uint256 collateralTokensToReturn =
_getQuoteInTokenFromEUR(wadCollateralNotTakenInEUR, collateralToken);
// Transfer back the remaining collateral tokens to the recipient
// slither-disable-next-line arbitrary-send-erc20-permit
IERC20Metadata(collateralToken).safeTransferFrom(
$.treasury, caller, collateralTokensToReturn
);
matchedAmountInTokenDecimals = amountInTokenDecimals - collateralTokensToReturn;
} else {
matchedAmountInTokenDecimals = amountInTokenDecimals;
}
matchedAmountInEUR = wadCollateralQuoteInEUR - wadCollateralNotTakenInEUR;
emit Swap(caller, collateralToken, matchedAmountInTokenDecimals, matchedAmountInEUR);
return (matchedAmountInTokenDecimals, matchedAmountInEUR);
}
/*//////////////////////////////////////////////////////////////
External
//////////////////////////////////////////////////////////////*/
/// @inheritdoc IDaoCollateral
function swap(address collateralToken, uint256 amount, uint256 minAmountOut)
public
nonReentrant
whenNotPaused
{
DaoCollateralStorageV0 storage $ = _daoCollateralStorageV0();
if ($._swapPaused) {
revert SwapMustNotBePaused();
}
uint256 wadQuoteInEUR = _swapCheckAndGetEURQuote(collateralToken, amount);
// Check if the amount is greater than the minAmountOut
if (wadQuoteInEUR < minAmountOut) {
revert AmountTooLow();
}
_transferCollateralTokenAndMintEur0(collateralToken, amount, wadQuoteInEUR);
emit Swap(msg.sender, collateralToken, amount, wadQuoteInEUR);
}
/// @inheritdoc IDaoCollateral
function swapWithPermit(
address collateralToken,
uint256 amount,
uint256 minAmountOut,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external {
// solhint-disable-next-line no-empty-blocks
try IERC20Permit(collateralToken).permit(
msg.sender, address(this), amount, deadline, v, r, s
) {} catch {} // solhint-disable-line no-empty-blocks
swap(collateralToken, amount, minAmountOut);
}
/// @inheritdoc IDaoCollateral
function redeem(address collateralToken, uint256 amount, uint256 minAmountOut)
external
nonReentrant
whenNotPaused
{
DaoCollateralStorageV0 storage $ = _daoCollateralStorageV0();
// Amount can't be less than the minimum redeem amount
if (amount < $.minimumRedeemAmount) {
revert AmountTooLow();
}
if ($._redeemPaused) {
revert RedeemMustNotBePaused();
}
// check that collateralToken is a collateral token
if (!$.tokenMapping.isEur0Collateral(collateralToken)) {
revert InvalidToken();
}
uint256 stableFee = _calculateFee(amount, collateralToken);
if (stableFee == 0) revert RedeemFeeCannotBeZero();
uint256 returnedCollateral =
_burnEur0TokenAndTransferCollateral(collateralToken, amount, stableFee);
// Check if the amount is greater than the minAmountOut
if (returnedCollateral < minAmountOut) {
revert AmountTooLow();
}
emit Redeem(msg.sender, collateralToken, amount, returnedCollateral, stableFee);
}
/// @inheritdoc IDaoCollateral
function redeemDao(address collateralToken, uint256 amount)
external
nonReentrant
whenNotPaused
{
// Amount can't be 0
if (amount == 0) {
revert AmountIsZero();
}
DaoCollateralStorageV0 storage $ = _daoCollateralStorageV0();
$.registryAccess.onlyMatchingRole(DAO_REDEMPTION_ROLE);
// check that collateralToken is a collateral token
if (!$.tokenMapping.isEur0Collateral(collateralToken)) {
revert InvalidToken();
}
uint256 returnedCollateral = _burnEur0TokenAndTransferCollateral(collateralToken, amount, 0);
emit Redeem(msg.sender, collateralToken, amount, returnedCollateral, 0);
}
/// @inheritdoc IDaoCollateral
function swapCollateralTokenToEurc(
address collateralToken,
uint256 amountInTokenDecimals,
bool partialMatching,
uint256[] calldata orderIdsToTake,
Approval calldata approval
) external nonReentrant whenNotPaused {
DaoCollateralStorageV0 storage $ = _daoCollateralStorageV0();
if ($._swapPaused) {
revert SwapMustNotBePaused();
}
_swapCollateralTokenToEurc(
msg.sender,
collateralToken,
amountInTokenDecimals,
partialMatching,
orderIdsToTake,
approval
);
}
/// @inheritdoc IDaoCollateral
function invalidateNonce() external {
uint256 nonceUsed = _useNonce(msg.sender);
_daoCollateralStorageV0()._orderAmountTaken[msg.sender] = 0;
emit NonceInvalidated(msg.sender, nonceUsed);
}
/// @inheritdoc IDaoCollateral
function invalidateUpToNonce(uint256 newNonce) external {
_invalidateUpToNonce(msg.sender, newNonce);
_daoCollateralStorageV0()._orderAmountTaken[msg.sender] = 0;
emit NonceInvalidated(msg.sender, newNonce - 1);
}
/// @inheritdoc IDaoCollateral
function swapCollateralTokenToEurcIntent(
uint256[] calldata orderIdsToTake,
Approval calldata approval,
Intent calldata intent,
bool partialMatching
) external nonReentrant whenNotPaused {
if (block.timestamp > intent.deadline) {
revert ExpiredSignature(intent.deadline);
}
if (approval.deadline != intent.deadline) {
revert InvalidDeadline(approval.deadline, intent.deadline);
}
DaoCollateralStorageV0 storage $ = _daoCollateralStorageV0();
$.registryAccess.onlyMatchingRole(INTENT_MATCHING_ROLE);
if ($._swapPaused) {
revert SwapMustNotBePaused();
}
(uint256 remainingAmountUnmatched, uint256 nonce) = _isValidIntent(intent);
//NOTE: if its a full match then we increment the nonce
if (!partialMatching) {
emit IntentConsumed(
intent.recipient, nonce, intent.rwaToken, intent.amountInTokenDecimals
);
_useNonce(intent.recipient);
$._orderAmountTaken[intent.recipient] = 0;
}
(uint256 matchedAmountInTokenDecimals, uint256 matchedAmountInEUR) =
_swapCollateralTokenToEurc(
intent.recipient,
intent.rwaToken,
remainingAmountUnmatched,
partialMatching,
orderIdsToTake,
approval
);
//NOTE: if it is a partial match then we deduct the matched amount from the remaining unmatched, if the intent is used up then increment the nonce
if (partialMatching) {
_useIntentAmount(matchedAmountInTokenDecimals, intent);
}
emit IntentMatched(
intent.recipient,
nonce,
intent.rwaToken,
matchedAmountInTokenDecimals,
matchedAmountInEUR
);
}
/*//////////////////////////////////////////////////////////////
Getters
//////////////////////////////////////////////////////////////*/
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view virtual returns (bytes32) {
return _domainSeparatorV4();
}
/// @inheritdoc IDaoCollateral
function orderAmountTakenCurrentNonce(address owner) public view virtual returns (uint256) {
DaoCollateralStorageV0 storage $ = _daoCollateralStorageV0();
return $._orderAmountTaken[owner];
}
/// @inheritdoc IDaoCollateral
function nonceThreshold() external view returns (uint256) {
DaoCollateralStorageV0 storage $ = _daoCollateralStorageV0();
return $.nonceThreshold;
}
/// @inheritdoc IDaoCollateral
function isCBROn() external view returns (bool) {
DaoCollateralStorageV0 storage $ = _daoCollateralStorageV0();
return $.isCBROn;
}
/// @inheritdoc IDaoCollateral
function cbrCoef() public view returns (uint256) {
DaoCollateralStorageV0 storage $ = _daoCollateralStorageV0();
return $.cbrCoef;
}
/// @inheritdoc IDaoCollateral
function minimumRedeemAmount() public view returns (uint256) {
DaoCollateralStorageV0 storage $ = _daoCollateralStorageV0();
return $.minimumRedeemAmount;
}
/// @inheritdoc IDaoCollateral
function redeemFee() public view returns (uint256) {
DaoCollateralStorageV0 storage $ = _daoCollateralStorageV0();
return $.redeemFee;
}
/// @inheritdoc IDaoCollateral
function isRedeemPaused() public view returns (bool) {
DaoCollateralStorageV0 storage $ = _daoCollateralStorageV0();
return $._redeemPaused;
}
/// @inheritdoc IDaoCollateral
function isSwapPaused() public view returns (bool) {
DaoCollateralStorageV0 storage $ = _daoCollateralStorageV0();
return $._swapPaused;
}
}
"
},
"lib/openzeppelin-contracts/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;
}
}
"
},
"lib/openzeppelin-contracts-upgradeable/contracts/utils/cryptography/EIP712Upgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/EIP712.sol)
pragma solidity ^0.8.20;
import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
import {IERC5267} from "@openzeppelin/contracts/interfaces/IERC5267.sol";
import {Initializable} from "../../proxy/utils/Initializable.sol";
/**
* @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data.
*
* The encoding scheme specified in the EIP requires a domain separator and a hash of the typed structured data, whose
* encoding is very generic and therefore its implementation in Solidity is not feasible, thus this contract
* does not implement the encoding itself. Protocols need to implement the type-specific encoding they need in order to
* produce the hash of their typed data using a combination of `abi.encode` and `keccak256`.
*
* This contract implements the EIP 712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding
* scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA
* ({_hashTypedDataV4}).
*
* The implementation of the domain separator was designed to be as efficient as possible while still properly updating
* the chain id to protect against replay attacks on an eventual fork of the chain.
*
* NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method
* https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask].
*
* NOTE: In the upgradeable version of this contract, the cached values will correspond to the address, and the domain
* separator of the implementation contract. This will cause the {_domainSeparatorV4} function to always rebuild the
* separator from the immutable values, which is cheaper than accessing a cached version in cold storage.
*/
abstract contract EIP712Upgradeable is Initializable, IERC5267 {
bytes32 private constant TYPE_HASH =
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
/// @custom:storage-location erc7201:openzeppelin.storage.EIP712
struct EIP712Storage {
/// @custom:oz-renamed-from _HASHED_NAME
bytes32 _hashedName;
/// @custom:oz-renamed-from _HASHED_VERSION
bytes32 _hashedVersion;
string _name;
string _version;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.EIP712")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant EIP712StorageLocation = 0xa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d100;
function _getEIP712Storage() private pure returns (EIP712Storage storage $) {
assembly {
$.slot := EIP712StorageLocation
}
}
/**
* @dev Initializes the domain separator and parameter caches.
*
* The meaning of `name` and `version` is specified in
* https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP 712]:
*
* - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol.
* - `version`: the current major version of the signing domain.
*
* NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart
* contract upgrade].
*/
function __EIP712_init(string memory name, string memory version) internal onlyInitializing {
__EIP712_init_unchained(name, version);
}
function __EIP712_init_unchained(string memory name, string memory version) internal onlyInitializing {
EIP712Storage storage $ = _getEIP712Storage();
$._name = name;
$._version = version;
// Reset prior values in storage if upgrading
$._hashedName = 0;
$._hashedVersion = 0;
}
/**
* @dev Returns the domain separator for the current chain.
*/
function _domainSeparatorV4() internal view returns (bytes32) {
return _buildDomainSeparator();
}
function _buildDomainSeparator() private view returns (bytes32) {
return keccak256(abi.encode(TYPE_HASH, _EIP712NameHash(), _EIP712VersionHash(), block.chainid, address(this)));
}
/**
* @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this
* function returns the hash of the fully encoded EIP712 message for this domain.
*
* This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example:
*
* ```solidity
* bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(
* keccak256("Mail(address to,string contents)"),
* mailTo,
* keccak256(bytes(mailContents))
* )));
* address signer = ECDSA.recover(digest, signature);
* ```
*/
function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) {
return MessageHashUtils.toTypedDataHash(_domainSeparatorV4(), structHash);
}
/**
* @dev See {IERC-5267}.
*/
function eip712Domain()
public
view
virtual
returns (
bytes1 fields,
string memory name,
string memory version,
uint256 chainId,
address verifyingContract,
bytes32 salt,
uint256[] memory extensions
)
{
EIP712Storage storage $ = _getEIP712Storage();
// If the hashed name and version in storage are non-zero, the contract hasn't been properly initialized
// and the EIP712 domain is not reliable, as it will be missing name and version.
require($._hashedName == 0 && $._hashedVersion == 0, "EIP712: Uninitialized");
return (
hex"0f", // 01111
_EIP712Name(),
_EIP712Version(),
block.chainid,
address(this),
bytes32(0),
new uint256[](0)
);
}
/**
* @dev The name parameter for the EIP712 domain.
*
* NOTE: This function reads from storage by default, but can be redefined to return a constant value if gas costs
* are a concern.
*/
function _EIP712Name() internal view virtual returns (string memory) {
EIP712Storage storage $ = _getEIP712Storage();
return $._name;
}
/**
* @dev The version parameter for the EIP712 domain.
*
* NOTE: This function reads from storage by default, but can be redefined to return a constant value if gas costs
* are a concern.
*/
function _EIP712Version() internal view virtual returns (string memory) {
EIP712Storage storage $ = _getEIP712Storage();
return $._version;
}
/**
* @dev The hash of the name parameter for the EIP712 domain.
*
* NOTE: In previous versions this function was virtual. In this version you should override `_EIP712Name` instead.
*/
function _EIP712NameHash() internal view returns (bytes32) {
EIP712Storage storage $ = _getEIP712Storage();
string memory name = _EIP712Name();
if (bytes(name).length > 0) {
return keccak256(bytes(name));
} else {
// If the name is empty, the contract may have been upgraded without initializing the new storage.
// We return the name hash in storage if non-zero, otherwise we assume the name is empty by design.
bytes32 hashedName = $._hashedName;
if (hashedName != 0) {
return hashedName;
} else {
return keccak256("");
}
}
}
/**
* @dev The hash of the version parameter for the EIP712 domain.
*
* NOTE: In previous versions this function was virtual. In this version you should override `_EIP712Version` instead.
*/
function _EIP712VersionHash() internal view returns (bytes32) {
EIP712Storage storage $ = _getEIP712Storage();
string memory version = _EIP712Version();
if (bytes(version).length > 0) {
return keccak256(bytes(version));
} else {
// If the version is empty, the contract may have been upgraded without initializing the new storage.
// We return the version hash in storage if non-zero, otherwise we assume the version is empty by design.
bytes32 hashedVersion = $._hashedVersion;
if (hashedVersion != 0) {
return hashedVersion;
} else {
return keccak256("");
}
}
}
}
"
},
"lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Permit.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Permit.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*
* ==== Security Considerations
*
* There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
* expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
* considered as an intention to spend the allowance in any specific way. The second is that because permits have
* built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
* take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
* generally recommended is:
*
* ```solidity
* function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
* try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
* doThing(..., value);
* }
*
* function doThing(..., uint256 value) public {
* token.safeTransferFrom(msg.sender, address(this), value);
* ...
* }
* ```
*
* Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
* `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
* {SafeERC20-safeTransferFrom}).
*
* Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
* contracts should have entry points that don't rely on permit.
*/
interface IERC20Permit {
/**
* @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
* given ``owner``'s signed approval.
*
* IMPORTANT: The same issues {IERC20-approve} has related to transaction
* ordering also apply here.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `deadline` must be a timestamp in the future.
* - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
* over the EIP712-formatted function arguments.
* - the signature must use ``owner``'s current nonce (see {nonces}).
*
* For more information on the signature format, see the
* https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
* section].
*
* CAUTION: See Security Considerations above.
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
/**
* @dev Returns the current nonce for `owner`. This value must be
* included whenever a signature is generated for {permit}.
*
* Every successful call to {permit} increases ``owner``'s nonce by one. This
* prevents a signature from being used multiple times.
*/
function nonces(address owner) external view returns (uint256);
/**
* @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
*/
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view returns (bytes32);
}
"
},
"lib/openzeppelin-contracts/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);
}
"
},
"lib/openzeppelin-contracts/contracts/utils/cryptography/SignatureChecker.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/SignatureChecker.sol)
pragma solidity ^0.8.20;
import {ECDSA} from "./ECDSA.sol";
import {IERC1271} from "../../interfaces/IERC1271.sol";
/**
* @dev Signature verification helper that can be used instead of `ECDSA.recover` to seamlessly support both ECDSA
* signatures from externally owned accounts (EOAs) as well as ERC1271 signatures from smart contract wallets like
* Argent and Safe Wallet (previously Gnosis Safe).
*/
library SignatureChecker {
/**
* @dev Checks if a signature is valid for a given signer and data hash. If the signer is a smart contract, the
* signature is validated against that smart contract using ERC1271, otherwise it's validated using `ECDSA.recover`.
*
* NOTE: Unlike ECDSA signatures, contract signatures are revocable, and the outcome of this function can thus
* change through time. It could return true at block N and false at block N+1 (or the opposite).
*/
function isValidSignatureNow(address signer, bytes32 hash, bytes memory signature) internal view returns (bool) {
(address recovered, ECDSA.RecoverError error, ) = ECDSA.tryRecover(hash, signature);
return
(error == ECDSA.RecoverError.NoError && recovered == signer) ||
isValidERC1271SignatureNow(signer, hash, signature);
}
/**
* @dev Checks if a signature is valid for a given signer and data hash. The signature is validated
* against the signer smart contract using ERC1271.
*
* NOTE: Unlike ECDSA signatures, contract signatures are revocable, and the outcome of this function can thus
* change through time. It could return true at block N and false at block N+1 (or the opposite).
*/
function isValidERC1271SignatureNow(
address signer,
bytes32 hash,
bytes memory signature
) internal view returns (bool) {
(bool success, bytes memory result) = signer.staticcall(
abi.encodeCall(IERC1271.isValidSignature, (hash, signature))
);
return (success &&
result.length >= 32 &&
abi.decode(result, (bytes32)) == bytes32(IERC1271.isValidSignature.selector));
}
}
"
},
"lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Metadata.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
/**
* @dev Interface for the optional metadata functions from the ERC20 standard.
*/
interface IERC20Metadata is IERC20 {
/**
* @dev Returns the name of the token.
*/
function name() external view returns (string memory);
/**
* @dev Returns the symbol of the token.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the decimals places of the token.
*/
function decimals() external view returns (uint8);
}
"
},
"lib/openzeppelin-contracts/contracts/utils/math/Math.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/Math.sol)
pragma solidity ^0.8.20;
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library Math {
/**
* @dev Muldiv operation overflow.
*/
error MathOverflowedMulDiv();
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 overflow flag.
*/
function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
uint256 c = a + b;
if (c < a) return (false, 0);
return (true, c);
}
}
/**
* @dev Returns the subtraction of two unsigned integers, with an overflow flag.
*/
function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b > a) return (false, 0);
return (true, a - b);
}
}
/**
* @dev Returns the multiplication of two unsigned integers, with an overflow flag.
*/
function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
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 division by zero flag.
*/
function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b == 0) return (false, 0);
return (true, a / b);
}
}
/**
* @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
*/
function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b == 0) return (false, 0);
return (true, a % b);
}
}
/**
* @dev Returns the largest of two numbers.
*/
function max(uint256 a, uint256 b) internal pure retur
Submitted on: 2025-10-03 11:33:36
Comments
Log in to comment.
No comments yet.