Source Code
Advanced mode: Intended for advanced users or developers and will display all Internal Transactions including zero value transfers.
Latest 25 internal transactions (View All)
Advanced mode:
| Parent Transaction Hash | Block | From | To | ||||
|---|---|---|---|---|---|---|---|
| 28119787 | 75 days ago | 0 FRAX | |||||
| 28119787 | 75 days ago | 0 FRAX | |||||
| 28119787 | 75 days ago | 0 FRAX | |||||
| 28119787 | 75 days ago | 0 FRAX | |||||
| 28119787 | 75 days ago | 0 FRAX | |||||
| 28119787 | 75 days ago | 0 FRAX | |||||
| 28119787 | 75 days ago | 0 FRAX | |||||
| 28119787 | 75 days ago | 0 FRAX | |||||
| 28119787 | 75 days ago | 0 FRAX | |||||
| 28119787 | 75 days ago | 0 FRAX | |||||
| 28119787 | 75 days ago | 0 FRAX | |||||
| 28119787 | 75 days ago | 0 FRAX | |||||
| 28119787 | 75 days ago | 0 FRAX | |||||
| 28119787 | 75 days ago | 0 FRAX | |||||
| 28119787 | 75 days ago | 0 FRAX | |||||
| 28119787 | 75 days ago | 0 FRAX | |||||
| 28119787 | 75 days ago | 0 FRAX | |||||
| 28119787 | 75 days ago | 0 FRAX | |||||
| 28119787 | 75 days ago | 0 FRAX | |||||
| 28119787 | 75 days ago | 0 FRAX | |||||
| 28119787 | 75 days ago | 0 FRAX | |||||
| 28119787 | 75 days ago | 0 FRAX | |||||
| 28119787 | 75 days ago | 0 FRAX | |||||
| 28119787 | 75 days ago | 0 FRAX | |||||
| 28119787 | 75 days ago | 0 FRAX |
Cross-Chain Transactions
Loading...
Loading
Contract Name:
FraxtalArbitrageHelper
Compiler Version
v0.8.20+commit.a1b79de6
Optimization Enabled:
Yes with 200 runs
Other Settings:
shanghai EvmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts-5/access/AccessControl.sol";
import "@openzeppelin/contracts/interfaces/IERC3156FlashBorrower.sol";
import "@openzeppelin/contracts-5/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts-5/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts-5/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts-5/utils/Pausable.sol";
import "src/test/ERC20StablecoinUpgradeable.sol";
import "src/dusd/IssuerV2.sol";
import "src/dusd/RedeemerV2.sol";
// Fraxtal Multihop Router Interface (based on the FraxArb contract)
interface IFraxswapRouterMultihop {
struct FraxswapParams {
address tokenIn;
uint256 amountIn;
address tokenOut;
uint256 amountOutMinimum;
address recipient;
uint256 deadline;
bool approveMax;
uint8 v;
bytes32 r;
bytes32 s;
bytes route;
}
function swap(FraxswapParams memory params) external returns (uint256 amountOut);
function encodeRoute(
address tokenOut,
uint256 percentOfHop,
bytes[] memory steps,
bytes[] memory nextHops
) external pure returns (bytes memory out);
function encodeStep(
uint8 swapType,
uint8 directFundNextPool,
uint8 directFundThisPool,
address tokenOut,
address pool,
uint256 extraParam1,
uint256 extraParam2,
uint256 percentOfHop
) external pure returns (bytes memory out);
}
/**
* @title FraxtalArbitrageHelper
* @dev Advanced Arbitrage Helper using Fraxtal Multihop Router with dUSD Integration
*
* This contract facilitates sophisticated arbitrage operations with:
* 1. Flash minting dUSD tokens for capital efficiency
* 2. Fraxtal multihop routing through multiple DEXs (Curve, ERC4626, etc.)
* 3. dUSD issuance and redemption arbitrage strategies
* 4. Collateral-based circular arbitrage (Issue → Swap → Redeem)
* 5. Advanced arbitrage strategies with packed parameters
* 6. Gas optimizations and automatic profit distribution
*
* Arbitrage Strategies:
* - Flash Loan + DEX: Flash mint dUSD → Swap → Profit
* - Issue + DEX: Collateral → Issue dUSD → Swap → Redeem → More Collateral
* - Redeem + DEX: Flash loan dUSD → Redeem → Swap collateral → Issue → Profit
* - Circular: Issue → Multi-hop swap → Redeem → Net profit in collateral
*/
contract FraxtalArbitrageHelper is AccessControl, ReentrancyGuard, Pausable {
using SafeERC20 for IERC20;
/* Events */
event ArbitrageExecuted(
address indexed tokenIn,
address indexed tokenOut,
uint256 amountIn,
uint256 amountOut,
uint256 profit,
string strategy
);
event FlashLoanArbitrageExecuted(
address indexed tokenIn,
address indexed tokenOut,
uint256 flashLoanAmount,
uint256 amountIn,
uint256 amountOut,
uint256 profit,
string strategy
);
event IssueRedeemArbitrageExecuted(
address indexed collateralAsset,
uint256 collateralIn,
uint256 dusdIssued,
uint256 dusdRedeemed,
uint256 collateralOut,
uint256 profit,
string strategy
);
event IssuerUpdated(address indexed oldIssuer, address indexed newIssuer);
event RedeemerUpdated(address indexed oldRedeemer, address indexed newRedeemer);
/* Errors */
error ZeroAddress();
error InsufficientDStableReceived(uint256 expected, uint256 actual);
error FlashLoanRepaymentFailed();
error UnauthorizedFlashLoan();
error InvalidFlashLoanInitiator();
error SlippageTooHigh(uint256 requestedSlippage, uint256 maxSlippage);
error InsufficientCollateralReceived(uint256 expected, uint256 actual);
error FlashLoanAmountExceedsMaximum(uint256 requested, uint256 maximum);
error InvalidPathLength();
error DeadlineExceeded();
error ZeroDStableAmount();
error NoSwapPathProvided();
error InvalidSwapPathTokens();
error InvalidFeeTier(uint24 feeTier);
error InvalidIntermediateToken(address token);
error AssetRedemptionPaused(address asset);
error UniswapExactInputFailed(bytes reason);
error FraxswapSwapFailed(bytes reason);
error InvalidArbitrageParameters();
error InsufficientProfit(uint256 expected, uint256 actual);
error InsufficientDusdIssued(uint256 expected, uint256 actual);
error InsufficientCollateralRedeemed(uint256 expected, uint256 actual);
error ZeroCollateralAmount();
error InvalidCollateralAsset();
/* Roles */
bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");
bytes32 public constant ARBITRAGE_ROLE = keccak256("ARBITRAGE_ROLE");
/* Constants */
uint256 public constant HUNDRED_PERCENT_BPS = 10_000;
uint256 public constant MAX_SLIPPAGE_BPS = 2_000; // 20% maximum slippage
/* State Variables */
ERC20StablecoinUpgradeable public immutable dstable;
IFraxswapRouterMultihop public immutable fraxswapRouter;
IssuerV2 public issuer;
RedeemerV2 public redeemer;
/* Structs */
struct ArbitrageParams {
address tokenIn;
address tokenOut;
uint256 amountIn;
uint256 minAmountOut;
uint256 deadline;
uint256 slippageBps;
address profitTo;
// Fraxtal multihop specific parameters
bytes fraxswapRoute;
uint8 v;
bytes32 r;
bytes32 s;
bool approveMax;
}
struct FlashLoanArbitrageParams {
address tokenIn;
address tokenOut;
uint256 flashLoanAmount;
uint256 minAmountOut;
uint256 deadline;
uint256 slippageBps;
address profitTo;
// Fraxtal multihop route
bytes fraxswapRoute;
uint8 v;
bytes32 r;
bytes32 s;
bool approveMax;
// Optional fields for ISSUE operation when route ends in collateral
address collateralAsset; // The collateral asset if route ends in collateral (zero address if route ends in dUSD)
uint256 minDusdFromIssue; // Minimum dUSD to receive from ISSUE operation (for slippage protection)
}
struct PackedArbitrageParams {
bytes packedData; // Compressed parameters for gas optimization
address tokenIn;
address tokenOut;
uint256 amountIn;
uint256 minAmountOut;
uint256 deadline;
}
struct IssueSwapRedeemParams {
address collateralAsset; // Collateral to deposit for dUSD issuance
uint256 collateralAmount; // Amount of collateral to deposit
uint256 minDusdIssued; // Minimum dUSD to receive from issue
uint256 minCollateralRedeemed; // Minimum collateral to receive from redemption
uint256 deadline;
uint256 slippageBps;
address profitTo;
bytes fraxswapRoute; // Route for swapping through DEXs
uint8 v;
bytes32 r;
bytes32 s;
bool approveMax;
}
struct FlashLoanRedeemSwapIssueParams {
address collateralAsset; // Collateral asset to use
uint256 flashLoanAmount; // Amount of dUSD to flash loan
uint256 minCollateralFromRedeem; // Min collateral from redeeming dUSD
uint256 minDusdFromIssue; // Min dUSD from issuing with swapped collateral
uint256 deadline;
uint256 slippageBps;
address profitTo;
bytes fraxswapRoute; // Route for swapping collateral
uint8 v;
bytes32 r;
bytes32 s;
bool approveMax;
}
struct FlashLoanSwapIssueParams {
address collateralAsset; // Target collateral asset produced by swap
uint256 flashLoanAmount; // Amount of dUSD to flash loan
uint256 minCollateralFromSwap; // Min collateral after Fraxswap route
uint256 minDusdFromIssue; // Min dUSD from issuing with swapped collateral
uint256 deadline;
uint256 slippageBps;
address profitTo;
bytes fraxswapRoute; // Route for swapping dUSD -> collateral
uint8 v;
bytes32 r;
bytes32 s;
bool approveMax;
}
constructor(
address _dstable,
address _fraxswapRouter,
address _issuer,
address _redeemer,
address _operator
) {
if (
_dstable == address(0) ||
_fraxswapRouter == address(0) ||
_issuer == address(0) ||
_redeemer == address(0) ||
_operator == address(0)
) {
revert ZeroAddress();
}
dstable = ERC20StablecoinUpgradeable(_dstable);
fraxswapRouter = IFraxswapRouterMultihop(_fraxswapRouter);
issuer = IssuerV2(_issuer);
redeemer = RedeemerV2(_redeemer);
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
_grantRole(OPERATOR_ROLE, _operator);
_grantRole(ARBITRAGE_ROLE, _operator);
}
/**
* @notice Executes flash loan arbitrage using dSTABLE flash minting
* @param params Flash loan arbitrage parameters including routing options
*/
function executeFlashLoanArbitrage(
FlashLoanArbitrageParams calldata params
) external onlyRole(OPERATOR_ROLE) whenNotPaused nonReentrant {
_validateFlashLoanArbitrageParams(params);
// Check if flash loan is supported
uint256 maxFlashLoan = dstable.maxFlashLoan(address(dstable));
if (params.flashLoanAmount > maxFlashLoan) {
revert FlashLoanAmountExceedsMaximum(params.flashLoanAmount, maxFlashLoan);
}
// Encode parameters for flash loan callback
bytes memory data = abi.encode(params);
// Execute flash loan with arbitrage callback
dstable.flashLoan(
IERC3156FlashBorrowerUpgradeable(address(this)),
address(dstable),
params.flashLoanAmount,
data
);
}
/**
* @notice Executes arbitrage using Fraxswap multihop routing
* @param params Arbitrage parameters including routing strategy
*/
function executeArbitrage(
ArbitrageParams calldata params
) external onlyRole(ARBITRAGE_ROLE) whenNotPaused nonReentrant {
_validateArbitrageParams(params);
uint256 balanceBefore = IERC20(params.tokenOut).balanceOf(address(this));
// Approve input token to Fraxtal router
IERC20(params.tokenIn).forceApprove(address(fraxswapRouter), 0);
IERC20(params.tokenIn).forceApprove(address(fraxswapRouter), params.amountIn);
// Execute Fraxtal multihop arbitrage
_executeFraxswapArbitrage(params);
uint256 balanceAfter = IERC20(params.tokenOut).balanceOf(address(this));
uint256 amountOut = balanceAfter - balanceBefore;
if (amountOut < params.minAmountOut) {
revert InsufficientDStableReceived(params.minAmountOut, amountOut);
}
// Calculate and distribute profit
uint256 profit = _calculateAndDistributeProfit(
params.tokenIn,
params.tokenOut,
params.amountIn,
amountOut,
params.profitTo
);
emit ArbitrageExecuted(
params.tokenIn,
params.tokenOut,
params.amountIn,
amountOut,
profit,
"Fraxtal-Multihop"
);
}
/**
* @notice Executes arbitrage using packed parameters for gas optimization
* @param params Packed arbitrage parameters
*/
function executePackedArbitrage(
PackedArbitrageParams calldata params
) external onlyRole(ARBITRAGE_ROLE) whenNotPaused nonReentrant {
// Unpack the compressed parameters
ArbitrageParams memory unpackedParams = _unpackArbitrageParams(params);
// Execute the arbitrage
_executeArbitrageInternal(unpackedParams);
}
/**
* @notice Executes circular arbitrage: Issue dUSD → Swap → Redeem → Profit
* @dev Strategy: Deposit collateral → Issue dUSD → Swap through DEXs → Redeem back to collateral
* @param params Issue-Swap-Redeem arbitrage parameters
*
* Flow:
* 1. Transfer collateral from caller
* 2. Issue dUSD using collateral via Issuer
* 3. Swap dUSD through Fraxswap multihop routes
* 4. Redeem final dUSD back to collateral via Redeemer
* 5. Return profit in collateral to profitTo address
*/
function executeIssueSwapRedeem(
IssueSwapRedeemParams calldata params
) external onlyRole(ARBITRAGE_ROLE) whenNotPaused nonReentrant {
_validateIssueSwapRedeemParams(params);
uint256 collateralBalanceBefore = IERC20(params.collateralAsset).balanceOf(address(this));
// Step 1: Transfer collateral from caller
IERC20(params.collateralAsset).safeTransferFrom(
msg.sender,
address(this),
params.collateralAmount
);
// Step 2: Approve Issuer to spend collateral
IERC20(params.collateralAsset).forceApprove(address(issuer), 0);
IERC20(params.collateralAsset).forceApprove(address(issuer), params.collateralAmount);
// Step 3: Issue dUSD
uint256 dusdBalanceBefore = dstable.balanceOf(address(this));
issuer.issue(params.collateralAmount, params.collateralAsset, params.minDusdIssued);
uint256 dusdIssued = dstable.balanceOf(address(this)) - dusdBalanceBefore;
if (dusdIssued < params.minDusdIssued) {
revert InsufficientDusdIssued(params.minDusdIssued, dusdIssued);
}
// Step 4: Swap dUSD through DEX routes (if route provided)
uint256 dusdAfterSwap = dusdIssued;
if (params.fraxswapRoute.length > 0) {
// Approve Fraxswap router
IERC20(address(dstable)).forceApprove(address(fraxswapRouter), 0);
IERC20(address(dstable)).forceApprove(address(fraxswapRouter), dusdIssued);
// Execute swap
IFraxswapRouterMultihop.FraxswapParams memory fraxParams = IFraxswapRouterMultihop.FraxswapParams({
tokenIn: address(dstable),
amountIn: dusdIssued,
tokenOut: address(dstable),
amountOutMinimum: _calculateAmountLimit(dusdIssued, params.slippageBps),
recipient: address(this),
deadline: params.deadline,
approveMax: params.approveMax,
v: params.v,
r: params.r,
s: params.s,
route: params.fraxswapRoute
});
try fraxswapRouter.swap(fraxParams) returns (uint256 amountOut) {
dusdAfterSwap = amountOut;
} catch (bytes memory reason) {
revert FraxswapSwapFailed(reason);
}
}
// Step 5: Approve Redeemer to spend dUSD
IERC20(address(dstable)).forceApprove(address(redeemer), 0);
IERC20(address(dstable)).forceApprove(address(redeemer), dusdAfterSwap);
// Step 6: Redeem dUSD back to collateral (using public redeem - fees apply!)
// Note: RedeemerV2.redeem() deducts fees, so minCollateralRedeemed is the NET amount
redeemer.redeem(dusdAfterSwap, params.collateralAsset, params.minCollateralRedeemed);
uint256 collateralBalanceAfter = IERC20(params.collateralAsset).balanceOf(address(this));
uint256 collateralOut = collateralBalanceAfter - collateralBalanceBefore;
if (collateralOut < params.minCollateralRedeemed) {
revert InsufficientCollateralRedeemed(params.minCollateralRedeemed, collateralOut);
}
// Step 7: Calculate and distribute profit
uint256 profit = collateralOut > params.collateralAmount ?
collateralOut - params.collateralAmount : 0;
if (profit > 0) {
IERC20(params.collateralAsset).safeTransfer(params.profitTo, profit);
}
// Return original collateral to caller if no profit was made
if (collateralOut >= params.collateralAmount) {
IERC20(params.collateralAsset).safeTransfer(msg.sender, params.collateralAmount);
}
emit IssueRedeemArbitrageExecuted(
params.collateralAsset,
params.collateralAmount,
dusdIssued,
dusdAfterSwap,
collateralOut,
profit,
"Issue-Swap-Redeem"
);
}
/**
* @notice Executes flash loan arbitrage with redeem and issue
* @dev Strategy: Flash loan dUSD → Redeem to collateral → Swap → Issue dUSD → Repay + Profit
* @param params Flash loan redeem-swap-issue parameters
*
* Flow:
* 1. Flash loan dUSD
* 2. Redeem dUSD for collateral
* 3. Swap collateral through DEXs for more collateral
* 4. Issue dUSD with swapped collateral
* 5. Repay flash loan + fee
* 6. Keep profit in dUSD
*/
function executeFlashLoanRedeemSwapIssue(
FlashLoanRedeemSwapIssueParams calldata params
) external onlyRole(OPERATOR_ROLE) whenNotPaused nonReentrant {
_validateFlashLoanRedeemSwapIssueParams(params);
// Check if flash loan is supported
uint256 maxFlashLoan = dstable.maxFlashLoan(address(dstable));
if (params.flashLoanAmount > maxFlashLoan) {
revert FlashLoanAmountExceedsMaximum(params.flashLoanAmount, maxFlashLoan);
}
// Encode parameters for flash loan callback
bytes memory data = abi.encode(params);
// Execute flash loan with arbitrage callback
dstable.flashLoan(
IERC3156FlashBorrowerUpgradeable(address(this)),
address(dstable),
params.flashLoanAmount,
data
);
}
/**
* @notice Executes flash loan arbitrage with swap then issue
* @dev Strategy: Flash loan dUSD → Swap to collateral → Issue dUSD → Repay + Profit
*/
function executeFlashLoanSwapIssue(
FlashLoanSwapIssueParams calldata params
) external onlyRole(OPERATOR_ROLE) whenNotPaused nonReentrant {
_validateFlashLoanSwapIssueParams(params);
uint256 maxFlashLoan = dstable.maxFlashLoan(address(dstable));
if (params.flashLoanAmount > maxFlashLoan) {
revert FlashLoanAmountExceedsMaximum(params.flashLoanAmount, maxFlashLoan);
}
bytes memory data = abi.encode(params);
dstable.flashLoan(
IERC3156FlashBorrowerUpgradeable(address(this)),
address(dstable),
params.flashLoanAmount,
data
);
}
/**
* @notice Flash loan callback function for arbitrage operations
*/
function onFlashLoan(
address initiator,
address /* token */,
uint256 amount,
uint256 fee,
bytes calldata data
) external returns (bytes32) {
// Validate flash loan
if (msg.sender != address(dstable)) {
revert UnauthorizedFlashLoan();
}
if (initiator != address(this)) {
revert InvalidFlashLoanInitiator();
}
// Try strategy: Swap -> Issue
try this._executeFlashLoanSwapIssue(amount, fee, data) {
// Executed swap-issue strategy
} catch
{
// Try strategy: Redeem -> Swap -> Issue
try this._executeFlashLoanRedeemSwapIssue(amount, fee, data) {
// Executed redeem-swap-issue strategy
} catch {
// Fall back to standard multihop arbitrage (swap only or swap + ISSUE)
FlashLoanArbitrageParams memory params = abi.decode(data, (FlashLoanArbitrageParams));
// Validate deadline
if (block.timestamp > params.deadline) {
revert DeadlineExceeded();
}
// Execute arbitrage using the flash loaned dSTABLE
uint256 amountOut = _executeArbitrageWithAmount(amount, params);
// Validate output amount
if (amountOut < params.minAmountOut) {
revert InsufficientDStableReceived(params.minAmountOut, amountOut);
}
uint256 dusdForRepayment = amountOut;
// If route ends in collateral, ISSUE dUSD from collateral
if (params.collateralAsset != address(0) && params.tokenOut != address(dstable)) {
// Route ended in collateral, need to ISSUE dUSD
uint256 collateralAmount = amountOut;
dusdForRepayment = _issueDusdFromCollateral(
params.collateralAsset,
collateralAmount,
params.minDusdFromIssue
);
}
// Repay flash loan
if (dusdForRepayment < amount + fee) {
revert FlashLoanRepaymentFailed();
}
IERC20(address(dstable)).forceApprove(address(dstable), 0);
IERC20(address(dstable)).forceApprove(address(dstable), amount + fee);
// Calculate and distribute profit
uint256 profit = dusdForRepayment - amount - fee;
if (profit > 0) {
IERC20(address(dstable)).safeTransfer(params.profitTo, profit);
}
// Clean up approvals
IERC20(params.tokenIn).forceApprove(address(fraxswapRouter), 0);
if (params.collateralAsset != address(0)) {
IERC20(params.collateralAsset).forceApprove(address(issuer), 0);
}
emit FlashLoanArbitrageExecuted(
params.tokenIn,
params.tokenOut,
amount,
amount,
dusdForRepayment,
profit,
params.collateralAsset != address(0) ? "Fraxtal-FlashLoan-Issue" : "Fraxtal-FlashLoan"
);
}
}
return keccak256("ERC3156FlashBorrower.onFlashLoan");
}
/**
* @notice Internal function to execute flash loan redeem-swap-issue strategy
* @dev Called from onFlashLoan callback
*/
function _executeFlashLoanRedeemSwapIssue(
uint256 amount,
uint256 fee,
bytes calldata data
) external {
// Only callable by this contract
require(msg.sender == address(this), "Internal only");
// Decode parameters
FlashLoanRedeemSwapIssueParams memory params = abi.decode(data, (FlashLoanRedeemSwapIssueParams));
// Validate deadline
if (block.timestamp > params.deadline) {
revert DeadlineExceeded();
}
// Step 1: Approve Redeemer to spend flash loaned dUSD
IERC20(address(dstable)).forceApprove(address(redeemer), 0);
IERC20(address(dstable)).forceApprove(address(redeemer), amount);
// Step 2: Redeem dUSD for collateral (using public redeem - fees apply!)
// Note: RedeemerV2.redeem() deducts fees, so minCollateralFromRedeem is the NET amount
uint256 collateralBalanceBefore = IERC20(params.collateralAsset).balanceOf(address(this));
redeemer.redeem(amount, params.collateralAsset, params.minCollateralFromRedeem);
uint256 collateralFromRedeem = IERC20(params.collateralAsset).balanceOf(address(this)) - collateralBalanceBefore;
if (collateralFromRedeem < params.minCollateralFromRedeem) {
revert InsufficientCollateralRedeemed(params.minCollateralFromRedeem, collateralFromRedeem);
}
// Step 3: Swap collateral through DEX routes (if route provided)
uint256 collateralAfterSwap = collateralFromRedeem;
if (params.fraxswapRoute.length > 0) {
// Approve Fraxswap router
IERC20(params.collateralAsset).forceApprove(address(fraxswapRouter), 0);
IERC20(params.collateralAsset).forceApprove(address(fraxswapRouter), collateralFromRedeem);
// Execute swap (collateral to collateral or different token)
IFraxswapRouterMultihop.FraxswapParams memory fraxParams = IFraxswapRouterMultihop.FraxswapParams({
tokenIn: params.collateralAsset,
amountIn: collateralFromRedeem,
tokenOut: params.collateralAsset, // Same asset for simplicity
amountOutMinimum: _calculateAmountLimit(collateralFromRedeem, params.slippageBps),
recipient: address(this),
deadline: params.deadline,
approveMax: params.approveMax,
v: params.v,
r: params.r,
s: params.s,
route: params.fraxswapRoute
});
try fraxswapRouter.swap(fraxParams) returns (uint256 amountOut) {
collateralAfterSwap = amountOut;
} catch (bytes memory reason) {
revert FraxswapSwapFailed(reason);
}
}
// Step 4: Approve Issuer to spend collateral
IERC20(params.collateralAsset).forceApprove(address(issuer), 0);
IERC20(params.collateralAsset).forceApprove(address(issuer), collateralAfterSwap);
// Step 5: Issue dUSD with swapped collateral
uint256 dusdBalanceBefore = dstable.balanceOf(address(this));
issuer.issue(collateralAfterSwap, params.collateralAsset, params.minDusdFromIssue);
uint256 dusdFromIssue = dstable.balanceOf(address(this)) - dusdBalanceBefore;
if (dusdFromIssue < params.minDusdFromIssue) {
revert InsufficientDusdIssued(params.minDusdFromIssue, dusdFromIssue);
}
// Step 6: Repay flash loan
uint256 repayAmount = amount + fee;
if (dusdFromIssue < repayAmount) {
revert FlashLoanRepaymentFailed();
}
IERC20(address(dstable)).forceApprove(address(dstable), 0);
IERC20(address(dstable)).forceApprove(address(dstable), repayAmount);
// Step 7: Calculate and distribute profit
uint256 profit = dusdFromIssue - repayAmount;
if (profit > 0) {
IERC20(address(dstable)).safeTransfer(params.profitTo, profit);
}
emit IssueRedeemArbitrageExecuted(
params.collateralAsset,
amount,
dusdFromIssue,
amount,
collateralAfterSwap,
profit,
"FlashLoan-Redeem-Swap-Issue"
);
}
/**
* @notice Internal: Flashloan dUSD -> Fraxswap to collateral -> Issue dUSD -> Repay
*/
function _executeFlashLoanSwapIssue(
uint256 amount,
uint256 fee,
bytes calldata data
) external {
require(msg.sender == address(this), "Internal only");
FlashLoanSwapIssueParams memory params = abi.decode(data, (FlashLoanSwapIssueParams));
if (block.timestamp > params.deadline) {
revert DeadlineExceeded();
}
// Step 1: Approve Fraxswap router to spend flashloaned dUSD
IERC20(address(dstable)).forceApprove(address(fraxswapRouter), 0);
IERC20(address(dstable)).forceApprove(address(fraxswapRouter), amount);
// Step 2: Execute swap dUSD -> collateral via Fraxswap route
uint256 collateralBalanceBefore = IERC20(params.collateralAsset).balanceOf(address(this));
IFraxswapRouterMultihop.FraxswapParams memory fraxParams = IFraxswapRouterMultihop.FraxswapParams({
tokenIn: address(dstable),
amountIn: amount,
tokenOut: params.collateralAsset,
amountOutMinimum: params.minCollateralFromSwap,
recipient: address(this),
deadline: params.deadline,
approveMax: params.approveMax,
v: params.v,
r: params.r,
s: params.s,
route: params.fraxswapRoute
});
uint256 collateralAfterSwap;
try fraxswapRouter.swap(fraxParams) returns (uint256 amountOut) {
collateralAfterSwap = amountOut;
} catch (bytes memory reason) {
revert FraxswapSwapFailed(reason);
}
// Sanity check
if (collateralAfterSwap < params.minCollateralFromSwap) {
revert InsufficientCollateralReceived(params.minCollateralFromSwap, collateralAfterSwap);
}
// Step 3: Approve Issuer and issue dUSD with swapped collateral
IERC20(params.collateralAsset).forceApprove(address(issuer), 0);
IERC20(params.collateralAsset).forceApprove(address(issuer), collateralAfterSwap);
uint256 dusdBalanceBefore = dstable.balanceOf(address(this));
issuer.issue(collateralAfterSwap, params.collateralAsset, params.minDusdFromIssue);
uint256 dusdFromIssue = dstable.balanceOf(address(this)) - dusdBalanceBefore;
if (dusdFromIssue < params.minDusdFromIssue) {
revert InsufficientDusdIssued(params.minDusdFromIssue, dusdFromIssue);
}
// Step 4: Repay flash loan
uint256 repayAmount = amount + fee;
if (dusdFromIssue < repayAmount) {
revert FlashLoanRepaymentFailed();
}
IERC20(address(dstable)).forceApprove(address(dstable), 0);
IERC20(address(dstable)).forceApprove(address(dstable), repayAmount);
// Step 5: Distribute profit
uint256 profit = dusdFromIssue - repayAmount;
if (profit > 0) {
IERC20(address(dstable)).safeTransfer(params.profitTo, profit);
}
emit IssueRedeemArbitrageExecuted(
params.collateralAsset,
amount,
dusdFromIssue,
amount,
collateralAfterSwap,
profit,
"FlashLoan-Swap-Issue"
);
}
/**
* @notice Pauses the contract
*/
function pause() external onlyRole(DEFAULT_ADMIN_ROLE) {
_pause();
}
/**
* @notice Unpauses the contract
*/
function unpause() external onlyRole(DEFAULT_ADMIN_ROLE) {
_unpause();
}
/**
* @notice Rescues stuck ETH
*/
function rescueETH(
address to,
uint256 amount
) external onlyRole(DEFAULT_ADMIN_ROLE) {
if (to == address(0)) {
revert ZeroAddress();
}
(bool ok, ) = payable(to).call{value: amount}("");
require(ok, "ETH_TRANSFER_FAILED");
}
/**
* @notice Rescues stuck tokens
*/
function rescueTokens(
address token,
address to,
uint256 amount
) external onlyRole(DEFAULT_ADMIN_ROLE) {
if (to == address(0)) {
revert ZeroAddress();
}
IERC20(token).safeTransfer(to, amount);
}
/**
* @notice Updates the IssuerV2 contract address
* @param _issuer New IssuerV2 contract address
*/
function setIssuer(address _issuer) external onlyRole(DEFAULT_ADMIN_ROLE) {
if (_issuer == address(0)) {
revert ZeroAddress();
}
address oldIssuer = address(issuer);
issuer = IssuerV2(_issuer);
emit IssuerUpdated(oldIssuer, _issuer);
}
/**
* @notice Updates the RedeemerV2 contract address
* @param _redeemer New RedeemerV2 contract address
*/
function setRedeemer(address _redeemer) external onlyRole(DEFAULT_ADMIN_ROLE) {
if (_redeemer == address(0)) {
revert ZeroAddress();
}
address oldRedeemer = address(redeemer);
redeemer = RedeemerV2(_redeemer);
emit RedeemerUpdated(oldRedeemer, _redeemer);
}
/**
* @notice Returns the maximum allowed slippage in basis points
*/
function getMaxSlippageBps() external pure returns (uint256) {
return MAX_SLIPPAGE_BPS;
}
/**
* @notice Checks if the contract supports the IERC3156FlashBorrower interface
*/
function supportsInterface(
bytes4 interfaceId
) public view virtual override returns (bool) {
return
interfaceId == type(IERC3156FlashBorrowerUpgradeable).interfaceId ||
super.supportsInterface(interfaceId);
}
// Allow contract to receive ETH
receive() external payable {}
/* Internal Functions */
function _validateFlashLoanArbitrageParams(
FlashLoanArbitrageParams calldata params
) internal view {
if (block.timestamp > params.deadline) {
revert DeadlineExceeded();
}
if (params.flashLoanAmount == 0) {
revert ZeroDStableAmount();
}
if (params.tokenIn == address(0)) {
revert ZeroAddress();
}
// tokenIn must always be dUSD for flash loan
if (params.tokenIn != address(dstable)) {
revert InvalidArbitrageParameters();
}
// tokenOut can be either dUSD or a collateral asset
if (params.tokenOut == address(0)) {
revert ZeroAddress();
}
if (params.slippageBps > MAX_SLIPPAGE_BPS) {
revert SlippageTooHigh(params.slippageBps, MAX_SLIPPAGE_BPS);
}
if (params.profitTo == address(0)) {
revert ZeroAddress();
}
if (params.fraxswapRoute.length == 0) {
revert NoSwapPathProvided();
}
// If collateralAsset is provided, validate it
if (params.collateralAsset != address(0)) {
// Validate collateral asset is supported by issuer
// Note: We check via issuer's collateralVault which has isCollateralSupported()
// Since we can't directly access collateralVault from issuer, we'll validate
// that collateralAsset matches tokenOut (route should end in collateral)
if (params.collateralAsset != params.tokenOut) {
revert InvalidCollateralAsset();
}
// Validate minDusdFromIssue is provided if collateralAsset is set
if (params.minDusdFromIssue == 0) {
revert InvalidArbitrageParameters();
}
} else {
// If no collateralAsset, route should end in dUSD
if (params.tokenOut != address(dstable)) {
revert InvalidArbitrageParameters();
}
}
}
function _validateArbitrageParams(
ArbitrageParams memory params
) internal view {
if (block.timestamp > params.deadline) {
revert DeadlineExceeded();
}
if (params.tokenIn == address(0) || params.tokenOut == address(0)) {
revert ZeroAddress();
}
if (params.slippageBps > MAX_SLIPPAGE_BPS) {
revert SlippageTooHigh(params.slippageBps, MAX_SLIPPAGE_BPS);
}
if (params.profitTo == address(0)) {
revert ZeroAddress();
}
if (params.fraxswapRoute.length == 0) {
revert NoSwapPathProvided();
}
}
function _validateIssueSwapRedeemParams(
IssueSwapRedeemParams calldata params
) internal view {
if (block.timestamp > params.deadline) {
revert DeadlineExceeded();
}
if (params.collateralAsset == address(0)) {
revert InvalidCollateralAsset();
}
if (params.collateralAmount == 0) {
revert ZeroCollateralAmount();
}
if (params.slippageBps > MAX_SLIPPAGE_BPS) {
revert SlippageTooHigh(params.slippageBps, MAX_SLIPPAGE_BPS);
}
if (params.profitTo == address(0)) {
revert ZeroAddress();
}
}
function _validateFlashLoanRedeemSwapIssueParams(
FlashLoanRedeemSwapIssueParams calldata params
) internal view {
if (block.timestamp > params.deadline) {
revert DeadlineExceeded();
}
if (params.collateralAsset == address(0)) {
revert InvalidCollateralAsset();
}
if (params.flashLoanAmount == 0) {
revert ZeroDStableAmount();
}
if (params.slippageBps > MAX_SLIPPAGE_BPS) {
revert SlippageTooHigh(params.slippageBps, MAX_SLIPPAGE_BPS);
}
if (params.profitTo == address(0)) {
revert ZeroAddress();
}
}
function _validateFlashLoanSwapIssueParams(
FlashLoanSwapIssueParams calldata params
) internal view {
if (block.timestamp > params.deadline) {
revert DeadlineExceeded();
}
if (params.collateralAsset == address(0)) {
revert InvalidCollateralAsset();
}
if (params.flashLoanAmount == 0) {
revert ZeroDStableAmount();
}
if (params.slippageBps > MAX_SLIPPAGE_BPS) {
revert SlippageTooHigh(params.slippageBps, MAX_SLIPPAGE_BPS);
}
if (params.profitTo == address(0)) {
revert ZeroAddress();
}
if (params.fraxswapRoute.length == 0) {
revert NoSwapPathProvided();
}
}
function _executeArbitrageWithAmount(
uint256 amount,
FlashLoanArbitrageParams memory params
) internal returns (uint256) {
uint256 balanceBefore = IERC20(params.tokenOut).balanceOf(address(this));
// Approve Fraxtal router to spend the flash loaned amount
IERC20(params.tokenIn).forceApprove(address(fraxswapRouter), 0);
IERC20(params.tokenIn).forceApprove(address(fraxswapRouter), amount);
// Execute Fraxtal multihop arbitrage
IFraxswapRouterMultihop.FraxswapParams memory fraxParams = IFraxswapRouterMultihop.FraxswapParams({
tokenIn: params.tokenIn,
amountIn: amount,
tokenOut: params.tokenOut,
amountOutMinimum: params.minAmountOut,
recipient: address(this),
deadline: params.deadline,
approveMax: params.approveMax,
v: params.v,
r: params.r,
s: params.s,
route: params.fraxswapRoute
});
try fraxswapRouter.swap(fraxParams) returns (
uint256 /* amountOut */
) {
// Success
} catch (bytes memory reason) {
revert FraxswapSwapFailed(reason);
}
uint256 balanceAfter = IERC20(params.tokenOut).balanceOf(address(this));
return balanceAfter ;
}
function _executeFraxswapArbitrage(
ArbitrageParams memory params
) internal {
// Create Fraxtal multihop parameters
IFraxswapRouterMultihop.FraxswapParams memory fraxParams = IFraxswapRouterMultihop.FraxswapParams({
tokenIn: params.tokenIn,
amountIn: params.amountIn,
tokenOut: params.tokenOut,
amountOutMinimum: params.minAmountOut,
recipient: address(this),
deadline: params.deadline,
approveMax: params.approveMax,
v: params.v,
r: params.r,
s: params.s,
route: params.fraxswapRoute
});
try fraxswapRouter.swap(fraxParams) returns (
uint256 /* amountOut */
) {
// Success
} catch (bytes memory reason) {
revert FraxswapSwapFailed(reason);
}
}
function _calculateAndDistributeProfit(
address tokenIn,
address tokenOut,
uint256 amountIn,
uint256 amountOut,
address profitTo
) internal returns (uint256) {
// For simplicity, assuming we're arbitraging the same token
// In practice, you'd need to convert amounts to a common unit
if (tokenIn == tokenOut) {
uint256 profit = amountOut > amountIn ? amountOut - amountIn : 0;
if (profit > 0) {
IERC20(tokenOut).safeTransfer(profitTo, profit);
}
return profit;
}
return 0;
}
function _executeArbitrageInternal(
ArbitrageParams memory params
) internal {
_validateArbitrageParams(params);
uint256 balanceBefore = IERC20(params.tokenOut).balanceOf(address(this));
// Approve input token to Fraxtal router
IERC20(params.tokenIn).forceApprove(address(fraxswapRouter), 0);
IERC20(params.tokenIn).forceApprove(address(fraxswapRouter), params.amountIn);
// Execute Fraxtal multihop arbitrage
_executeFraxswapArbitrage(params);
uint256 balanceAfter = IERC20(params.tokenOut).balanceOf(address(this));
uint256 amountOut = balanceAfter - balanceBefore;
if (amountOut < params.minAmountOut) {
revert InsufficientDStableReceived(params.minAmountOut, amountOut);
}
// Calculate and distribute profit
uint256 profit = _calculateAndDistributeProfit(
params.tokenIn,
params.tokenOut,
params.amountIn,
amountOut,
params.profitTo
);
emit ArbitrageExecuted(
params.tokenIn,
params.tokenOut,
params.amountIn,
amountOut,
profit,
"Fraxtal-Multihop"
);
}
function _unpackArbitrageParams(
PackedArbitrageParams calldata params
) internal view returns (ArbitrageParams memory) {
// This would implement the unpacking logic similar to the FraxArb contract
// For now, returning a basic structure - implement based on your packing format
return ArbitrageParams({
tokenIn: params.tokenIn,
tokenOut: params.tokenOut,
amountIn: params.amountIn,
minAmountOut: params.minAmountOut,
deadline: params.deadline,
slippageBps: 100, // Default 1%
profitTo: msg.sender,
fraxswapRoute: params.packedData, // Use packed data as route
v: 0,
r: bytes32(0),
s: bytes32(0),
approveMax: false
});
}
function _calculateAmountLimit(
uint256 quotedAmount,
uint256 slippageBps
) internal pure returns (uint256) {
return
(quotedAmount * (HUNDRED_PERCENT_BPS - slippageBps)) /
HUNDRED_PERCENT_BPS;
}
/**
* @notice Internal helper function to ISSUE dUSD from collateral
* @param collateralAsset The collateral asset to issue from
* @param collateralAmount The amount of collateral to issue with
* @param minDusd The minimum dUSD to receive from ISSUE
* @return dusdIssued The amount of dUSD issued
*/
function _issueDusdFromCollateral(
address collateralAsset,
uint256 collateralAmount,
uint256 minDusd
) internal returns (uint256) {
// Approve issuer to spend collateral
IERC20(collateralAsset).forceApprove(address(issuer), 0);
IERC20(collateralAsset).forceApprove(address(issuer), collateralAmount);
// Issue dUSD
uint256 dusdBalanceBefore = dstable.balanceOf(address(this));
issuer.issue(collateralAmount, collateralAsset, minDusd);
uint256 dusdIssued = dstable.balanceOf(address(this)) - dusdBalanceBefore;
if (dusdIssued < minDusd) {
revert InsufficientDusdIssued(minDusd, dusdIssued);
}
return dusdIssued;
}
}// 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;
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC3156FlashBorrower.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC3156 FlashBorrower, as defined in
* https://eips.ethereum.org/EIPS/eip-3156[ERC-3156].
*/
interface IERC3156FlashBorrower {
/**
* @dev Receive a flash loan.
* @param initiator The initiator of the loan.
* @param token The loan currency.
* @param amount The amount of tokens lent.
* @param fee The additional amount of tokens to repay.
* @param data Arbitrary data structure, intended to contain user-defined parameters.
* @return The keccak256 hash of "ERC3156FlashBorrower.onFlashLoan"
*/
function onFlashLoan(
address initiator,
address token,
uint256 amount,
uint256 fee,
bytes calldata data
) external returns (bytes32);
}// 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);
}// 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;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/ReentrancyGuard.sol)
pragma solidity ^0.8.20;
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuard {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant NOT_ENTERED = 1;
uint256 private constant ENTERED = 2;
uint256 private _status;
/**
* @dev Unauthorized reentrant call.
*/
error ReentrancyGuardReentrantCall();
constructor() {
_status = NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
// On the first call to nonReentrant, _status will be NOT_ENTERED
if (_status == ENTERED) {
revert ReentrancyGuardReentrantCall();
}
// Any calls to nonReentrant after this point will fail
_status = ENTERED;
}
function _nonReentrantAfter() private {
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = NOT_ENTERED;
}
/**
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
* `nonReentrant` function in the call stack.
*/
function _reentrancyGuardEntered() internal view returns (bool) {
return _status == ENTERED;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Pausable.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
/**
* @dev Contract module which allows children to implement an emergency stop
* mechanism that can be triggered by an authorized account.
*
* This module is used through inheritance. It will make available the
* modifiers `whenNotPaused` and `whenPaused`, which can be applied to
* the functions of your contract. Note that they will not be pausable by
* simply including this module, only once the modifiers are put in place.
*/
abstract contract Pausable is Context {
bool private _paused;
/**
* @dev Emitted when the pause is triggered by `account`.
*/
event Paused(address account);
/**
* @dev Emitted when the pause is lifted by `account`.
*/
event Unpaused(address account);
/**
* @dev The operation failed because the contract is paused.
*/
error EnforcedPause();
/**
* @dev The operation failed because the contract is not paused.
*/
error ExpectedPause();
/**
* @dev Initializes the contract in unpaused state.
*/
constructor() {
_paused = false;
}
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*
* Requirements:
*
* - The contract must not be paused.
*/
modifier whenNotPaused() {
_requireNotPaused();
_;
}
/**
* @dev Modifier to make a function callable only when the contract is paused.
*
* Requirements:
*
* - The contract must be paused.
*/
modifier whenPaused() {
_requirePaused();
_;
}
/**
* @dev Returns true if the contract is paused, and false otherwise.
*/
function paused() public view virtual returns (bool) {
return _paused;
}
/**
* @dev Throws if the contract is paused.
*/
function _requireNotPaused() internal view virtual {
if (paused()) {
revert EnforcedPause();
}
}
/**
* @dev Throws if the contract is not paused.
*/
function _requirePaused() internal view virtual {
if (!paused()) {
revert ExpectedPause();
}
}
/**
* @dev Triggers stopped state.
*
* Requirements:
*
* - The contract must not be paused.
*/
function _pause() internal virtual whenNotPaused {
_paused = true;
emit Paused(_msgSender());
}
/**
* @dev Returns to normal state.
*
* Requirements:
*
* - The contract must be paused.
*/
function _unpause() internal virtual whenPaused {
_paused = false;
emit Unpaused(_msgSender());
}
}// SPDX-License-Identifier: MIT
/* ———————————————————————————————————————————————————————————————————————————————— *
* _____ ______ ______ __ __ __ __ ______ __ __ *
* /\ __-. /\__ _\ /\ == \ /\ \ /\ "-.\ \ /\ \ /\__ _\ /\ \_\ \ *
* \ \ \/\ \ \/_/\ \/ \ \ __< \ \ \ \ \ \-. \ \ \ \ \/_/\ \/ \ \____ \ *
* \ \____- \ \_\ \ \_\ \_\ \ \_\ \ \_\\"\_\ \ \_\ \ \_\ \/\_____\ *
* \/____/ \/_/ \/_/ /_/ \/_/ \/_/ \/_/ \/_/ \/_/ \/_____/ *
* *
* ————————————————————————————————— dtrinity.org ————————————————————————————————— *
* *
* ▲ *
* ▲ ▲ *
* *
* ———————————————————————————————————————————————————————————————————————————————— *
* dTRINITY Protocol: https://github.com/dtrinity *
* ———————————————————————————————————————————————————————————————————————————————— */
pragma solidity ^0.8.9;
import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20BurnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PermitUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20FlashMintUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "./extensions/ERC20ComplianceUpgradeable.sol";
contract ERC20StablecoinUpgradeable is
Initializable,
ERC20Upgradeable,
ERC20BurnableUpgradeable,
PausableUpgradeable,
AccessControlUpgradeable,
ERC20PermitUpgradeable,
ERC20FlashMintUpgradeable,
ERC20ComplianceUpgradeable
{
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
bytes32 public constant COMPLIANCE_ROLE = keccak256("COMPLIANCE_ROLE");
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
function initialize(
string memory name,
string memory symbol
) public initializer {
__ERC20_init(name, symbol);
__ERC20Burnable_init();
__Pausable_init();
__AccessControl_init();
__ERC20Permit_init(name);
__ERC20FlashMint_init();
__ERC20Compliance_init();
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
_grantRole(PAUSER_ROLE, msg.sender);
_grantRole(MINTER_ROLE, msg.sender);
}
function decimals() public view virtual override returns (uint8) {
return 6;
}
function pause() public onlyRole(PAUSER_ROLE) {
_pause();
}
function unpause() public onlyRole(PAUSER_ROLE) {
_unpause();
}
function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) {
_mint(to, amount);
}
function freeze(address account) public virtual onlyRole(COMPLIANCE_ROLE) {
_freeze(account);
}
function unfreeze(
address account
) public virtual onlyRole(COMPLIANCE_ROLE) {
_unfreeze(account);
}
function seize(
address account,
uint256 amount
) public virtual onlyRole(COMPLIANCE_ROLE) {
_seize(account, amount);
}
function _beforeTokenTransfer(
address from,
address to,
uint256 amount
) internal override whenNotPaused whenNotFrozen(from) {
super._beforeTokenTransfer(from, to, amount);
}
}// SPDX-License-Identifier: MIT
/* ———————————————————————————————————————————————————————————————————————————————— *
* _____ ______ ______ __ __ __ __ ______ __ __ *
* /\ __-. /\__ _\ /\ == \ /\ \ /\ "-.\ \ /\ \ /\__ _\ /\ \_\ \ *
* \ \ \/\ \ \/_/\ \/ \ \ __< \ \ \ \ \ \-. \ \ \ \ \/_/\ \/ \ \____ \ *
* \ \____- \ \_\ \ \_\ \_\ \ \_\ \ \_\\"\_\ \ \_\ \ \_\ \/\_____\ *
* \/____/ \/_/ \/_/ /_/ \/_/ \/_/ \/_/ \/_/ \/_/ \/_____/ *
* *
* ————————————————————————————————— dtrinity.org ————————————————————————————————— *
* *
* ▲ *
* ▲ ▲ *
* *
* ———————————————————————————————————————————————————————————————————————————————— *
* dTRINITY Protocol: https://github.com/dtrinity *
* ———————————————————————————————————————————————————————————————————————————————— */
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/Pausable.sol";
import "@openzeppelin/contracts/utils/math/Math.sol";
import "contracts/common/IAaveOracle.sol";
import "contracts/common/IMintableERC20.sol";
import "./CollateralVault.sol";
import "./AmoManager.sol";
import "./OracleAware.sol";
/**
* @title IssuerV2
* @notice Extended issuer responsible for issuing dStable tokens with asset-level minting overrides and global pause
*/
contract IssuerV2 is AccessControl, OracleAware, ReentrancyGuard, Pausable {
using SafeERC20 for IERC20Metadata;
/* Core state */
IMintableERC20 public dstable;
uint8 public immutable dstableDecimals;
CollateralVault public collateralVault;
AmoManager public amoManager;
/* Events */
event CollateralVaultSet(address indexed collateralVault);
event AmoManagerSet(address indexed amoManager);
event AssetMintingPauseUpdated(address indexed asset, bool paused);
/* Roles */
bytes32 public constant AMO_MANAGER_ROLE = keccak256("AMO_MANAGER_ROLE");
bytes32 public constant INCENTIVES_MANAGER_ROLE = keccak256("INCENTIVES_MANAGER_ROLE");
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
/* Errors */
error SlippageTooHigh(uint256 minDStable, uint256 dstableAmount);
error IssuanceSurpassesExcessCollateral(uint256 collateralInDstable, uint256 circulatingDstable);
error MintingToAmoShouldNotIncreaseSupply(uint256 circulatingDstableBefore, uint256 circulatingDstableAfter);
error AssetMintingPaused(address asset);
/* Overrides */
// If true, minting with this collateral asset is paused at the issuer level
mapping(address => bool) public assetMintingPaused;
/**
* @notice Initializes the IssuerV2 contract with core dependencies
* @param _collateralVault The address of the collateral vault
* @param _dstable The address of the dStable stablecoin
* @param oracle The address of the price oracle
* @param _amoManager The address of the AMO Manager
*/
constructor(
address _collateralVault,
address _dstable,
IPriceOracleGetter oracle,
address _amoManager
) OracleAware(oracle, oracle.BASE_CURRENCY_UNIT()) {
collateralVault = CollateralVault(_collateralVault);
dstable = IMintableERC20(_dstable);
dstableDecimals = dstable.decimals();
amoManager = AmoManager(_amoManager);
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
grantRole(AMO_MANAGER_ROLE, msg.sender);
grantRole(INCENTIVES_MANAGER_ROLE, msg.sender);
grantRole(PAUSER_ROLE, msg.sender);
}
/* Issuer */
/**
* @notice Issues dStable tokens in exchange for collateral from the caller
* @param collateralAmount The amount of collateral to deposit
* @param collateralAsset The address of the collateral asset
* @param minDStable The minimum amount of dStable to receive, used for slippage protection
*/
function issue(
uint256 collateralAmount,
address collateralAsset,
uint256 minDStable
) external nonReentrant whenNotPaused {
// Ensure the collateral asset is supported by the vault before any further processing
if (!collateralVault.isCollateralSupported(collateralAsset)) {
revert CollateralVault.UnsupportedCollateral(collateralAsset);
}
// Ensure the issuer has not paused this asset for minting
if (assetMintingPaused[collateralAsset]) {
revert AssetMintingPaused(collateralAsset);
}
uint8 collateralDecimals = IERC20Metadata(collateralAsset).decimals();
uint256 baseValue = Math.mulDiv(
oracle.getAssetPrice(collateralAsset),
collateralAmount,
10 ** collateralDecimals
);
uint256 dstableAmount = baseValueToDstableAmount(baseValue);
if (dstableAmount < minDStable) {
revert SlippageTooHigh(minDStable, dstableAmount);
}
// Transfer collateral directly to vault
IERC20Metadata(collateralAsset).safeTransferFrom(msg.sender, address(collateralVault), collateralAmount);
dstable.mint(msg.sender, dstableAmount);
}
/**
* @notice Issues dStable tokens using excess collateral in the system
* @param receiver The address to receive the minted dStable tokens
* @param dstableAmount The amount of dStable to mint
*/
function issueUsingExcessCollateral(
address receiver,
uint256 dstableAmount
) external onlyRole(INCENTIVES_MANAGER_ROLE) whenNotPaused {
dstable.mint(receiver, dstableAmount);
// We don't use the buffer value here because we only mint up to the excess collateral
uint256 _circulatingDstable = circulatingDstable();
uint256 _collateralInDstable = collateralInDstable();
if (_collateralInDstable < _circulatingDstable) {
revert IssuanceSurpassesExcessCollateral(_collateralInDstable, _circulatingDstable);
}
}
/**
* @notice Increases the AMO supply by minting new dStable tokens
* @param dstableAmount The amount of dStable to mint and send to the AMO Manager
*/
function increaseAmoSupply(uint256 dstableAmount) external onlyRole(AMO_MANAGER_ROLE) whenNotPaused {
uint256 _circulatingDstableBefore = circulatingDstable();
dstable.mint(address(amoManager), dstableAmount);
uint256 _circulatingDstableAfter = circulatingDstable();
// Sanity check that we are sending to the active AMO Manager
if (_circulatingDstableAfter != _circulatingDstableBefore) {
revert MintingToAmoShouldNotIncreaseSupply(_circulatingDstableBefore, _circulatingDstableAfter);
}
}
/**
* @notice Calculates the circulating supply of dStable tokens
* @return The amount of dStable tokens that are not held by the AMO Manager
*/
function circulatingDstable() public view returns (uint256) {
uint256 totalDstable = dstable.totalSupply();
uint256 amoDstable = amoManager.totalAmoSupply();
return totalDstable - amoDstable;
}
/**
* @notice Calculates the collateral value in dStable tokens
* @return The amount of dStable tokens equivalent to the collateral value
*/
function collateralInDstable() public view returns (uint256) {
uint256 _collateralInBase = collateralVault.totalValue();
return baseValueToDstableAmount(_collateralInBase);
}
/**
* @notice Converts a base value to an equivalent amount of dStable tokens
* @param baseValue The amount of base value to convert
* @return The equivalent amount of dStable tokens
*/
function baseValueToDstableAmount(uint256 baseValue) public view returns (uint256) {
return Math.mulDiv(baseValue, 10 ** dstableDecimals, baseCurrencyUnit);
}
/**
* @notice Returns whether `asset` is currently enabled for minting by the issuer
* @dev Asset must be supported by the collateral vault and not paused by issuer
*/
function isAssetMintingEnabled(address asset) public view returns (bool) {
if (!collateralVault.isCollateralSupported(asset)) return false;
return !assetMintingPaused[asset];
}
/* Admin */
/**
* @notice Sets the AMO Manager address
* @param _amoManager The address of the AMO Manager
*/
function setAmoManager(address _amoManager) external onlyRole(DEFAULT_ADMIN_ROLE) {
address old = address(amoManager);
amoManager = AmoManager(_amoManager);
grantRole(AMO_MANAGER_ROLE, _amoManager);
if (old != address(0) && old != _amoManager) {
revokeRole(AMO_MANAGER_ROLE, old);
}
emit AmoManagerSet(_amoManager);
}
/**
* @notice Sets the collateral vault address
* @param _collateralVault The address of the collateral vault
*/
function setCollateralVault(address _collateralVault) external onlyRole(DEFAULT_ADMIN_ROLE) {
collateralVault = CollateralVault(_collateralVault);
emit CollateralVaultSet(_collateralVault);
}
/**
* @notice Set minting pause override for a specific collateral asset
* @param asset The collateral asset address
* @param paused True to pause minting; false to enable
*/
function setAssetMintingPause(address asset, bool paused) external onlyRole(PAUSER_ROLE) {
// Optional guard: if vault does not support the asset, setting an override is meaningless
if (!collateralVault.isCollateralSupported(asset)) {
revert CollateralVault.UnsupportedCollateral(asset);
}
assetMintingPaused[asset] = paused;
emit AssetMintingPauseUpdated(asset, paused);
}
/**
* @notice Pause all minting operations
*/
function pauseMinting() external onlyRole(PAUSER_ROLE) {
_pause();
}
/**
* @notice Unpause all minting operations
*/
function unpauseMinting() external onlyRole(PAUSER_ROLE) {
_unpause();
}
}// SPDX-License-Identifier: MIT
/* ———————————————————————————————————————————————————————————————————————————————— *
* _____ ______ ______ __ __ __ __ ______ __ __ *
* /\ __-. /\__ _\ /\ == \ /\ \ /\ "-.\ \ /\ \ /\__ _\ /\ \_\ \ *
* \ \ \/\ \ \/_/\ \/ \ \ __< \ \ \ \ \ \-. \ \ \ \ \/_/\ \/ \ \____ \ *
* \ \____- \ \_\ \ \_\ \_\ \ \_\ \ \_\\"\_\ \ \_\ \ \_\ \/\_____\ *
* \/____/ \/_/ \/_/ /_/ \/_/ \/_/ \/_/ \/_/ \/_/ \/_____/ *
* *
* ————————————————————————————————— dtrinity.org ————————————————————————————————— *
* *
* ▲ *
* ▲ ▲ *
* *
* ———————————————————————————————————————————————————————————————————————————————— *
* dTRINITY Protocol: https://github.com/dtrinity *
* ———————————————————————————————————————————————————————————————————————————————— */
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/access/AccessControl.sol";
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
import "@openzeppelin/contracts/utils/Pausable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "contracts/common/IMintableERC20.sol";
import "contracts/common/BasisPointConstants.sol";
import "./CollateralVault.sol";
import "./OracleAware.sol";
/**
* @title RedeemerV2
* @notice Extended Redeemer with global pause and per-asset redemption pause controls
*/
contract RedeemerV2 is AccessControl, OracleAware, Pausable, ReentrancyGuard {
/* Constants */
uint256 public immutable MAX_FEE_BPS;
/* Core state */
IMintableERC20 public dstable;
uint8 public immutable dstableDecimals;
CollateralVault public collateralVault;
/* Fee related state */
address public feeReceiver;
uint256 public defaultRedemptionFeeBps; // Default fee in basis points
// Per-asset fee bps. Separately track whether an override is active to allow 0 bps overrides even if default > 0.
mapping(address => uint256) public collateralRedemptionFeeBps; // Fee in basis points per collateral asset
mapping(address => bool) public isCollateralFeeOverridden;
/* Events */
event AssetRedemptionPauseUpdated(address indexed asset, bool paused);
event FeeReceiverUpdated(address indexed oldFeeReceiver, address indexed newFeeReceiver);
event DefaultRedemptionFeeUpdated(uint256 oldFeeBps, uint256 newFeeBps);
event CollateralRedemptionFeeUpdated(address indexed collateralAsset, uint256 oldFeeBps, uint256 newFeeBps);
event Redemption(
address indexed redeemer,
address indexed collateralAsset,
uint256 dstableAmount,
uint256 collateralAmountToRedeemer,
uint256 feeAmountCollateral
);
event CollateralVaultSet(address indexed collateralVault);
/* Roles */
bytes32 public constant REDEMPTION_MANAGER_ROLE = keccak256("REDEMPTION_MANAGER_ROLE");
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
/* Errors */
error DStableTransferFailed();
error SlippageTooHigh(uint256 actualCollateral, uint256 minCollateral);
error AssetRedemptionPaused(address asset);
error FeeTooHigh(uint256 requestedFeeBps, uint256 maxFeeBps);
error CollateralTransferFailed(address recipient, uint256 amount, address token);
error CannotBeZeroAddress();
/* Overrides */
// If true, redemption with this collateral asset is paused at the redeemer level
mapping(address => bool) public assetRedemptionPaused;
/**
* @notice Initializes the RedeemerV2 contract
* @param _collateralVault The address of the collateral vault
* @param _dstable The address of the dStable stablecoin
* @param _oracle The address of the price oracle
* @param _initialFeeReceiver The initial address to receive redemption fees
* @param _initialRedemptionFeeBps The initial redemption fee in basis points
*/
constructor(
address _collateralVault,
address _dstable,
IPriceOracleGetter _oracle,
address _initialFeeReceiver,
uint256 _initialRedemptionFeeBps
) OracleAware(_oracle, _oracle.BASE_CURRENCY_UNIT()) {
if (_collateralVault == address(0) || _dstable == address(0) || address(_oracle) == address(0)) {
revert CannotBeZeroAddress();
}
if (_initialFeeReceiver == address(0)) {
revert CannotBeZeroAddress();
}
MAX_FEE_BPS = 5 * BasisPointConstants.ONE_PERCENT_BPS; // 5%
if (_initialRedemptionFeeBps > MAX_FEE_BPS) {
revert FeeTooHigh(_initialRedemptionFeeBps, MAX_FEE_BPS);
}
collateralVault = CollateralVault(_collateralVault);
dstable = IMintableERC20(_dstable);
dstableDecimals = dstable.decimals();
// Initial fee configuration
feeReceiver = _initialFeeReceiver;
defaultRedemptionFeeBps = _initialRedemptionFeeBps;
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
grantRole(REDEMPTION_MANAGER_ROLE, msg.sender);
grantRole(PAUSER_ROLE, msg.sender);
emit FeeReceiverUpdated(address(0), _initialFeeReceiver);
emit DefaultRedemptionFeeUpdated(0, _initialRedemptionFeeBps);
}
/* Redeemer */
function redeem(
uint256 dstableAmount,
address collateralAsset,
uint256 minNetCollateral
) external whenNotPaused nonReentrant {
// Ensure the collateral asset is supported by the vault before any further processing
if (!collateralVault.isCollateralSupported(collateralAsset)) {
revert CollateralVault.UnsupportedCollateral(collateralAsset);
}
// Ensure the redeemer has not paused this asset for redemption
if (assetRedemptionPaused[collateralAsset]) {
revert AssetRedemptionPaused(collateralAsset);
}
// Calculate collateral amount and fee
uint256 dstableValue = dstableAmountToBaseValue(dstableAmount);
uint256 totalCollateral = collateralVault.assetAmountFromValue(dstableValue, collateralAsset);
uint256 currentFeeBps = isCollateralFeeOverridden[collateralAsset]
? collateralRedemptionFeeBps[collateralAsset]
: defaultRedemptionFeeBps;
uint256 feeCollateral = 0;
if (currentFeeBps > 0) {
feeCollateral = Math.mulDiv(totalCollateral, currentFeeBps, BasisPointConstants.ONE_HUNDRED_PERCENT_BPS);
}
uint256 netCollateral = totalCollateral - feeCollateral;
if (netCollateral < minNetCollateral) {
revert SlippageTooHigh(netCollateral, minNetCollateral);
}
// Burn and withdraw net amount to redeemer
_redeem(msg.sender, dstableAmount, collateralAsset, netCollateral);
// Withdraw fee to feeReceiver
if (feeCollateral > 0) {
collateralVault.withdrawTo(feeReceiver, feeCollateral, collateralAsset);
}
emit Redemption(msg.sender, collateralAsset, dstableAmount, netCollateral, feeCollateral);
}
function redeemAsProtocol(
uint256 dstableAmount,
address collateralAsset,
uint256 minCollateral
) external onlyRole(REDEMPTION_MANAGER_ROLE) whenNotPaused nonReentrant {
// Ensure the collateral asset is supported by the vault before any further processing
if (!collateralVault.isCollateralSupported(collateralAsset)) {
revert CollateralVault.UnsupportedCollateral(collateralAsset);
}
// Ensure the redeemer has not paused this asset for redemption
if (assetRedemptionPaused[collateralAsset]) {
revert AssetRedemptionPaused(collateralAsset);
}
// Calculate collateral amount
uint256 dstableValue = dstableAmountToBaseValue(dstableAmount);
uint256 totalCollateral = collateralVault.assetAmountFromValue(dstableValue, collateralAsset);
if (totalCollateral < minCollateral) {
revert SlippageTooHigh(totalCollateral, minCollateral);
}
// Burn and withdraw full amount to redeemer
_redeem(msg.sender, dstableAmount, collateralAsset, totalCollateral);
emit Redemption(msg.sender, collateralAsset, dstableAmount, totalCollateral, 0);
}
function _redeem(
address redeemerAddress,
uint256 dstableAmount,
address collateralAsset,
uint256 collateralAmount
) internal {
// Transfer dStable from redeemer to this contract
if (!dstable.transferFrom(redeemerAddress, address(this), dstableAmount)) {
revert DStableTransferFailed();
}
// Burn the dStable
dstable.burn(dstableAmount);
// Withdraw collateral from the vault
collateralVault.withdrawTo(redeemerAddress, collateralAmount, collateralAsset);
}
function dstableAmountToBaseValue(uint256 dstableAmount) public view returns (uint256) {
return Math.mulDiv(dstableAmount, baseCurrencyUnit, 10 ** dstableDecimals);
}
/* Views */
function isAssetRedemptionEnabled(address asset) public view returns (bool) {
if (!collateralVault.isCollateralSupported(asset)) return false;
return !assetRedemptionPaused[asset];
}
/* Admin */
function setCollateralVault(address _collateralVault) external onlyRole(DEFAULT_ADMIN_ROLE) {
if (_collateralVault == address(0)) {
revert CannotBeZeroAddress();
}
collateralVault = CollateralVault(_collateralVault);
emit CollateralVaultSet(_collateralVault);
}
function setAssetRedemptionPause(address asset, bool paused) external onlyRole(PAUSER_ROLE) {
if (!collateralVault.isCollateralSupported(asset)) {
revert CollateralVault.UnsupportedCollateral(asset);
}
assetRedemptionPaused[asset] = paused;
emit AssetRedemptionPauseUpdated(asset, paused);
}
function setFeeReceiver(address _newFeeReceiver) external onlyRole(DEFAULT_ADMIN_ROLE) {
if (_newFeeReceiver == address(0)) {
revert CannotBeZeroAddress();
}
address oldFeeReceiver = feeReceiver;
feeReceiver = _newFeeReceiver;
emit FeeReceiverUpdated(oldFeeReceiver, _newFeeReceiver);
}
function setDefaultRedemptionFee(uint256 _newFeeBps) external onlyRole(DEFAULT_ADMIN_ROLE) {
if (_newFeeBps > MAX_FEE_BPS) {
revert FeeTooHigh(_newFeeBps, MAX_FEE_BPS);
}
uint256 oldFeeBps = defaultRedemptionFeeBps;
defaultRedemptionFeeBps = _newFeeBps;
emit DefaultRedemptionFeeUpdated(oldFeeBps, _newFeeBps);
}
function setCollateralRedemptionFee(
address _collateralAsset,
uint256 _newFeeBps
) external onlyRole(DEFAULT_ADMIN_ROLE) {
if (_collateralAsset == address(0)) {
revert CannotBeZeroAddress();
}
if (_newFeeBps > MAX_FEE_BPS) {
revert FeeTooHigh(_newFeeBps, MAX_FEE_BPS);
}
uint256 oldFeeBps = collateralRedemptionFeeBps[_collateralAsset];
collateralRedemptionFeeBps[_collateralAsset] = _newFeeBps;
isCollateralFeeOverridden[_collateralAsset] = true; // enable override, allowing 0 bps explicitly
emit CollateralRedemptionFeeUpdated(_collateralAsset, oldFeeBps, _newFeeBps);
}
/**
* @notice Clears a per-asset fee override so the default fee applies again
* @param _collateralAsset The collateral asset for which to clear the override
*/
function clearCollateralRedemptionFee(address _collateralAsset) external onlyRole(DEFAULT_ADMIN_ROLE) {
if (_collateralAsset == address(0)) {
revert CannotBeZeroAddress();
}
uint256 oldFeeBps = collateralRedemptionFeeBps[_collateralAsset];
collateralRedemptionFeeBps[_collateralAsset] = 0;
isCollateralFeeOverridden[_collateralAsset] = false;
emit CollateralRedemptionFeeUpdated(_collateralAsset, oldFeeBps, 0);
}
function pauseRedemption() external onlyRole(PAUSER_ROLE) {
_pause();
}
function unpauseRedemption() external onlyRole(PAUSER_ROLE) {
_unpause();
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/IAccessControl.sol)
pragma solidity ^0.8.20;
/**
* @dev External interface of AccessControl declared to support ERC165 detection.
*/
interface IAccessControl {
/**
* @dev The `account` is missing a role.
*/
error AccessControlUnauthorizedAccount(address account, bytes32 neededRole);
/**
* @dev The caller of a function is not the expected one.
*
* NOTE: Don't confuse with {AccessControlUnauthorizedAccount}.
*/
error AccessControlBadConfirmation();
/**
* @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
*
* `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
* {RoleAdminChanged} not being emitted signaling this.
*/
event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);
/**
* @dev Emitted when `account` is granted `role`.
*
* `sender` is the account that originated the contract call, an admin role
* bearer except when using {AccessControl-_setupRole}.
*/
event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Emitted when `account` is revoked `role`.
*
* `sender` is the account that originated the contract call:
* - if using `revokeRole`, it is the admin role bearer
* - if using `renounceRole`, it is the role bearer (i.e. `account`)
*/
event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) external view returns (bool);
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
*
* To change a role's admin, use {AccessControl-_setRoleAdmin}.
*/
function getRoleAdmin(bytes32 role) external view returns (bytes32);
/**
* @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.
*/
function grantRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function revokeRole(bytes32 role, address account) external;
/**
* @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 granted `role`, emits a {RoleRevoked}
* event.
*
* Requirements:
*
* - the caller must be `callerConfirmation`.
*/
function renounceRole(bytes32 role, address callerConfirmation) external;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Context.sol)
pragma solidity ^0.8.20;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/ERC165.sol)
pragma solidity ^0.8.20;
import {IERC165} from "./IERC165.sol";
/**
* @dev Implementation of the {IERC165} interface.
*
* Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
* for the additional interface id that will be supported. For example:
*
* ```solidity
* function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
* return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
* }
* ```
*/
abstract contract ERC165 is IERC165 {
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
return interfaceId == type(IERC165).interfaceId;
}
}// 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);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol)
pragma solidity ^0.8.20;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev The ETH balance of the account is not enough to perform the operation.
*/
error AddressInsufficientBalance(address account);
/**
* @dev There's no code at `target` (it is not a contract).
*/
error AddressEmptyCode(address target);
/**
* @dev A call to an address target failed. The target may have reverted.
*/
error FailedInnerCall();
/**
* @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 AddressInsufficientBalance(address(this));
}
(bool success, ) = recipient.call{value: amount}("");
if (!success) {
revert FailedInnerCall();
}
}
/**
* @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
* {FailedInnerCall} 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 AddressInsufficientBalance(address(this));
}
(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 {FailedInnerCall}) 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 {FailedInnerCall} 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 {FailedInnerCall}.
*/
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
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert FailedInnerCall();
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/ERC20.sol)
pragma solidity ^0.8.0;
import "./IERC20Upgradeable.sol";
import "./extensions/IERC20MetadataUpgradeable.sol";
import "../../utils/ContextUpgradeable.sol";
import "../../proxy/utils/Initializable.sol";
/**
* @dev Implementation of the {IERC20} interface.
*
* This implementation is agnostic to the way tokens are created. This means
* that a supply mechanism has to be added in a derived contract using {_mint}.
* For a generic mechanism see {ERC20PresetMinterPauser}.
*
* TIP: For a detailed writeup see our guide
* https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
* to implement supply mechanisms].
*
* The default value of {decimals} is 18. To change this, you should override
* this function so it returns a different value.
*
* We have followed general OpenZeppelin Contracts guidelines: functions revert
* instead returning `false` on failure. This behavior is nonetheless
* conventional and does not conflict with the expectations of ERC20
* applications.
*
* Additionally, an {Approval} event is emitted on calls to {transferFrom}.
* This allows applications to reconstruct the allowance for all accounts just
* by listening to said events. Other implementations of the EIP may not emit
* these events, as it isn't required by the specification.
*
* Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
* functions have been added to mitigate the well-known issues around setting
* allowances. See {IERC20-approve}.
*/
contract ERC20Upgradeable is Initializable, ContextUpgradeable, IERC20Upgradeable, IERC20MetadataUpgradeable {
mapping(address => uint256) private _balances;
mapping(address => mapping(address => uint256)) private _allowances;
uint256 private _totalSupply;
string private _name;
string private _symbol;
/**
* @dev Sets the values for {name} and {symbol}.
*
* All two of these values are immutable: they can only be set once during
* construction.
*/
function __ERC20_init(string memory name_, string memory symbol_) internal onlyInitializing {
__ERC20_init_unchained(name_, symbol_);
}
function __ERC20_init_unchained(string memory name_, string memory symbol_) internal onlyInitializing {
_name = name_;
_symbol = symbol_;
}
/**
* @dev Returns the name of the token.
*/
function name() public view virtual override returns (string memory) {
return _name;
}
/**
* @dev Returns the symbol of the token, usually a shorter version of the
* name.
*/
function symbol() public view virtual override returns (string memory) {
return _symbol;
}
/**
* @dev Returns the number of decimals used to get its user representation.
* For example, if `decimals` equals `2`, a balance of `505` tokens should
* be displayed to a user as `5.05` (`505 / 10 ** 2`).
*
* Tokens usually opt for a value of 18, imitating the relationship between
* Ether and Wei. This is the default value returned by this function, unless
* it's overridden.
*
* NOTE: This information is only used for _display_ purposes: it in
* no way affects any of the arithmetic of the contract, including
* {IERC20-balanceOf} and {IERC20-transfer}.
*/
function decimals() public view virtual override returns (uint8) {
return 18;
}
/**
* @dev See {IERC20-totalSupply}.
*/
function totalSupply() public view virtual override returns (uint256) {
return _totalSupply;
}
/**
* @dev See {IERC20-balanceOf}.
*/
function balanceOf(address account) public view virtual override returns (uint256) {
return _balances[account];
}
/**
* @dev See {IERC20-transfer}.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - the caller must have a balance of at least `amount`.
*/
function transfer(address to, uint256 amount) public virtual override returns (bool) {
address owner = _msgSender();
_transfer(owner, to, amount);
return true;
}
/**
* @dev See {IERC20-allowance}.
*/
function allowance(address owner, address spender) public view virtual override returns (uint256) {
return _allowances[owner][spender];
}
/**
* @dev See {IERC20-approve}.
*
* NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on
* `transferFrom`. This is semantically equivalent to an infinite approval.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function approve(address spender, uint256 amount) public virtual override returns (bool) {
address owner = _msgSender();
_approve(owner, spender, amount);
return true;
}
/**
* @dev See {IERC20-transferFrom}.
*
* Emits an {Approval} event indicating the updated allowance. This is not
* required by the EIP. See the note at the beginning of {ERC20}.
*
* NOTE: Does not update the allowance if the current allowance
* is the maximum `uint256`.
*
* Requirements:
*
* - `from` and `to` cannot be the zero address.
* - `from` must have a balance of at least `amount`.
* - the caller must have allowance for ``from``'s tokens of at least
* `amount`.
*/
function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) {
address spender = _msgSender();
_spendAllowance(from, spender, amount);
_transfer(from, to, amount);
return true;
}
/**
* @dev Atomically increases the allowance granted to `spender` by the caller.
*
* This is an alternative to {approve} that can be used as a mitigation for
* problems described in {IERC20-approve}.
*
* Emits an {Approval} event indicating the updated allowance.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
address owner = _msgSender();
_approve(owner, spender, allowance(owner, spender) + addedValue);
return true;
}
/**
* @dev Atomically decreases the allowance granted to `spender` by the caller.
*
* This is an alternative to {approve} that can be used as a mitigation for
* problems described in {IERC20-approve}.
*
* Emits an {Approval} event indicating the updated allowance.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `spender` must have allowance for the caller of at least
* `subtractedValue`.
*/
function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
address owner = _msgSender();
uint256 currentAllowance = allowance(owner, spender);
require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
unchecked {
_approve(owner, spender, currentAllowance - subtractedValue);
}
return true;
}
/**
* @dev Moves `amount` of tokens from `from` to `to`.
*
* This internal function is equivalent to {transfer}, and can be used to
* e.g. implement automatic token fees, slashing mechanisms, etc.
*
* Emits a {Transfer} event.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `from` must have a balance of at least `amount`.
*/
function _transfer(address from, address to, uint256 amount) internal virtual {
require(from != address(0), "ERC20: transfer from the zero address");
require(to != address(0), "ERC20: transfer to the zero address");
_beforeTokenTransfer(from, to, amount);
uint256 fromBalance = _balances[from];
require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
unchecked {
_balances[from] = fromBalance - amount;
// Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by
// decrementing then incrementing.
_balances[to] += amount;
}
emit Transfer(from, to, amount);
_afterTokenTransfer(from, to, amount);
}
/** @dev Creates `amount` tokens and assigns them to `account`, increasing
* the total supply.
*
* Emits a {Transfer} event with `from` set to the zero address.
*
* Requirements:
*
* - `account` cannot be the zero address.
*/
function _mint(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20: mint to the zero address");
_beforeTokenTransfer(address(0), account, amount);
_totalSupply += amount;
unchecked {
// Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above.
_balances[account] += amount;
}
emit Transfer(address(0), account, amount);
_afterTokenTransfer(address(0), account, amount);
}
/**
* @dev Destroys `amount` tokens from `account`, reducing the
* total supply.
*
* Emits a {Transfer} event with `to` set to the zero address.
*
* Requirements:
*
* - `account` cannot be the zero address.
* - `account` must have at least `amount` tokens.
*/
function _burn(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20: burn from the zero address");
_beforeTokenTransfer(account, address(0), amount);
uint256 accountBalance = _balances[account];
require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
unchecked {
_balances[account] = accountBalance - amount;
// Overflow not possible: amount <= accountBalance <= totalSupply.
_totalSupply -= amount;
}
emit Transfer(account, address(0), amount);
_afterTokenTransfer(account, address(0), amount);
}
/**
* @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
*
* This internal function is equivalent to `approve`, and can be used to
* e.g. set automatic allowances for certain subsystems, etc.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `owner` cannot be the zero address.
* - `spender` cannot be the zero address.
*/
function _approve(address owner, address spender, uint256 amount) internal virtual {
require(owner != address(0), "ERC20: approve from the zero address");
require(spender != address(0), "ERC20: approve to the zero address");
_allowances[owner][spender] = amount;
emit Approval(owner, spender, amount);
}
/**
* @dev Updates `owner` s allowance for `spender` based on spent `amount`.
*
* Does not update the allowance amount in case of infinite allowance.
* Revert if not enough allowance is available.
*
* Might emit an {Approval} event.
*/
function _spendAllowance(address owner, address spender, uint256 amount) internal virtual {
uint256 currentAllowance = allowance(owner, spender);
if (currentAllowance != type(uint256).max) {
require(currentAllowance >= amount, "ERC20: insufficient allowance");
unchecked {
_approve(owner, spender, currentAllowance - amount);
}
}
}
/**
* @dev Hook that is called before any transfer of tokens. This includes
* minting and burning.
*
* Calling conditions:
*
* - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
* will be transferred to `to`.
* - when `from` is zero, `amount` tokens will be minted for `to`.
* - when `to` is zero, `amount` of ``from``'s tokens will be burned.
* - `from` and `to` are never both zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {}
/**
* @dev Hook that is called after any transfer of tokens. This includes
* minting and burning.
*
* Calling conditions:
*
* - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
* has been transferred to `to`.
* - when `from` is zero, `amount` tokens have been minted for `to`.
* - when `to` is zero, `amount` of ``from``'s tokens have been burned.
* - `from` and `to` are never both zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[45] private __gap;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/extensions/ERC20Burnable.sol)
pragma solidity ^0.8.0;
import "../ERC20Upgradeable.sol";
import "../../../utils/ContextUpgradeable.sol";
import "../../../proxy/utils/Initializable.sol";
/**
* @dev Extension of {ERC20} that allows token holders to destroy both their own
* tokens and those that they have an allowance for, in a way that can be
* recognized off-chain (via event analysis).
*/
abstract contract ERC20BurnableUpgradeable is Initializable, ContextUpgradeable, ERC20Upgradeable {
function __ERC20Burnable_init() internal onlyInitializing {
}
function __ERC20Burnable_init_unchained() internal onlyInitializing {
}
/**
* @dev Destroys `amount` tokens from the caller.
*
* See {ERC20-_burn}.
*/
function burn(uint256 amount) public virtual {
_burn(_msgSender(), amount);
}
/**
* @dev Destroys `amount` tokens from `account`, deducting from the caller's
* allowance.
*
* See {ERC20-_burn} and {ERC20-allowance}.
*
* Requirements:
*
* - the caller must have allowance for ``accounts``'s tokens of at least
* `amount`.
*/
function burnFrom(address account, uint256 amount) public virtual {
_spendAllowance(account, _msgSender(), amount);
_burn(account, amount);
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[50] private __gap;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (security/Pausable.sol)
pragma solidity ^0.8.0;
import "../utils/ContextUpgradeable.sol";
import "../proxy/utils/Initializable.sol";
/**
* @dev Contract module which allows children to implement an emergency stop
* mechanism that can be triggered by an authorized account.
*
* This module is used through inheritance. It will make available the
* modifiers `whenNotPaused` and `whenPaused`, which can be applied to
* the functions of your contract. Note that they will not be pausable by
* simply including this module, only once the modifiers are put in place.
*/
abstract contract PausableUpgradeable is Initializable, ContextUpgradeable {
/**
* @dev Emitted when the pause is triggered by `account`.
*/
event Paused(address account);
/**
* @dev Emitted when the pause is lifted by `account`.
*/
event Unpaused(address account);
bool private _paused;
/**
* @dev Initializes the contract in unpaused state.
*/
function __Pausable_init() internal onlyInitializing {
__Pausable_init_unchained();
}
function __Pausable_init_unchained() internal onlyInitializing {
_paused = false;
}
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*
* Requirements:
*
* - The contract must not be paused.
*/
modifier whenNotPaused() {
_requireNotPaused();
_;
}
/**
* @dev Modifier to make a function callable only when the contract is paused.
*
* Requirements:
*
* - The contract must be paused.
*/
modifier whenPaused() {
_requirePaused();
_;
}
/**
* @dev Returns true if the contract is paused, and false otherwise.
*/
function paused() public view virtual returns (bool) {
return _paused;
}
/**
* @dev Throws if the contract is paused.
*/
function _requireNotPaused() internal view virtual {
require(!paused(), "Pausable: paused");
}
/**
* @dev Throws if the contract is not paused.
*/
function _requirePaused() internal view virtual {
require(paused(), "Pausable: not paused");
}
/**
* @dev Triggers stopped state.
*
* Requirements:
*
* - The contract must not be paused.
*/
function _pause() internal virtual whenNotPaused {
_paused = true;
emit Paused(_msgSender());
}
/**
* @dev Returns to normal state.
*
* Requirements:
*
* - The contract must be paused.
*/
function _unpause() internal virtual whenPaused {
_paused = false;
emit Unpaused(_msgSender());
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[49] private __gap;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/AccessControl.sol)
pragma solidity ^0.8.0;
import "./IAccessControlUpgradeable.sol";
import "../utils/ContextUpgradeable.sol";
import "../utils/StringsUpgradeable.sol";
import "../utils/introspection/ERC165Upgradeable.sol";
import "../proxy/utils/Initializable.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 AccessControlUpgradeable is Initializable, ContextUpgradeable, IAccessControlUpgradeable, ERC165Upgradeable {
function __AccessControl_init() internal onlyInitializing {
}
function __AccessControl_init_unchained() internal onlyInitializing {
}
struct RoleData {
mapping(address => bool) members;
bytes32 adminRole;
}
mapping(bytes32 => RoleData) private _roles;
bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
/**
* @dev Modifier that checks that an account has a specific role. Reverts
* with a standardized message including the required role.
*
* The format of the revert reason is given by the following regular expression:
*
* /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
*
* _Available since v4.1._
*/
modifier onlyRole(bytes32 role) {
_checkRole(role);
_;
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IAccessControlUpgradeable).interfaceId || super.supportsInterface(interfaceId);
}
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) public view virtual override returns (bool) {
return _roles[role].members[account];
}
/**
* @dev Revert with a standard message if `_msgSender()` is missing `role`.
* Overriding this function changes the behavior of the {onlyRole} modifier.
*
* Format of the revert message is described in {_checkRole}.
*
* _Available since v4.6._
*/
function _checkRole(bytes32 role) internal view virtual {
_checkRole(role, _msgSender());
}
/**
* @dev Revert with a standard message if `account` is missing `role`.
*
* The format of the revert reason is given by the following regular expression:
*
* /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
*/
function _checkRole(bytes32 role, address account) internal view virtual {
if (!hasRole(role, account)) {
revert(
string(
abi.encodePacked(
"AccessControl: account ",
StringsUpgradeable.toHexString(account),
" is missing role ",
StringsUpgradeable.toHexString(uint256(role), 32)
)
)
);
}
}
/**
* @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 override 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 override 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 override 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 `account`.
*
* May emit a {RoleRevoked} event.
*/
function renounceRole(bytes32 role, address account) public virtual override {
require(account == _msgSender(), "AccessControl: can only renounce roles for self");
_revokeRole(role, account);
}
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event. Note that unlike {grantRole}, this function doesn't perform any
* checks on the calling account.
*
* May emit a {RoleGranted} event.
*
* [WARNING]
* ====
* This function should only be called from the constructor when setting
* up the initial roles for the system.
*
* Using this function in any other way is effectively circumventing the admin
* system imposed by {AccessControl}.
* ====
*
* NOTE: This function is deprecated in favor of {_grantRole}.
*/
function _setupRole(bytes32 role, address account) internal virtual {
_grantRole(role, account);
}
/**
* @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 Grants `role` to `account`.
*
* Internal function without access restriction.
*
* May emit a {RoleGranted} event.
*/
function _grantRole(bytes32 role, address account) internal virtual {
if (!hasRole(role, account)) {
_roles[role].members[account] = true;
emit RoleGranted(role, account, _msgSender());
}
}
/**
* @dev Revokes `role` from `account`.
*
* Internal function without access restriction.
*
* May emit a {RoleRevoked} event.
*/
function _revokeRole(bytes32 role, address account) internal virtual {
if (hasRole(role, account)) {
_roles[role].members[account] = false;
emit RoleRevoked(role, account, _msgSender());
}
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[49] private __gap;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/ERC20Permit.sol)
pragma solidity ^0.8.0;
import "./IERC20PermitUpgradeable.sol";
import "../ERC20Upgradeable.sol";
import "../../../utils/cryptography/ECDSAUpgradeable.sol";
import "../../../utils/cryptography/EIP712Upgradeable.sol";
import "../../../utils/CountersUpgradeable.sol";
import "../../../proxy/utils/Initializable.sol";
/**
* @dev Implementation 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.
*
* _Available since v3.4._
*
* @custom:storage-size 51
*/
abstract contract ERC20PermitUpgradeable is Initializable, ERC20Upgradeable, IERC20PermitUpgradeable, EIP712Upgradeable {
using CountersUpgradeable for CountersUpgradeable.Counter;
mapping(address => CountersUpgradeable.Counter) private _nonces;
// solhint-disable-next-line var-name-mixedcase
bytes32 private constant _PERMIT_TYPEHASH =
keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
/**
* @dev In previous versions `_PERMIT_TYPEHASH` was declared as `immutable`.
* However, to ensure consistency with the upgradeable transpiler, we will continue
* to reserve a slot.
* @custom:oz-renamed-from _PERMIT_TYPEHASH
*/
// solhint-disable-next-line var-name-mixedcase
bytes32 private _PERMIT_TYPEHASH_DEPRECATED_SLOT;
/**
* @dev Initializes the {EIP712} domain separator using the `name` parameter, and setting `version` to `"1"`.
*
* It's a good idea to use the same `name` that is defined as the ERC20 token name.
*/
function __ERC20Permit_init(string memory name) internal onlyInitializing {
__EIP712_init_unchained(name, "1");
}
function __ERC20Permit_init_unchained(string memory) internal onlyInitializing {}
/**
* @dev See {IERC20Permit-permit}.
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public virtual override {
require(block.timestamp <= deadline, "ERC20Permit: expired deadline");
bytes32 structHash = keccak256(abi.encode(_PERMIT_TYPEHASH, owner, spender, value, _useNonce(owner), deadline));
bytes32 hash = _hashTypedDataV4(structHash);
address signer = ECDSAUpgradeable.recover(hash, v, r, s);
require(signer == owner, "ERC20Permit: invalid signature");
_approve(owner, spender, value);
}
/**
* @dev See {IERC20Permit-nonces}.
*/
function nonces(address owner) public view virtual override returns (uint256) {
return _nonces[owner].current();
}
/**
* @dev See {IERC20Permit-DOMAIN_SEPARATOR}.
*/
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view override returns (bytes32) {
return _domainSeparatorV4();
}
/**
* @dev "Consume a nonce": return the current value and increment.
*
* _Available since v4.1._
*/
function _useNonce(address owner) internal virtual returns (uint256 current) {
CountersUpgradeable.Counter storage nonce = _nonces[owner];
current = nonce.current();
nonce.increment();
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[49] private __gap;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/extensions/ERC20FlashMint.sol)
pragma solidity ^0.8.0;
import "../../../interfaces/IERC3156FlashBorrowerUpgradeable.sol";
import "../../../interfaces/IERC3156FlashLenderUpgradeable.sol";
import "../ERC20Upgradeable.sol";
import "../../../proxy/utils/Initializable.sol";
/**
* @dev Implementation of the ERC3156 Flash loans extension, as defined in
* https://eips.ethereum.org/EIPS/eip-3156[ERC-3156].
*
* Adds the {flashLoan} method, which provides flash loan support at the token
* level. By default there is no fee, but this can be changed by overriding {flashFee}.
*
* _Available since v4.1._
*/
abstract contract ERC20FlashMintUpgradeable is Initializable, ERC20Upgradeable, IERC3156FlashLenderUpgradeable {
function __ERC20FlashMint_init() internal onlyInitializing {
}
function __ERC20FlashMint_init_unchained() internal onlyInitializing {
}
bytes32 private constant _RETURN_VALUE = keccak256("ERC3156FlashBorrower.onFlashLoan");
/**
* @dev Returns the maximum amount of tokens available for loan.
* @param token The address of the token that is requested.
* @return The amount of token that can be loaned.
*/
function maxFlashLoan(address token) public view virtual override returns (uint256) {
return token == address(this) ? type(uint256).max - ERC20Upgradeable.totalSupply() : 0;
}
/**
* @dev Returns the fee applied when doing flash loans. This function calls
* the {_flashFee} function which returns the fee applied when doing flash
* loans.
* @param token The token to be flash loaned.
* @param amount The amount of tokens to be loaned.
* @return The fees applied to the corresponding flash loan.
*/
function flashFee(address token, uint256 amount) public view virtual override returns (uint256) {
require(token == address(this), "ERC20FlashMint: wrong token");
return _flashFee(token, amount);
}
/**
* @dev Returns the fee applied when doing flash loans. By default this
* implementation has 0 fees. This function can be overloaded to make
* the flash loan mechanism deflationary.
* @param token The token to be flash loaned.
* @param amount The amount of tokens to be loaned.
* @return The fees applied to the corresponding flash loan.
*/
function _flashFee(address token, uint256 amount) internal view virtual returns (uint256) {
// silence warning about unused variable without the addition of bytecode.
token;
amount;
return 0;
}
/**
* @dev Returns the receiver address of the flash fee. By default this
* implementation returns the address(0) which means the fee amount will be burnt.
* This function can be overloaded to change the fee receiver.
* @return The address for which the flash fee will be sent to.
*/
function _flashFeeReceiver() internal view virtual returns (address) {
return address(0);
}
/**
* @dev Performs a flash loan. New tokens are minted and sent to the
* `receiver`, who is required to implement the {IERC3156FlashBorrower}
* interface. By the end of the flash loan, the receiver is expected to own
* amount + fee tokens and have them approved back to the token contract itself so
* they can be burned.
* @param receiver The receiver of the flash loan. Should implement the
* {IERC3156FlashBorrower-onFlashLoan} interface.
* @param token The token to be flash loaned. Only `address(this)` is
* supported.
* @param amount The amount of tokens to be loaned.
* @param data An arbitrary datafield that is passed to the receiver.
* @return `true` if the flash loan was successful.
*/
// This function can reenter, but it doesn't pose a risk because it always preserves the property that the amount
// minted at the beginning is always recovered and burned at the end, or else the entire function will revert.
// slither-disable-next-line reentrancy-no-eth
function flashLoan(
IERC3156FlashBorrowerUpgradeable receiver,
address token,
uint256 amount,
bytes calldata data
) public virtual override returns (bool) {
require(amount <= maxFlashLoan(token), "ERC20FlashMint: amount exceeds maxFlashLoan");
uint256 fee = flashFee(token, amount);
_mint(address(receiver), amount);
require(
receiver.onFlashLoan(msg.sender, token, amount, fee, data) == _RETURN_VALUE,
"ERC20FlashMint: invalid return value"
);
address flashFeeReceiver = _flashFeeReceiver();
_spendAllowance(address(receiver), address(this), amount + fee);
if (fee == 0 || flashFeeReceiver == address(0)) {
_burn(address(receiver), amount + fee);
} else {
_burn(address(receiver), amount);
_transfer(address(receiver), flashFeeReceiver, fee);
}
return true;
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[50] private __gap;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (proxy/utils/Initializable.sol)
pragma solidity ^0.8.2;
import "../../utils/AddressUpgradeable.sol";
/**
* @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
* behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
* external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
* function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
*
* The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
* reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
* case an upgrade adds a module that needs to be initialized.
*
* For example:
*
* [.hljs-theme-light.nopadding]
* ```solidity
* contract MyToken is ERC20Upgradeable {
* function initialize() initializer public {
* __ERC20_init("MyToken", "MTK");
* }
* }
*
* contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
* function initializeV2() reinitializer(2) public {
* __ERC20Permit_init("MyToken");
* }
* }
* ```
*
* TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
* possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
*
* CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
* that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
*
* [CAUTION]
* ====
* Avoid leaving a contract uninitialized.
*
* An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
* contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
* the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
*
* [.hljs-theme-light.nopadding]
* ```
* /// @custom:oz-upgrades-unsafe-allow constructor
* constructor() {
* _disableInitializers();
* }
* ```
* ====
*/
abstract contract Initializable {
/**
* @dev Indicates that the contract has been initialized.
* @custom:oz-retyped-from bool
*/
uint8 private _initialized;
/**
* @dev Indicates that the contract is in the process of being initialized.
*/
bool private _initializing;
/**
* @dev Triggered when the contract has been initialized or reinitialized.
*/
event Initialized(uint8 version);
/**
* @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
* `onlyInitializing` functions can be used to initialize parent contracts.
*
* Similar to `reinitializer(1)`, except that functions marked with `initializer` can be nested in the context of a
* constructor.
*
* Emits an {Initialized} event.
*/
modifier initializer() {
bool isTopLevelCall = !_initializing;
require(
(isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1),
"Initializable: contract is already initialized"
);
_initialized = 1;
if (isTopLevelCall) {
_initializing = true;
}
_;
if (isTopLevelCall) {
_initializing = false;
emit Initialized(1);
}
}
/**
* @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
* contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
* used to initialize parent contracts.
*
* A reinitializer may be used after the original initialization step. This is essential to configure modules that
* are added through upgrades and that require initialization.
*
* When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
* cannot be nested. If one is invoked in the context of another, execution will revert.
*
* Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
* a contract, executing them in the right order is up to the developer or operator.
*
* WARNING: setting the version to 255 will prevent any future reinitialization.
*
* Emits an {Initialized} event.
*/
modifier reinitializer(uint8 version) {
require(!_initializing && _initialized < version, "Initializable: contract is already initialized");
_initialized = version;
_initializing = true;
_;
_initializing = false;
emit Initialized(version);
}
/**
* @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
* {initializer} and {reinitializer} modifiers, directly or indirectly.
*/
modifier onlyInitializing() {
require(_initializing, "Initializable: contract is not initializing");
_;
}
/**
* @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
* Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
* to any version. It is recommended to use this to lock implementation contracts that are designed to be called
* through proxies.
*
* Emits an {Initialized} event the first time it is successfully executed.
*/
function _disableInitializers() internal virtual {
require(!_initializing, "Initializable: contract is initializing");
if (_initialized != type(uint8).max) {
_initialized = type(uint8).max;
emit Initialized(type(uint8).max);
}
}
/**
* @dev Returns the highest version that has been initialized. See {reinitializer}.
*/
function _getInitializedVersion() internal view returns (uint8) {
return _initialized;
}
/**
* @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
*/
function _isInitializing() internal view returns (bool) {
return _initializing;
}
}// SPDX-License-Identifier: MIT
/* ———————————————————————————————————————————————————————————————————————————————— *
* _____ ______ ______ __ __ __ __ ______ __ __ *
* /\ __-. /\__ _\ /\ == \ /\ \ /\ "-.\ \ /\ \ /\__ _\ /\ \_\ \ *
* \ \ \/\ \ \/_/\ \/ \ \ __< \ \ \ \ \ \-. \ \ \ \ \/_/\ \/ \ \____ \ *
* \ \____- \ \_\ \ \_\ \_\ \ \_\ \ \_\\"\_\ \ \_\ \ \_\ \/\_____\ *
* \/____/ \/_/ \/_/ /_/ \/_/ \/_/ \/_/ \/_/ \/_/ \/_____/ *
* *
* ————————————————————————————————— dtrinity.org ————————————————————————————————— *
* *
* ▲ *
* ▲ ▲ *
* *
* ———————————————————————————————————————————————————————————————————————————————— *
* dTRINITY Protocol: https://github.com/dtrinity *
* ———————————————————————————————————————————————————————————————————————————————— */
pragma solidity ^0.8.9;
import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
/**
* @dev Extension of {ERC20} that allows a compliance role to freeze and seize
* tokens for compliance purposes.
*/
abstract contract ERC20ComplianceUpgradeable is
Initializable,
ContextUpgradeable,
ERC20Upgradeable
{
mapping(address => bool) private _frozen;
function __ERC20Compliance_init() internal onlyInitializing {}
function __ERC20Compliance_init_unchained() internal onlyInitializing {}
modifier whenNotFrozen(address account) {
require(!_frozen[account], "ERC20Compliance: account is frozen");
_;
}
function _freeze(address account) internal virtual {
_frozen[account] = true;
}
function _unfreeze(address account) internal virtual {
_frozen[account] = false;
}
function _seize(address account, uint256 amount) internal virtual {
_burn(account, amount);
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[50] private __gap;
}// 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);
}// 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 returns (uint256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return 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.
return a / b;
}
// (a + b - 1) / b can overflow on addition, so we distribute.
return a == 0 ? 0 : (a - 1) / b + 1;
}
/**
* @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or
* denominator == 0.
* @dev 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^256 and mod 2^256 - 1, then use
// use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
// variables such that product = prod1 * 2^256 + 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^256. Also prevents denominator == 0.
if (denominator <= prod1) {
revert MathOverflowedMulDiv();
}
///////////////////////////////////////////////
// 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^256 / 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^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
// that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
// four bits. That is, denominator * inv = 1 mod 2^4.
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^8
inverse *= 2 - denominator * inverse; // inverse mod 2^16
inverse *= 2 - denominator * inverse; // inverse mod 2^32
inverse *= 2 - denominator * inverse; // inverse mod 2^64
inverse *= 2 - denominator * inverse; // inverse mod 2^128
inverse *= 2 - denominator * inverse; // inverse mod 2^256
// 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^256. Since the preconditions guarantee that the outcome is
// less than 2^256, 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;
}
}
/**
* @notice 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) {
uint256 result = mulDiv(x, y, denominator);
if (unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0) {
result += 1;
}
return result;
}
/**
* @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded
* towards zero.
*
* Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
*/
function sqrt(uint256 a) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
// For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
//
// We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
// `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
//
// This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
// → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
// → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
//
// Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
uint256 result = 1 << (log2(a) >> 1);
// At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
// since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
// every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
// into the expected uint128 result.
unchecked {
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
return min(result, a / result);
}
}
/**
* @notice Calculates sqrt(a), following the selected rounding direction.
*/
function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = sqrt(a);
return result + (unsignedRoundsUp(rounding) && result * result < a ? 1 : 0);
}
}
/**
* @dev Return the log in base 2 of a positive value rounded towards zero.
* Returns 0 if given 0.
*/
function log2(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 128;
}
if (value >> 64 > 0) {
value >>= 64;
result += 64;
}
if (value >> 32 > 0) {
value >>= 32;
result += 32;
}
if (value >> 16 > 0) {
value >>= 16;
result += 16;
}
if (value >> 8 > 0) {
value >>= 8;
result += 8;
}
if (value >> 4 > 0) {
value >>= 4;
result += 4;
}
if (value >> 2 > 0) {
value >>= 2;
result += 2;
}
if (value >> 1 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 2, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log2(value);
return result + (unsignedRoundsUp(rounding) && 1 << result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 10 of a positive value rounded towards zero.
* Returns 0 if given 0.
*/
function log10(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >= 10 ** 64) {
value /= 10 ** 64;
result += 64;
}
if (value >= 10 ** 32) {
value /= 10 ** 32;
result += 32;
}
if (value >= 10 ** 16) {
value /= 10 ** 16;
result += 16;
}
if (value >= 10 ** 8) {
value /= 10 ** 8;
result += 8;
}
if (value >= 10 ** 4) {
value /= 10 ** 4;
result += 4;
}
if (value >= 10 ** 2) {
value /= 10 ** 2;
result += 2;
}
if (value >= 10 ** 1) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 10, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log10(value);
return result + (unsignedRoundsUp(rounding) && 10 ** result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 256 of a positive value rounded towards zero.
* Returns 0 if given 0.
*
* Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
*/
function log256(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 16;
}
if (value >> 64 > 0) {
value >>= 64;
result += 8;
}
if (value >> 32 > 0) {
value >>= 32;
result += 4;
}
if (value >> 16 > 0) {
value >>= 16;
result += 2;
}
if (value >> 8 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 256, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log256(value);
return result + (unsignedRoundsUp(rounding) && 1 << (result << 3) < value ? 1 : 0);
}
}
/**
* @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers.
*/
function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) {
return uint8(rounding) % 2 == 1;
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
interface IAaveOracle {
function getAssetPrice(address asset) external view returns (uint256);
function getAssetsPrices(address[] calldata assets) external view returns (uint256[] memory);
function getSourceOfAsset(address asset) external view returns (address);
function getFallbackOracle() external view returns (address);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
interface IMintableERC20 {
function mint(address to, uint256 amount) external;
function burn(uint256 amount) external;
function decimals() external view returns (uint8);
function totalSupply() external view returns (uint256);
function transferFrom(address from, address to, uint256 amount) external returns (bool);
function transfer(address to, uint256 amount) external returns (bool);
function approve(address spender, uint256 amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function name() external view returns (string memory);
function symbol() external view returns (string memory);
}// SPDX-License-Identifier: MIT
/* ———————————————————————————————————————————————————————————————————————————————— *
* _____ ______ ______ __ __ __ __ ______ __ __ *
* /\ __-. /\__ _\ /\ == \ /\ \ /\ "-.\ \ /\ \ /\__ _\ /\ \_\ \ *
* \ \ \/\ \ \/_/\ \/ \ \ __< \ \ \ \ \ \-. \ \ \ \ \/_/\ \/ \ \____ \ *
* \ \____- \ \_\ \ \_\ \_\ \ \_\ \ \_\\"\_\ \ \_\ \ \_\ \/\_____\ *
* \/____/ \/_/ \/_/ /_/ \/_/ \/_/ \/_/ \/_/ \/_/ \/_____/ *
* *
* ————————————————————————————————— dtrinity.org ————————————————————————————————— *
* *
* ▲ *
* ▲ ▲ *
* *
* ———————————————————————————————————————————————————————————————————————————————— *
* dTRINITY Protocol: https://github.com/dtrinity *
* ———————————————————————————————————————————————————————————————————————————————— */
pragma solidity ^0.8.20;
import "@openzeppelin/contracts-5/access/AccessControl.sol";
import "@openzeppelin/contracts-5/token/ERC20/extensions/IERC20Metadata.sol";
import "@openzeppelin/contracts-5/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts-5/utils/structs/EnumerableSet.sol";
import "src/shared/Constants.sol";
import "src/lending/core/interfaces/IPriceOracleGetter.sol";
import "src/dusd/OracleAware.sol";
/**
* @title CollateralVault
* @notice Abstract contract for any contract that manages collateral assets
\ */
abstract contract CollateralVault is AccessControl, OracleAware {
using SafeERC20 for IERC20Metadata;
using EnumerableSet for EnumerableSet.AddressSet;
/* Core state */
EnumerableSet.AddressSet internal _supportedCollaterals;
/* Events */
event CollateralAllowed(address indexed collateralAsset);
event CollateralDisallowed(address indexed collateralAsset);
/* Roles */
bytes32 public constant COLLATERAL_MANAGER_ROLE =
keccak256("COLLATERAL_MANAGER_ROLE");
bytes32 public constant COLLATERAL_STRATEGY_ROLE =
keccak256("COLLATERAL_STRATEGY_ROLE");
bytes32 public constant COLLATERAL_WITHDRAWER_ROLE =
keccak256("COLLATERAL_WITHDRAWER_ROLE");
/* Errors */
error UnsupportedCollateral(address collateralAsset);
error CollateralAlreadyAllowed(address collateralAsset);
error NoOracleSupport(address collateralAsset);
error FailedToAddCollateral(address collateralAsset);
error CollateralNotSupported(address collateralAsset);
error MustSupportAtLeastOneCollateral();
error FailedToRemoveCollateral(address collateralAsset);
/**
* @notice Initializes the vault with an oracle and sets up initial roles
* @dev Grants all roles to the contract deployer initially
* @param oracle The price oracle to use for collateral valuation
*/
constructor(
IPriceOracleGetter oracle
) OracleAware(oracle, Constants.ORACLE_BASE_CURRENCY_UNIT) {
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender); // This is the super admin
grantRole(COLLATERAL_MANAGER_ROLE, msg.sender);
grantRole(COLLATERAL_WITHDRAWER_ROLE, msg.sender);
grantRole(COLLATERAL_STRATEGY_ROLE, msg.sender);
}
/* Deposit */
/**
* @notice Deposit collateral into the vault
* @param collateralAmount The amount of collateral to deposit
* @param collateralAsset The address of the collateral asset
*/
function deposit(uint256 collateralAmount, address collateralAsset) public {
if (!_supportedCollaterals.contains(collateralAsset)) {
revert UnsupportedCollateral(collateralAsset);
}
IERC20Metadata(collateralAsset).safeTransferFrom(
msg.sender,
address(this),
collateralAmount
);
}
/* Withdrawal */
/**
* @notice Withdraws collateral from the vault
* @param collateralAmount The amount of collateral to withdraw
* @param collateralAsset The address of the collateral asset
*/
function withdraw(
uint256 collateralAmount,
address collateralAsset
) public onlyRole(COLLATERAL_WITHDRAWER_ROLE) {
return _withdraw(msg.sender, collateralAmount, collateralAsset);
}
/**
* @notice Withdraws collateral from the vault to a specific address
* @param recipient The address receiving the collateral
* @param collateralAmount The amount of collateral to withdraw
* @param collateralAsset The address of the collateral asset
*/
function withdrawTo(
address recipient,
uint256 collateralAmount,
address collateralAsset
) public onlyRole(COLLATERAL_WITHDRAWER_ROLE) {
return _withdraw(recipient, collateralAmount, collateralAsset);
}
/**
* @notice Internal function to withdraw collateral from the vault
* @param withdrawer The address withdrawing the collateral
* @param collateralAmount The amount of collateral to withdraw
* @param collateralAsset The address of the collateral asset
*/
function _withdraw(
address withdrawer,
uint256 collateralAmount,
address collateralAsset
) internal {
IERC20Metadata(collateralAsset).safeTransfer(
withdrawer,
collateralAmount
);
}
/* Collateral Info */
/**
* @notice Calculates the total value of all assets in the vault
* @return usdValue The total value of all assets in USD
*/
function totalValue() public view virtual returns (uint256 usdValue);
/**
* @notice Calculates the USD value of a given amount of an asset
* @param assetAmount The amount of the asset
* @param asset The address of the asset
* @return usdValue The USD value of the asset
*/
function assetValueFromAmount(
uint256 assetAmount,
address asset
) public view returns (uint256 usdValue) {
uint256 assetPrice = oracle.getAssetPrice(asset);
uint8 assetDecimals = IERC20Metadata(asset).decimals();
return (assetPrice * assetAmount) / (10 ** assetDecimals);
}
/**
* @notice Calculates the amount of an asset that corresponds to a given USD value
* @param usdValue The USD value
* @param asset The address of the asset
* @return assetAmount The amount of the asset
*/
function assetAmountFromValue(
uint256 usdValue,
address asset
) public view returns (uint256 assetAmount) {
uint256 assetPrice = oracle.getAssetPrice(asset);
uint8 assetDecimals = IERC20Metadata(asset).decimals();
return (usdValue * (10 ** assetDecimals)) / assetPrice;
}
/* Collateral management */
/**
* @notice Allows a new collateral asset
* @param collateralAsset The address of the collateral asset
*/
function allowCollateral(
address collateralAsset
) public onlyRole(COLLATERAL_MANAGER_ROLE) {
if (_supportedCollaterals.contains(collateralAsset)) {
revert CollateralAlreadyAllowed(collateralAsset);
}
if (oracle.getAssetPrice(collateralAsset) == 0) {
revert NoOracleSupport(collateralAsset);
}
if (!_supportedCollaterals.add(collateralAsset)) {
revert FailedToAddCollateral(collateralAsset);
}
emit CollateralAllowed(collateralAsset);
}
/**
* @notice Disallows a previously supported collateral asset
* @dev Requires at least one collateral asset to remain supported
* @param collateralAsset The address of the collateral asset to disallow
*/
function disallowCollateral(
address collateralAsset
) public onlyRole(COLLATERAL_MANAGER_ROLE) {
if (!_supportedCollaterals.contains(collateralAsset)) {
revert CollateralNotSupported(collateralAsset);
}
if (_supportedCollaterals.length() <= 1) {
revert MustSupportAtLeastOneCollateral();
}
if (!_supportedCollaterals.remove(collateralAsset)) {
revert FailedToRemoveCollateral(collateralAsset);
}
emit CollateralDisallowed(collateralAsset);
}
/**
* @notice Checks if a given asset is supported as collateral
* @param collateralAsset The address of the collateral asset to check
* @return bool True if the asset is supported, false otherwise
*/
function isCollateralSupported(
address collateralAsset
) public view returns (bool) {
return _supportedCollaterals.contains(collateralAsset);
}
/**
* @notice Returns a list of all supported collateral assets
* @return address[] Array of collateral asset addresses
*/
function listCollateral() public view returns (address[] memory) {
return _supportedCollaterals.values();
}
/**
* @notice Calculates the total USD value of all supported collateral assets in the vault
* @dev Iterates through all supported collaterals and sums their USD values
* @return uint256 The total value in USD
*/
function _totalValueOfSupportedCollaterals()
internal
view
returns (uint256)
{
uint256 totalUsdValue = 0;
for (uint256 i = 0; i < _supportedCollaterals.length(); i++) {
address collateral = _supportedCollaterals.at(i);
uint256 collateralPrice = oracle.getAssetPrice(collateral);
uint8 collateralDecimals = IERC20Metadata(collateral).decimals();
uint256 collateralValue = (collateralPrice *
IERC20Metadata(collateral).balanceOf(address(this))) /
(10 ** collateralDecimals);
totalUsdValue += collateralValue;
}
return totalUsdValue;
}
}// SPDX-License-Identifier: MIT
/* ———————————————————————————————————————————————————————————————————————————————— *
* _____ ______ ______ __ __ __ __ ______ __ __ *
* /\ __-. /\__ _\ /\ == \ /\ \ /\ "-.\ \ /\ \ /\__ _\ /\ \_\ \ *
* \ \ \/\ \ \/_/\ \/ \ \ __< \ \ \ \ \ \-. \ \ \ \ \/_/\ \/ \ \____ \ *
* \ \____- \ \_\ \ \_\ \_\ \ \_\ \ \_\\"\_\ \ \_\ \ \_\ \/\_____\ *
* \/____/ \/_/ \/_/ /_/ \/_/ \/_/ \/_/ \/_/ \/_/ \/_____/ *
* *
* ————————————————————————————————— dtrinity.org ————————————————————————————————— *
* *
* ▲ *
* ▲ ▲ *
* *
* ———————————————————————————————————————————————————————————————————————————————— *
* dTRINITY Protocol: https://github.com/dtrinity *
* ———————————————————————————————————————————————————————————————————————————————— */
pragma solidity ^0.8.20;
import "@openzeppelin/contracts-5/access/AccessControl.sol";
import "@openzeppelin/contracts-5/utils/structs/EnumerableMap.sol";
import "src/shared/Constants.sol";
import "src/token/IERC20Stablecoin.sol";
import "src/dusd/AmoVault.sol";
/**
* @title AmoManager
* @dev Manages AMOs for dUSD
* Handles allocation, deallocation, collateral management, and profit management for AMO vaults.
*/
contract AmoManager is AccessControl, OracleAware {
using EnumerableMap for EnumerableMap.AddressToUintMap;
/* Core state */
EnumerableMap.AddressToUintMap private _amoVaults;
uint256 public totalAllocated;
IERC20Stablecoin public dusd;
CollateralVault public collateralHolderVault;
uint256 public immutable USD_UNIT;
/* Events */
event AmoVaultSet(address indexed amoVault, bool isActive);
event AmoAllocated(address indexed amoVault, uint256 dusdAmount);
event AmoDeallocated(address indexed amoVault, uint256 dusdAmount);
event ProfitsWithdrawn(address indexed amoVault, uint256 amount);
/* Roles */
bytes32 public constant AMO_ALLOCATOR_ROLE =
keccak256("AMO_ALLOCATOR_ROLE");
bytes32 public constant FEE_COLLECTOR_ROLE =
keccak256("FEE_COLLECTOR_ROLE");
/* Errors */
error InactiveAmoVault(address amoVault);
error AmoSupplyInvariantViolation(
uint256 startingSupply,
uint256 endingSupply
);
error AmoVaultAlreadyEnabled(address amoVault);
error CannotTransferDUSD();
error InsufficientProfits(
uint256 takeProfitValueInUsd,
int256 availableProfitInUsd
);
/**
* @notice Initializes the AmoManager contract.
* @param _dusd The address of the dUSD stablecoin.
* @param _collateralHolderVault The address of the collateral holder vault.
* @param _oracle The oracle for price feeds.
*/
constructor(
address _dusd,
address _collateralHolderVault,
IPriceOracleGetter _oracle
) OracleAware(_oracle, Constants.ORACLE_BASE_CURRENCY_UNIT) {
dusd = IERC20Stablecoin(_dusd);
collateralHolderVault = CollateralVault(_collateralHolderVault);
USD_UNIT = oracle.BASE_CURRENCY_UNIT();
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
grantRole(AMO_ALLOCATOR_ROLE, msg.sender);
grantRole(FEE_COLLECTOR_ROLE, msg.sender);
}
/* AMO */
/**
* @notice Allocates AMO tokens to an AMO vault.
* @param amoVault The address of the AMO vault.
* @param dusdAmount The amount of dUSD to allocate.
*/
function allocateAmo(
address amoVault,
uint256 dusdAmount
) public onlyRole(AMO_ALLOCATOR_ROLE) {
uint256 startingAmoSupply = totalAmoSupply();
// Make sure the vault is active
if (!isAmoActive(amoVault)) {
revert InactiveAmoVault(amoVault);
}
// Update the allocation for this vault
(, uint256 currentAllocation) = _amoVaults.tryGet(amoVault);
_amoVaults.set(amoVault, currentAllocation + dusdAmount);
// Make the deposit
totalAllocated += dusdAmount;
dusd.transfer(amoVault, dusdAmount);
// Check invariants
uint256 endingAmoSupply = totalAmoSupply();
if (endingAmoSupply != startingAmoSupply) {
revert AmoSupplyInvariantViolation(
startingAmoSupply,
endingAmoSupply
);
}
emit AmoAllocated(amoVault, dusdAmount);
}
/**
* @notice Deallocates AMO tokens from an AMO vault.
* @param amoVault The address of the AMO vault.
* @param dusdAmount The amount of dUSD to deallocate.
*/
function deallocateAmo(
address amoVault,
uint256 dusdAmount
) public onlyRole(AMO_ALLOCATOR_ROLE) {
uint256 startingAmoSupply = totalAmoSupply();
// We don't require that the vault is active or has allocation, since we want to allow withdrawing from inactive vaults
// If the vault is still active, make sure it has enough allocation and decrease it
(, uint256 currentAllocation) = _amoVaults.tryGet(amoVault);
if (currentAllocation > 0) {
// Update the allocation for this vault
_amoVaults.set(amoVault, currentAllocation - dusdAmount);
}
// Make the withdrawal
totalAllocated -= dusdAmount;
dusd.transferFrom(amoVault, address(this), dusdAmount);
// Check invariants
uint256 endingAmoSupply = totalAmoSupply();
if (endingAmoSupply != startingAmoSupply) {
revert AmoSupplyInvariantViolation(
startingAmoSupply,
endingAmoSupply
);
}
emit AmoDeallocated(amoVault, dusdAmount);
}
/**
* @notice Returns the total AMO supply.
* @return The total AMO supply.
*/
function totalAmoSupply() public view returns (uint256) {
uint256 freeBalance = dusd.balanceOf(address(this));
return freeBalance + totalAllocated;
}
/**
* @notice Decreases the AMO supply by burning dUSD.
* @param dusdAmount The amount of dUSD to burn.
*/
function decreaseAmoSupply(
uint256 dusdAmount
) public onlyRole(AMO_ALLOCATOR_ROLE) {
dusd.burn(dusdAmount);
}
/**
* @notice Checks if an AMO vault is active.
* @param amoVault The address of the AMO vault to check.
* @return True if the AMO vault is active, false otherwise.
*/
function isAmoActive(address amoVault) public view returns (bool) {
return _amoVaults.contains(amoVault);
}
/**
* @notice Returns the allocation for a specific AMO vault.
* @param amoVault The address of the AMO vault.
* @return The current allocation for the vault.
*/
function amoVaultAllocation(
address amoVault
) public view returns (uint256) {
(bool exists, uint256 allocation) = _amoVaults.tryGet(amoVault);
return exists ? allocation : 0;
}
/**
* @notice Returns the list of all AMO vaults.
* @return The list of AMO vault addresses.
*/
function amoVaults() public view returns (address[] memory) {
return _amoVaults.keys();
}
/**
* @notice Enables an AMO vault.
* @param amoVault The address of the AMO vault.
*/
function enableAmoVault(
address amoVault
) public onlyRole(DEFAULT_ADMIN_ROLE) {
if (_amoVaults.contains(amoVault)) {
revert AmoVaultAlreadyEnabled(amoVault);
}
_amoVaults.set(amoVault, 0);
emit AmoVaultSet(amoVault, true);
}
/**
* @notice Disables an AMO vault.
* @param amoVault The address of the AMO vault.
*/
function disableAmoVault(
address amoVault
) public onlyRole(DEFAULT_ADMIN_ROLE) {
if (!_amoVaults.contains(amoVault)) {
revert InactiveAmoVault(amoVault);
}
_amoVaults.remove(amoVault);
emit AmoVaultSet(amoVault, false);
}
/* Collateral Management */
/**
* @notice Returns the total collateral value of all active AMO vaults.
* @return The total collateral value in USD.
*/
function totalCollateralValue() public view returns (uint256) {
uint256 totalUsdValue = 0;
for (uint256 i = 0; i < _amoVaults.length(); i++) {
(address vaultAddress, ) = _amoVaults.at(i);
if (isAmoActive(vaultAddress)) {
totalUsdValue += AmoVault(vaultAddress).totalCollateralValue();
}
}
return totalUsdValue;
}
/**
* @notice Transfers collateral from an AMO vault to the holding vault.
* @param amoVault The address of the AMO vault.
* @param token The address of the collateral token to transfer.
* @param amount The amount of collateral to transfer.
*/
function transferFromAmoVaultToHoldingVault(
address amoVault,
address token,
uint256 amount
) public onlyRole(AMO_ALLOCATOR_ROLE) {
if (token == address(dusd)) {
revert CannotTransferDUSD();
}
// Update allocation
// A note on why we modify AMO allocation when we withdraw collateral:
// 1. When dUSD AMO enters the AMO vault, the dUSD is initially unbacked
// 2. Over time the AMO vault accrues collateral in exchange for distributing dUSD
// 3. We may be able to make better use of that collateral in a different collateral vault
// 4. So we transfer the collateral out of the AMO vault, but at that point the dUSD that
// converted to that collateral is now free-floating and fully backed
// 5. Thus we decrement the AMO allocation to reflect the fact that the dUSD is no longer
// unbacked, but is actually fully backed and circulating
uint256 collateralUsdValue = collateralHolderVault.assetValueFromAmount(
amount,
token
);
uint256 collateralInDusd = usdValueToDusdAmount(collateralUsdValue);
(, uint256 currentAllocation) = _amoVaults.tryGet(amoVault);
_amoVaults.set(amoVault, currentAllocation - collateralInDusd);
totalAllocated -= collateralInDusd;
// Transfer the collateral
AmoVault(amoVault).withdrawTo(
address(collateralHolderVault),
amount,
token
);
}
/**
* @notice Transfers collateral from the holding vault to an AMO vault.
* @param amoVault The address of the AMO vault.
* @param token The address of the collateral token to transfer.
* @param amount The amount of collateral to transfer.
*/
function transferFromHoldingVaultToAmoVault(
address amoVault,
address token,
uint256 amount
) public onlyRole(AMO_ALLOCATOR_ROLE) {
if (token == address(dusd)) {
revert CannotTransferDUSD();
}
if (!_amoVaults.contains(amoVault)) {
revert InactiveAmoVault(amoVault);
}
// Update allocation
// A note on why we modify AMO allocation when we deposit collateral:
// 1. When we deposit collateral, it can be used to buy back dUSD
// 2. When we buy back dUSD, the dUSD is now unbacked (a redemption)
// 3. Thus any collateral deposited to an AMO vault can create unbacked dUSD,
// which means the AMO allocation for that vault must be increased to reflect this
uint256 collateralUsdValue = collateralHolderVault.assetValueFromAmount(
amount,
token
);
uint256 collateralInDusd = usdValueToDusdAmount(collateralUsdValue);
(, uint256 currentAllocation) = _amoVaults.tryGet(amoVault);
_amoVaults.set(amoVault, currentAllocation + collateralInDusd);
totalAllocated += collateralInDusd;
// Transfer the collateral
collateralHolderVault.withdrawTo(amoVault, amount, token);
}
/* Profit Management */
/**
* @notice Returns the available profit for a specific vault in USD.
* @param vaultAddress The address of the AMO vault to check.
* @return The available profit in USD (can be negative).
*/
function availableVaultProfitsInUsd(
address vaultAddress
) public view returns (int256) {
uint256 totalVaultValueInUsd = AmoVault(vaultAddress).totalValue();
uint256 allocatedDusd = amoVaultAllocation(vaultAddress);
uint256 allocatedValueInUsd = dusdAmountToUsdValue(allocatedDusd);
return int256(totalVaultValueInUsd) - int256(allocatedValueInUsd);
}
/**
* @notice Withdraws profits from an AMO vault to a recipient.
* @param amoVault The AMO vault from which to withdraw profits.
* @param recipient The address to receive the profits.
* @param takeProfitToken The collateral token to withdraw.
* @param takeProfitAmount The amount of collateral to withdraw.
* @return takeProfitValueInUsd The value of the withdrawn profits in USD.
*/
function withdrawProfits(
AmoVault amoVault,
address recipient,
address takeProfitToken,
uint256 takeProfitAmount
)
public
onlyRole(FEE_COLLECTOR_ROLE)
returns (uint256 takeProfitValueInUsd)
{
// Leave open the possibility of withdrawing profits from inactive vaults
takeProfitValueInUsd = amoVault.assetValueFromAmount(
takeProfitAmount,
takeProfitToken
);
int256 _availableProfitInUsd = availableVaultProfitsInUsd(
address(amoVault)
);
// Make sure we are withdrawing less than the available profit
if (
_availableProfitInUsd <= 0 ||
int256(takeProfitValueInUsd) > _availableProfitInUsd
) {
revert InsufficientProfits(
takeProfitValueInUsd,
_availableProfitInUsd
);
}
// Withdraw profits from the vault
amoVault.withdrawTo(recipient, takeProfitAmount, takeProfitToken);
emit ProfitsWithdrawn(address(amoVault), takeProfitValueInUsd);
return takeProfitValueInUsd;
}
/**
* @notice Returns the total available profit across all AMO vaults in USD.
* @return The total available profit in USD.
*/
function availableProfitInUsd() public view returns (int256) {
int256 totalProfit = 0;
// Iterate through all AMO vaults
for (uint256 i = 0; i < _amoVaults.length(); i++) {
(address vaultAddress, ) = _amoVaults.at(i);
if (isAmoActive(vaultAddress)) {
totalProfit += availableVaultProfitsInUsd(vaultAddress);
}
}
return totalProfit;
}
/* Utility */
/**
* @notice Converts a USD value to an equivalent amount of dUSD tokens.
* @param usdValue The amount of USD value to convert.
* @return The equivalent amount of dUSD tokens.
*/
function usdValueToDusdAmount(
uint256 usdValue
) public view returns (uint256) {
uint8 dusdDecimals = dusd.decimals();
return (usdValue * (10 ** dusdDecimals)) / USD_UNIT;
}
/**
* @notice Converts an amount of dUSD tokens to an equivalent USD value.
* @param dusdAmount The amount of dUSD tokens to convert.
* @return The equivalent amount of USD value.
*/
function dusdAmountToUsdValue(
uint256 dusdAmount
) public view returns (uint256) {
uint8 dusdDecimals = dusd.decimals();
return
(dusdAmount * oracle.getAssetPrice(address(dusd))) /
(10 ** dusdDecimals);
}
/* Admin */
/**
* @notice Sets the collateral vault address
* @param _collateralVault The address of the new collateral vault
*/
function setCollateralVault(
address _collateralVault
) external onlyRole(DEFAULT_ADMIN_ROLE) {
collateralHolderVault = CollateralVault(_collateralVault);
}
}
/**
* @title ICollateralSum
* @dev Interface for contracts that can provide total collateral value.
*/
interface ICollateralSum {
/**
* @notice Returns the total collateral value of the implementing contract.
* @return The total collateral value in base value (e.g., USD).
*/
function totalCollateralValue() external view returns (uint256);
}// SPDX-License-Identifier: MIT
/* ———————————————————————————————————————————————————————————————————————————————— *
* _____ ______ ______ __ __ __ __ ______ __ __ *
* /\ __-. /\__ _\ /\ == \ /\ \ /\ "-.\ \ /\ \ /\__ _\ /\ \_\ \ *
* \ \ \/\ \ \/_/\ \/ \ \ __< \ \ \ \ \ \-. \ \ \ \ \/_/\ \/ \ \____ \ *
* \ \____- \ \_\ \ \_\ \_\ \ \_\ \ \_\\"\_\ \ \_\ \ \_\ \/\_____\ *
* \/____/ \/_/ \/_/ /_/ \/_/ \/_/ \/_/ \/_/ \/_/ \/_____/ *
* *
* ————————————————————————————————— dtrinity.org ————————————————————————————————— *
* *
* ▲ *
* ▲ ▲ *
* *
* ———————————————————————————————————————————————————————————————————————————————— *
* dTRINITY Protocol: https://github.com/dtrinity *
* ———————————————————————————————————————————————————————————————————————————————— */
pragma solidity ^0.8.20;
import "@openzeppelin/contracts-5/access/AccessControl.sol";
import "src/lending/core/interfaces/IPriceOracleGetter.sol";
/**
* @title OracleAware
* @notice Abstract contract that provides oracle functionality to other contracts
*/
abstract contract OracleAware is AccessControl {
/* Core state */
IPriceOracleGetter public oracle;
uint256 public baseCurrencyUnit;
/* Events */
event OracleSet(address indexed newOracle);
/* Errors */
error IncorrectBaseCurrencyUnit(uint256 baseCurrencyUnit);
/**
* @notice Initializes the contract with an oracle and base currency unit
* @param initialOracle The initial oracle to use for price feeds
* @param _baseCurrencyUnit The base currency unit for price calculations
* @dev Sets up the initial oracle and base currency unit values
*/
constructor(IPriceOracleGetter initialOracle, uint256 _baseCurrencyUnit) {
oracle = initialOracle;
baseCurrencyUnit = _baseCurrencyUnit;
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
}
/**
* @notice Sets the oracle to use for collateral valuation
* @param newOracle The new oracle to use
*/
function setOracle(
IPriceOracleGetter newOracle
) public onlyRole(DEFAULT_ADMIN_ROLE) {
if (newOracle.BASE_CURRENCY_UNIT() != baseCurrencyUnit) {
revert IncorrectBaseCurrencyUnit(baseCurrencyUnit);
}
oracle = newOracle;
emit OracleSet(address(newOracle));
}
/**
* @notice Updates the base currency unit used for price calculations
* @param _newBaseCurrencyUnit The new base currency unit to set
* @dev Only used if the oracle's base currency unit changes
*/
function setBaseCurrencyUnit(
uint256 _newBaseCurrencyUnit
) public onlyRole(DEFAULT_ADMIN_ROLE) {
baseCurrencyUnit = _newBaseCurrencyUnit;
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
library BasisPointConstants {
uint256 public constant BASIS_POINTS = 10000;
uint256 public constant MAX_BASIS_POINTS = 10000;
uint256 public constant ONE_PERCENT_BPS = 100;
uint256 public constant ONE_HUNDRED_PERCENT_BPS = 10000;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/IERC165.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20Upgradeable {
/**
* @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 amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` 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 amount) 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 `amount` 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 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `from` to `to` using the
* allowance mechanism. `amount` 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 amount) external returns (bool);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)
pragma solidity ^0.8.0;
import "../IERC20Upgradeable.sol";
/**
* @dev Interface for the optional metadata functions from the ERC20 standard.
*
* _Available since v4.1._
*/
interface IERC20MetadataUpgradeable is IERC20Upgradeable {
/**
* @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);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
pragma solidity ^0.8.0;
import "../proxy/utils/Initializable.sol";
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract ContextUpgradeable is Initializable {
function __Context_init() internal onlyInitializing {
}
function __Context_init_unchained() internal onlyInitializing {
}
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[50] private __gap;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)
pragma solidity ^0.8.0;
/**
* @dev External interface of AccessControl declared to support ERC165 detection.
*/
interface IAccessControlUpgradeable {
/**
* @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
*
* `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
* {RoleAdminChanged} not being emitted signaling this.
*
* _Available since v3.1._
*/
event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);
/**
* @dev Emitted when `account` is granted `role`.
*
* `sender` is the account that originated the contract call, an admin role
* bearer except when using {AccessControl-_setupRole}.
*/
event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Emitted when `account` is revoked `role`.
*
* `sender` is the account that originated the contract call:
* - if using `revokeRole`, it is the admin role bearer
* - if using `renounceRole`, it is the role bearer (i.e. `account`)
*/
event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) external view returns (bool);
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
*
* To change a role's admin, use {AccessControl-_setRoleAdmin}.
*/
function getRoleAdmin(bytes32 role) external view returns (bytes32);
/**
* @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.
*/
function grantRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function revokeRole(bytes32 role, address account) external;
/**
* @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 granted `role`, emits a {RoleRevoked}
* event.
*
* Requirements:
*
* - the caller must be `account`.
*/
function renounceRole(bytes32 role, address account) external;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Strings.sol)
pragma solidity ^0.8.0;
import "./math/MathUpgradeable.sol";
import "./math/SignedMathUpgradeable.sol";
/**
* @dev String operations.
*/
library StringsUpgradeable {
bytes16 private constant _SYMBOLS = "0123456789abcdef";
uint8 private constant _ADDRESS_LENGTH = 20;
/**
* @dev Converts a `uint256` to its ASCII `string` decimal representation.
*/
function toString(uint256 value) internal pure returns (string memory) {
unchecked {
uint256 length = MathUpgradeable.log10(value) + 1;
string memory buffer = new string(length);
uint256 ptr;
/// @solidity memory-safe-assembly
assembly {
ptr := add(buffer, add(32, length))
}
while (true) {
ptr--;
/// @solidity memory-safe-assembly
assembly {
mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
}
value /= 10;
if (value == 0) break;
}
return buffer;
}
}
/**
* @dev Converts a `int256` to its ASCII `string` decimal representation.
*/
function toString(int256 value) internal pure returns (string memory) {
return string(abi.encodePacked(value < 0 ? "-" : "", toString(SignedMathUpgradeable.abs(value))));
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
*/
function toHexString(uint256 value) internal pure returns (string memory) {
unchecked {
return toHexString(value, MathUpgradeable.log256(value) + 1);
}
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
*/
function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
bytes memory buffer = new bytes(2 * length + 2);
buffer[0] = "0";
buffer[1] = "x";
for (uint256 i = 2 * length + 1; i > 1; --i) {
buffer[i] = _SYMBOLS[value & 0xf];
value >>= 4;
}
require(value == 0, "Strings: hex length insufficient");
return string(buffer);
}
/**
* @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
*/
function toHexString(address addr) internal pure returns (string memory) {
return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
}
/**
* @dev Returns true if the two strings are equal.
*/
function equal(string memory a, string memory b) internal pure returns (bool) {
return keccak256(bytes(a)) == keccak256(bytes(b));
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)
pragma solidity ^0.8.0;
import "./IERC165Upgradeable.sol";
import "../../proxy/utils/Initializable.sol";
/**
* @dev Implementation of the {IERC165} interface.
*
* Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
* for the additional interface id that will be supported. For example:
*
* ```solidity
* function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
* return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
* }
* ```
*
* Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
*/
abstract contract ERC165Upgradeable is Initializable, IERC165Upgradeable {
function __ERC165_init() internal onlyInitializing {
}
function __ERC165_init_unchained() internal onlyInitializing {
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IERC165Upgradeable).interfaceId;
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[50] private __gap;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/IERC20Permit.sol)
pragma solidity ^0.8.0;
/**
* @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.
*/
interface IERC20PermitUpgradeable {
/**
* @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].
*/
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);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/cryptography/ECDSA.sol)
pragma solidity ^0.8.0;
import "../StringsUpgradeable.sol";
/**
* @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
*
* These functions can be used to verify that a message was signed by the holder
* of the private keys of a given address.
*/
library ECDSAUpgradeable {
enum RecoverError {
NoError,
InvalidSignature,
InvalidSignatureLength,
InvalidSignatureS,
InvalidSignatureV // Deprecated in v4.8
}
function _throwError(RecoverError error) private pure {
if (error == RecoverError.NoError) {
return; // no error: do nothing
} else if (error == RecoverError.InvalidSignature) {
revert("ECDSA: invalid signature");
} else if (error == RecoverError.InvalidSignatureLength) {
revert("ECDSA: invalid signature length");
} else if (error == RecoverError.InvalidSignatureS) {
revert("ECDSA: invalid signature 's' value");
}
}
/**
* @dev Returns the address that signed a hashed message (`hash`) with
* `signature` or error string. This address can then be used for verification purposes.
*
* The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {toEthSignedMessageHash} on it.
*
* Documentation for signature generation:
* - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
* - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
*
* _Available since v4.3._
*/
function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {
if (signature.length == 65) {
bytes32 r;
bytes32 s;
uint8 v;
// ecrecover takes the signature parameters, and the only way to get them
// currently is to use assembly.
/// @solidity memory-safe-assembly
assembly {
r := mload(add(signature, 0x20))
s := mload(add(signature, 0x40))
v := byte(0, mload(add(signature, 0x60)))
}
return tryRecover(hash, v, r, s);
} else {
return (address(0), RecoverError.InvalidSignatureLength);
}
}
/**
* @dev Returns the address that signed a hashed message (`hash`) with
* `signature`. This address can then be used for verification purposes.
*
* The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {toEthSignedMessageHash} on it.
*/
function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
(address recovered, RecoverError error) = tryRecover(hash, signature);
_throwError(error);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
*
* See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
*
* _Available since v4.3._
*/
function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address, RecoverError) {
bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
uint8 v = uint8((uint256(vs) >> 255) + 27);
return tryRecover(hash, v, r, s);
}
/**
* @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
*
* _Available since v4.2._
*/
function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) {
(address recovered, RecoverError error) = tryRecover(hash, r, vs);
_throwError(error);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `v`,
* `r` and `s` signature fields separately.
*
* _Available since v4.3._
*/
function tryRecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address, RecoverError) {
// EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
// unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
// the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
// signatures from current libraries generate a unique signature with an s-value in the lower half order.
//
// If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
// with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
// vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
// these malleable signatures as well.
if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
return (address(0), RecoverError.InvalidSignatureS);
}
// If the signature is valid (and not malleable), return the signer address
address signer = ecrecover(hash, v, r, s);
if (signer == address(0)) {
return (address(0), RecoverError.InvalidSignature);
}
return (signer, RecoverError.NoError);
}
/**
* @dev Overload of {ECDSA-recover} that receives the `v`,
* `r` and `s` signature fields separately.
*/
function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) {
(address recovered, RecoverError error) = tryRecover(hash, v, r, s);
_throwError(error);
return recovered;
}
/**
* @dev Returns an Ethereum Signed Message, created from a `hash`. This
* produces hash corresponding to the one signed with the
* https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
* JSON-RPC method as part of EIP-191.
*
* See {recover}.
*/
function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32 message) {
// 32 is the length in bytes of hash,
// enforced by the type signature above
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, "\x19Ethereum Signed Message:\n32")
mstore(0x1c, hash)
message := keccak256(0x00, 0x3c)
}
}
/**
* @dev Returns an Ethereum Signed Message, created from `s`. This
* produces hash corresponding to the one signed with the
* https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
* JSON-RPC method as part of EIP-191.
*
* See {recover}.
*/
function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) {
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n", StringsUpgradeable.toString(s.length), s));
}
/**
* @dev Returns an Ethereum Signed Typed Data, created from a
* `domainSeparator` and a `structHash`. This produces hash corresponding
* to the one signed with the
* https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]
* JSON-RPC method as part of EIP-712.
*
* See {recover}.
*/
function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 data) {
/// @solidity memory-safe-assembly
assembly {
let ptr := mload(0x40)
mstore(ptr, "\x19\x01")
mstore(add(ptr, 0x02), domainSeparator)
mstore(add(ptr, 0x22), structHash)
data := keccak256(ptr, 0x42)
}
}
/**
* @dev Returns an Ethereum Signed Data with intended validator, created from a
* `validator` and `data` according to the version 0 of EIP-191.
*
* See {recover}.
*/
function toDataWithIntendedValidatorHash(address validator, bytes memory data) internal pure returns (bytes32) {
return keccak256(abi.encodePacked("\x19\x00", validator, data));
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/cryptography/EIP712.sol)
pragma solidity ^0.8.8;
import "./ECDSAUpgradeable.sol";
import "../../interfaces/IERC5267Upgradeable.sol";
import "../../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 specified in the EIP is very generic, and such a generic 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 their contracts 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.
*
* _Available since v3.4._
*
* @custom:storage-size 52
*/
abstract contract EIP712Upgradeable is Initializable, IERC5267Upgradeable {
bytes32 private constant _TYPE_HASH =
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
/// @custom:oz-renamed-from _HASHED_NAME
bytes32 private _hashedName;
/// @custom:oz-renamed-from _HASHED_VERSION
bytes32 private _hashedVersion;
string private _name;
string private _version;
/**
* @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 {
_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 ECDSAUpgradeable.toTypedDataHash(_domainSeparatorV4(), structHash);
}
/**
* @dev See {EIP-5267}.
*
* _Available since v4.9._
*/
function eip712Domain()
public
view
virtual
override
returns (
bytes1 fields,
string memory name,
string memory version,
uint256 chainId,
address verifyingContract,
bytes32 salt,
uint256[] memory extensions
)
{
// 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 virtual view returns (string memory) {
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 virtual view returns (string memory) {
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) {
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) {
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("");
}
}
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[48] private __gap;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Counters.sol)
pragma solidity ^0.8.0;
/**
* @title Counters
* @author Matt Condon (@shrugs)
* @dev Provides counters that can only be incremented, decremented or reset. This can be used e.g. to track the number
* of elements in a mapping, issuing ERC721 ids, or counting request ids.
*
* Include with `using Counters for Counters.Counter;`
*/
library CountersUpgradeable {
struct Counter {
// This variable should never be directly accessed by users of the library: interactions must be restricted to
// the library's function. As of Solidity v0.5.2, this cannot be enforced, though there is a proposal to add
// this feature: see https://github.com/ethereum/solidity/issues/4637
uint256 _value; // default: 0
}
function current(Counter storage counter) internal view returns (uint256) {
return counter._value;
}
function increment(Counter storage counter) internal {
unchecked {
counter._value += 1;
}
}
function decrement(Counter storage counter) internal {
uint256 value = counter._value;
require(value > 0, "Counter: decrement overflow");
unchecked {
counter._value = value - 1;
}
}
function reset(Counter storage counter) internal {
counter._value = 0;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC3156FlashBorrower.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC3156 FlashBorrower, as defined in
* https://eips.ethereum.org/EIPS/eip-3156[ERC-3156].
*
* _Available since v4.1._
*/
interface IERC3156FlashBorrowerUpgradeable {
/**
* @dev Receive a flash loan.
* @param initiator The initiator of the loan.
* @param token The loan currency.
* @param amount The amount of tokens lent.
* @param fee The additional amount of tokens to repay.
* @param data Arbitrary data structure, intended to contain user-defined parameters.
* @return The keccak256 hash of "ERC3156FlashBorrower.onFlashLoan"
*/
function onFlashLoan(
address initiator,
address token,
uint256 amount,
uint256 fee,
bytes calldata data
) external returns (bytes32);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (interfaces/IERC3156FlashLender.sol)
pragma solidity ^0.8.0;
import "./IERC3156FlashBorrowerUpgradeable.sol";
/**
* @dev Interface of the ERC3156 FlashLender, as defined in
* https://eips.ethereum.org/EIPS/eip-3156[ERC-3156].
*
* _Available since v4.1._
*/
interface IERC3156FlashLenderUpgradeable {
/**
* @dev The amount of currency available to be lended.
* @param token The loan currency.
* @return The amount of `token` that can be borrowed.
*/
function maxFlashLoan(address token) external view returns (uint256);
/**
* @dev The fee to be charged for a given loan.
* @param token The loan currency.
* @param amount The amount of tokens lent.
* @return The amount of `token` to be charged for the loan, on top of the returned principal.
*/
function flashFee(address token, uint256 amount) external view returns (uint256);
/**
* @dev Initiate a flash loan.
* @param receiver The receiver of the tokens in the loan, and the receiver of the callback.
* @param token The loan currency.
* @param amount The amount of tokens lent.
* @param data Arbitrary data structure, intended to contain user-defined parameters.
*/
function flashLoan(
IERC3156FlashBorrowerUpgradeable receiver,
address token,
uint256 amount,
bytes calldata data
) external returns (bool);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)
pragma solidity ^0.8.1;
/**
* @dev Collection of functions related to the address type
*/
library AddressUpgradeable {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
*
* Furthermore, `isContract` will also return true if the target contract within
* the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
* which only has an effect at the end of a transaction.
* ====
*
* [IMPORTANT]
* ====
* You shouldn't rely on `isContract` to protect against flash loan attacks!
*
* Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
* like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
* constructor.
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0
// for contracts in construction, since the code is only stored at the end
// of the constructor execution.
return account.code.length > 0;
}
/**
* @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.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @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, it is bubbled up by this
* function (like regular Solidity function calls).
*
* 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.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @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`.
*
* _Available since v3.1._
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
* the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
*
* _Available since v4.8._
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata,
string memory errorMessage
) internal view returns (bytes memory) {
if (success) {
if (returndata.length == 0) {
// only check isContract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
require(isContract(target), "Address: call to non-contract");
}
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
/**
* @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason or using the provided one.
*
* _Available since v4.3._
*/
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
function _revert(bytes memory returndata, string memory errorMessage) 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
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/structs/EnumerableSet.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.
pragma solidity ^0.8.20;
/**
* @dev Library for managing
* https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
* types.
*
* Sets have the following properties:
*
* - Elements are added, removed, and checked for existence in constant time
* (O(1)).
* - Elements are enumerated in O(n). No guarantees are made on the ordering.
*
* ```solidity
* contract Example {
* // Add the library methods
* using EnumerableSet for EnumerableSet.AddressSet;
*
* // Declare a set state variable
* EnumerableSet.AddressSet private mySet;
* }
* ```
*
* As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
* and `uint256` (`UintSet`) are supported.
*
* [WARNING]
* ====
* Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
* unusable.
* See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
*
* In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
* array of EnumerableSet.
* ====
*/
library EnumerableSet {
// To implement this library for multiple types with as little code
// repetition as possible, we write it in terms of a generic Set type with
// bytes32 values.
// The Set implementation uses private functions, and user-facing
// implementations (such as AddressSet) are just wrappers around the
// underlying Set.
// This means that we can only create new EnumerableSets for types that fit
// in bytes32.
struct Set {
// Storage of set values
bytes32[] _values;
// Position is the index of the value in the `values` array plus 1.
// Position 0 is used to mean a value is not in the set.
mapping(bytes32 value => uint256) _positions;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function _add(Set storage set, bytes32 value) private returns (bool) {
if (!_contains(set, value)) {
set._values.push(value);
// The value is stored at length-1, but we add 1 to all indexes
// and use 0 as a sentinel value
set._positions[value] = set._values.length;
return true;
} else {
return false;
}
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function _remove(Set storage set, bytes32 value) private returns (bool) {
// We cache the value's position to prevent multiple reads from the same storage slot
uint256 position = set._positions[value];
if (position != 0) {
// Equivalent to contains(set, value)
// To delete an element from the _values array in O(1), we swap the element to delete with the last one in
// the array, and then remove the last element (sometimes called as 'swap and pop').
// This modifies the order of the array, as noted in {at}.
uint256 valueIndex = position - 1;
uint256 lastIndex = set._values.length - 1;
if (valueIndex != lastIndex) {
bytes32 lastValue = set._values[lastIndex];
// Move the lastValue to the index where the value to delete is
set._values[valueIndex] = lastValue;
// Update the tracked position of the lastValue (that was just moved)
set._positions[lastValue] = position;
}
// Delete the slot where the moved value was stored
set._values.pop();
// Delete the tracked position for the deleted slot
delete set._positions[value];
return true;
} else {
return false;
}
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function _contains(Set storage set, bytes32 value) private view returns (bool) {
return set._positions[value] != 0;
}
/**
* @dev Returns the number of values on the set. O(1).
*/
function _length(Set storage set) private view returns (uint256) {
return set._values.length;
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function _at(Set storage set, uint256 index) private view returns (bytes32) {
return set._values[index];
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function _values(Set storage set) private view returns (bytes32[] memory) {
return set._values;
}
// Bytes32Set
struct Bytes32Set {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _add(set._inner, value);
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _remove(set._inner, value);
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
return _contains(set._inner, value);
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(Bytes32Set storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
return _at(set._inner, index);
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
bytes32[] memory store = _values(set._inner);
bytes32[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// AddressSet
struct AddressSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(AddressSet storage set, address value) internal returns (bool) {
return _add(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(AddressSet storage set, address value) internal returns (bool) {
return _remove(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(AddressSet storage set, address value) internal view returns (bool) {
return _contains(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(AddressSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(AddressSet storage set, uint256 index) internal view returns (address) {
return address(uint160(uint256(_at(set._inner, index))));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(AddressSet storage set) internal view returns (address[] memory) {
bytes32[] memory store = _values(set._inner);
address[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// UintSet
struct UintSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(UintSet storage set, uint256 value) internal returns (bool) {
return _add(set._inner, bytes32(value));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(UintSet storage set, uint256 value) internal returns (bool) {
return _remove(set._inner, bytes32(value));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(UintSet storage set, uint256 value) internal view returns (bool) {
return _contains(set._inner, bytes32(value));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(UintSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(UintSet storage set, uint256 index) internal view returns (uint256) {
return uint256(_at(set._inner, index));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(UintSet storage set) internal view returns (uint256[] memory) {
bytes32[] memory store = _values(set._inner);
uint256[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
}// SPDX-License-Identifier: MIT
/* ———————————————————————————————————————————————————————————————————————————————— *
* _____ ______ ______ __ __ __ __ ______ __ __ *
* /\ __-. /\__ _\ /\ == \ /\ \ /\ "-.\ \ /\ \ /\__ _\ /\ \_\ \ *
* \ \ \/\ \ \/_/\ \/ \ \ __< \ \ \ \ \ \-. \ \ \ \ \/_/\ \/ \ \____ \ *
* \ \____- \ \_\ \ \_\ \_\ \ \_\ \ \_\\"\_\ \ \_\ \ \_\ \/\_____\ *
* \/____/ \/_/ \/_/ /_/ \/_/ \/_/ \/_/ \/_/ \/_/ \/_____/ *
* *
* ————————————————————————————————— dtrinity.org ————————————————————————————————— *
* *
* ▲ *
* ▲ ▲ *
* *
* ———————————————————————————————————————————————————————————————————————————————— *
* dTRINITY Protocol: https://github.com/dtrinity *
* ———————————————————————————————————————————————————————————————————————————————— */
pragma solidity ^0.8.0;
library Constants {
// Shared definitions of how we represent percentages and basis points
uint16 public constant ONE_BPS = 100; // 1 basis point with 2 decimals
uint32 public constant ONE_PERCENT_BPS = ONE_BPS * 100;
uint32 public constant ONE_HUNDRED_PERCENT_BPS = ONE_PERCENT_BPS * 100;
uint32 public constant ORACLE_BASE_CURRENCY_UNIT = 1e8;
}// SPDX-License-Identifier: AGPL-3.0
/* ———————————————————————————————————————————————————————————————————————————————— *
* _____ ______ ______ __ __ __ __ ______ __ __ *
* /\ __-. /\__ _\ /\ == \ /\ \ /\ "-.\ \ /\ \ /\__ _\ /\ \_\ \ *
* \ \ \/\ \ \/_/\ \/ \ \ __< \ \ \ \ \ \-. \ \ \ \ \/_/\ \/ \ \____ \ *
* \ \____- \ \_\ \ \_\ \_\ \ \_\ \ \_\\"\_\ \ \_\ \ \_\ \/\_____\ *
* \/____/ \/_/ \/_/ /_/ \/_/ \/_/ \/_/ \/_/ \/_/ \/_____/ *
* *
* ————————————————————————————————— dtrinity.org ————————————————————————————————— *
* *
* ▲ *
* ▲ ▲ *
* *
* ———————————————————————————————————————————————————————————————————————————————— *
* dTRINITY Protocol: https://github.com/dtrinity *
* ———————————————————————————————————————————————————————————————————————————————— */
pragma solidity ^0.8.0;
/**
* @title IPriceOracleGetter
* @author Aave
* @notice Interface for the Aave price oracle.
*/
interface IPriceOracleGetter {
/**
* @notice Returns the base currency address
* @dev Address 0x0 is reserved for USD as base currency.
* @return Returns the base currency address.
*/
function BASE_CURRENCY() external view returns (address);
/**
* @notice Returns the base currency unit
* @dev 1 ether for ETH, 1e8 for USD.
* @return Returns the base currency unit.
*/
function BASE_CURRENCY_UNIT() external view returns (uint256);
/**
* @notice Returns the asset price in the base currency
* @param asset The address of the asset
* @return The price of the asset
*/
function getAssetPrice(address asset) external view returns (uint256);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/structs/EnumerableMap.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableMap.js.
pragma solidity ^0.8.20;
import {EnumerableSet} from "./EnumerableSet.sol";
/**
* @dev Library for managing an enumerable variant of Solidity's
* https://solidity.readthedocs.io/en/latest/types.html#mapping-types[`mapping`]
* type.
*
* Maps have the following properties:
*
* - Entries are added, removed, and checked for existence in constant time
* (O(1)).
* - Entries are enumerated in O(n). No guarantees are made on the ordering.
*
* ```solidity
* contract Example {
* // Add the library methods
* using EnumerableMap for EnumerableMap.UintToAddressMap;
*
* // Declare a set state variable
* EnumerableMap.UintToAddressMap private myMap;
* }
* ```
*
* The following map types are supported:
*
* - `uint256 -> address` (`UintToAddressMap`) since v3.0.0
* - `address -> uint256` (`AddressToUintMap`) since v4.6.0
* - `bytes32 -> bytes32` (`Bytes32ToBytes32Map`) since v4.6.0
* - `uint256 -> uint256` (`UintToUintMap`) since v4.7.0
* - `bytes32 -> uint256` (`Bytes32ToUintMap`) since v4.7.0
*
* [WARNING]
* ====
* Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
* unusable.
* See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
*
* In order to clean an EnumerableMap, you can either remove all elements one by one or create a fresh instance using an
* array of EnumerableMap.
* ====
*/
library EnumerableMap {
using EnumerableSet for EnumerableSet.Bytes32Set;
// To implement this library for multiple types with as little code repetition as possible, we write it in
// terms of a generic Map type with bytes32 keys and values. The Map implementation uses private functions,
// and user-facing implementations such as `UintToAddressMap` are just wrappers around the underlying Map.
// This means that we can only create new EnumerableMaps for types that fit in bytes32.
/**
* @dev Query for a nonexistent map key.
*/
error EnumerableMapNonexistentKey(bytes32 key);
struct Bytes32ToBytes32Map {
// Storage of keys
EnumerableSet.Bytes32Set _keys;
mapping(bytes32 key => bytes32) _values;
}
/**
* @dev Adds a key-value pair to a map, or updates the value for an existing
* key. O(1).
*
* Returns true if the key was added to the map, that is if it was not
* already present.
*/
function set(Bytes32ToBytes32Map storage map, bytes32 key, bytes32 value) internal returns (bool) {
map._values[key] = value;
return map._keys.add(key);
}
/**
* @dev Removes a key-value pair from a map. O(1).
*
* Returns true if the key was removed from the map, that is if it was present.
*/
function remove(Bytes32ToBytes32Map storage map, bytes32 key) internal returns (bool) {
delete map._values[key];
return map._keys.remove(key);
}
/**
* @dev Returns true if the key is in the map. O(1).
*/
function contains(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bool) {
return map._keys.contains(key);
}
/**
* @dev Returns the number of key-value pairs in the map. O(1).
*/
function length(Bytes32ToBytes32Map storage map) internal view returns (uint256) {
return map._keys.length();
}
/**
* @dev Returns the key-value pair stored at position `index` in the map. O(1).
*
* Note that there are no guarantees on the ordering of entries inside the
* array, and it may change when more entries are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(Bytes32ToBytes32Map storage map, uint256 index) internal view returns (bytes32, bytes32) {
bytes32 key = map._keys.at(index);
return (key, map._values[key]);
}
/**
* @dev Tries to returns the value associated with `key`. O(1).
* Does not revert if `key` is not in the map.
*/
function tryGet(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bool, bytes32) {
bytes32 value = map._values[key];
if (value == bytes32(0)) {
return (contains(map, key), bytes32(0));
} else {
return (true, value);
}
}
/**
* @dev Returns the value associated with `key`. O(1).
*
* Requirements:
*
* - `key` must be in the map.
*/
function get(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bytes32) {
bytes32 value = map._values[key];
if (value == 0 && !contains(map, key)) {
revert EnumerableMapNonexistentKey(key);
}
return value;
}
/**
* @dev Return the an array containing all the keys
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function keys(Bytes32ToBytes32Map storage map) internal view returns (bytes32[] memory) {
return map._keys.values();
}
// UintToUintMap
struct UintToUintMap {
Bytes32ToBytes32Map _inner;
}
/**
* @dev Adds a key-value pair to a map, or updates the value for an existing
* key. O(1).
*
* Returns true if the key was added to the map, that is if it was not
* already present.
*/
function set(UintToUintMap storage map, uint256 key, uint256 value) internal returns (bool) {
return set(map._inner, bytes32(key), bytes32(value));
}
/**
* @dev Removes a value from a map. O(1).
*
* Returns true if the key was removed from the map, that is if it was present.
*/
function remove(UintToUintMap storage map, uint256 key) internal returns (bool) {
return remove(map._inner, bytes32(key));
}
/**
* @dev Returns true if the key is in the map. O(1).
*/
function contains(UintToUintMap storage map, uint256 key) internal view returns (bool) {
return contains(map._inner, bytes32(key));
}
/**
* @dev Returns the number of elements in the map. O(1).
*/
function length(UintToUintMap storage map) internal view returns (uint256) {
return length(map._inner);
}
/**
* @dev Returns the element stored at position `index` in the map. O(1).
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(UintToUintMap storage map, uint256 index) internal view returns (uint256, uint256) {
(bytes32 key, bytes32 value) = at(map._inner, index);
return (uint256(key), uint256(value));
}
/**
* @dev Tries to returns the value associated with `key`. O(1).
* Does not revert if `key` is not in the map.
*/
function tryGet(UintToUintMap storage map, uint256 key) internal view returns (bool, uint256) {
(bool success, bytes32 value) = tryGet(map._inner, bytes32(key));
return (success, uint256(value));
}
/**
* @dev Returns the value associated with `key`. O(1).
*
* Requirements:
*
* - `key` must be in the map.
*/
function get(UintToUintMap storage map, uint256 key) internal view returns (uint256) {
return uint256(get(map._inner, bytes32(key)));
}
/**
* @dev Return the an array containing all the keys
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function keys(UintToUintMap storage map) internal view returns (uint256[] memory) {
bytes32[] memory store = keys(map._inner);
uint256[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// UintToAddressMap
struct UintToAddressMap {
Bytes32ToBytes32Map _inner;
}
/**
* @dev Adds a key-value pair to a map, or updates the value for an existing
* key. O(1).
*
* Returns true if the key was added to the map, that is if it was not
* already present.
*/
function set(UintToAddressMap storage map, uint256 key, address value) internal returns (bool) {
return set(map._inner, bytes32(key), bytes32(uint256(uint160(value))));
}
/**
* @dev Removes a value from a map. O(1).
*
* Returns true if the key was removed from the map, that is if it was present.
*/
function remove(UintToAddressMap storage map, uint256 key) internal returns (bool) {
return remove(map._inner, bytes32(key));
}
/**
* @dev Returns true if the key is in the map. O(1).
*/
function contains(UintToAddressMap storage map, uint256 key) internal view returns (bool) {
return contains(map._inner, bytes32(key));
}
/**
* @dev Returns the number of elements in the map. O(1).
*/
function length(UintToAddressMap storage map) internal view returns (uint256) {
return length(map._inner);
}
/**
* @dev Returns the element stored at position `index` in the map. O(1).
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(UintToAddressMap storage map, uint256 index) internal view returns (uint256, address) {
(bytes32 key, bytes32 value) = at(map._inner, index);
return (uint256(key), address(uint160(uint256(value))));
}
/**
* @dev Tries to returns the value associated with `key`. O(1).
* Does not revert if `key` is not in the map.
*/
function tryGet(UintToAddressMap storage map, uint256 key) internal view returns (bool, address) {
(bool success, bytes32 value) = tryGet(map._inner, bytes32(key));
return (success, address(uint160(uint256(value))));
}
/**
* @dev Returns the value associated with `key`. O(1).
*
* Requirements:
*
* - `key` must be in the map.
*/
function get(UintToAddressMap storage map, uint256 key) internal view returns (address) {
return address(uint160(uint256(get(map._inner, bytes32(key)))));
}
/**
* @dev Return the an array containing all the keys
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function keys(UintToAddressMap storage map) internal view returns (uint256[] memory) {
bytes32[] memory store = keys(map._inner);
uint256[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// AddressToUintMap
struct AddressToUintMap {
Bytes32ToBytes32Map _inner;
}
/**
* @dev Adds a key-value pair to a map, or updates the value for an existing
* key. O(1).
*
* Returns true if the key was added to the map, that is if it was not
* already present.
*/
function set(AddressToUintMap storage map, address key, uint256 value) internal returns (bool) {
return set(map._inner, bytes32(uint256(uint160(key))), bytes32(value));
}
/**
* @dev Removes a value from a map. O(1).
*
* Returns true if the key was removed from the map, that is if it was present.
*/
function remove(AddressToUintMap storage map, address key) internal returns (bool) {
return remove(map._inner, bytes32(uint256(uint160(key))));
}
/**
* @dev Returns true if the key is in the map. O(1).
*/
function contains(AddressToUintMap storage map, address key) internal view returns (bool) {
return contains(map._inner, bytes32(uint256(uint160(key))));
}
/**
* @dev Returns the number of elements in the map. O(1).
*/
function length(AddressToUintMap storage map) internal view returns (uint256) {
return length(map._inner);
}
/**
* @dev Returns the element stored at position `index` in the map. O(1).
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(AddressToUintMap storage map, uint256 index) internal view returns (address, uint256) {
(bytes32 key, bytes32 value) = at(map._inner, index);
return (address(uint160(uint256(key))), uint256(value));
}
/**
* @dev Tries to returns the value associated with `key`. O(1).
* Does not revert if `key` is not in the map.
*/
function tryGet(AddressToUintMap storage map, address key) internal view returns (bool, uint256) {
(bool success, bytes32 value) = tryGet(map._inner, bytes32(uint256(uint160(key))));
return (success, uint256(value));
}
/**
* @dev Returns the value associated with `key`. O(1).
*
* Requirements:
*
* - `key` must be in the map.
*/
function get(AddressToUintMap storage map, address key) internal view returns (uint256) {
return uint256(get(map._inner, bytes32(uint256(uint160(key)))));
}
/**
* @dev Return the an array containing all the keys
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function keys(AddressToUintMap storage map) internal view returns (address[] memory) {
bytes32[] memory store = keys(map._inner);
address[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// Bytes32ToUintMap
struct Bytes32ToUintMap {
Bytes32ToBytes32Map _inner;
}
/**
* @dev Adds a key-value pair to a map, or updates the value for an existing
* key. O(1).
*
* Returns true if the key was added to the map, that is if it was not
* already present.
*/
function set(Bytes32ToUintMap storage map, bytes32 key, uint256 value) internal returns (bool) {
return set(map._inner, key, bytes32(value));
}
/**
* @dev Removes a value from a map. O(1).
*
* Returns true if the key was removed from the map, that is if it was present.
*/
function remove(Bytes32ToUintMap storage map, bytes32 key) internal returns (bool) {
return remove(map._inner, key);
}
/**
* @dev Returns true if the key is in the map. O(1).
*/
function contains(Bytes32ToUintMap storage map, bytes32 key) internal view returns (bool) {
return contains(map._inner, key);
}
/**
* @dev Returns the number of elements in the map. O(1).
*/
function length(Bytes32ToUintMap storage map) internal view returns (uint256) {
return length(map._inner);
}
/**
* @dev Returns the element stored at position `index` in the map. O(1).
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(Bytes32ToUintMap storage map, uint256 index) internal view returns (bytes32, uint256) {
(bytes32 key, bytes32 value) = at(map._inner, index);
return (key, uint256(value));
}
/**
* @dev Tries to returns the value associated with `key`. O(1).
* Does not revert if `key` is not in the map.
*/
function tryGet(Bytes32ToUintMap storage map, bytes32 key) internal view returns (bool, uint256) {
(bool success, bytes32 value) = tryGet(map._inner, key);
return (success, uint256(value));
}
/**
* @dev Returns the value associated with `key`. O(1).
*
* Requirements:
*
* - `key` must be in the map.
*/
function get(Bytes32ToUintMap storage map, bytes32 key) internal view returns (uint256) {
return uint256(get(map._inner, key));
}
/**
* @dev Return the an array containing all the keys
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function keys(Bytes32ToUintMap storage map) internal view returns (bytes32[] memory) {
bytes32[] memory store = keys(map._inner);
bytes32[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
}// SPDX-License-Identifier: Unlicense
/* ———————————————————————————————————————————————————————————————————————————————— *
* _____ ______ ______ __ __ __ __ ______ __ __ *
* /\ __-. /\__ _\ /\ == \ /\ \ /\ "-.\ \ /\ \ /\__ _\ /\ \_\ \ *
* \ \ \/\ \ \/_/\ \/ \ \ __< \ \ \ \ \ \-. \ \ \ \ \/_/\ \/ \ \____ \ *
* \ \____- \ \_\ \ \_\ \_\ \ \_\ \ \_\\"\_\ \ \_\ \ \_\ \/\_____\ *
* \/____/ \/_/ \/_/ /_/ \/_/ \/_/ \/_/ \/_/ \/_/ \/_____/ *
* *
* ————————————————————————————————— dtrinity.org ————————————————————————————————— *
* *
* ▲ *
* ▲ ▲ *
* *
* ———————————————————————————————————————————————————————————————————————————————— *
* dTRINITY Protocol: https://github.com/dtrinity *
* ———————————————————————————————————————————————————————————————————————————————— */
pragma solidity ^0.8.20;
import "@openzeppelin/contracts-5/token/ERC20/IERC20.sol";
interface IERC20Stablecoin is IERC20 {
function mint(address to, uint256 amount) external;
function burn(uint256 amount) external;
function burnFrom(address account, uint256 amount) external;
function decimals() external view returns (uint8);
}// SPDX-License-Identifier: MIT
/* ———————————————————————————————————————————————————————————————————————————————— *
* _____ ______ ______ __ __ __ __ ______ __ __ *
* /\ __-. /\__ _\ /\ == \ /\ \ /\ "-.\ \ /\ \ /\__ _\ /\ \_\ \ *
* \ \ \/\ \ \/_/\ \/ \ \ __< \ \ \ \ \ \-. \ \ \ \ \/_/\ \/ \ \____ \ *
* \ \____- \ \_\ \ \_\ \_\ \ \_\ \ \_\\"\_\ \ \_\ \ \_\ \/\_____\ *
* \/____/ \/_/ \/_/ /_/ \/_/ \/_/ \/_/ \/_/ \/_/ \/_____/ *
* *
* ————————————————————————————————— dtrinity.org ————————————————————————————————— *
* *
* ▲ *
* ▲ ▲ *
* *
* ———————————————————————————————————————————————————————————————————————————————— *
* dTRINITY Protocol: https://github.com/dtrinity *
* ———————————————————————————————————————————————————————————————————————————————— */
pragma solidity ^0.8.20;
import "@openzeppelin/contracts-5/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts-5/token/ERC20/IERC20.sol";
import "src/token/IERC20Stablecoin.sol";
import "src/dusd/AmoManager.sol";
import "@openzeppelin/contracts-5/utils/Address.sol";
import "@openzeppelin/contracts-5/utils/ReentrancyGuard.sol";
import "src/dusd/CollateralVault.sol";
interface IRecoverable {
function recoverERC20(address token, address to, uint256 amount) external;
function recoverETH(address to, uint256 amount) external;
}
/**
* @title AmoVault
* @notice Base contract for AMO (Algorithmic Market Operations) vaults that manage dUSD and collateral assets
*/
abstract contract AmoVault is CollateralVault, IRecoverable, ReentrancyGuard {
using SafeERC20 for IERC20;
using Address for address payable;
/* Core state */
IERC20Stablecoin public immutable dusd;
uint8 public immutable dusdDecimals;
AmoManager public amoManager;
/* Roles */
bytes32 public constant RECOVERER_ROLE = keccak256("RECOVERER_ROLE");
/* Errors */
error CannotRecoverVaultToken(address token);
error InvalidAmoManager();
constructor(
address _dusd,
address _amoManager,
address _admin,
address _collateralWithdrawer,
address _recoverer,
IPriceOracleGetter _oracle
) CollateralVault(_oracle) {
dusd = IERC20Stablecoin(_dusd);
dusdDecimals = IERC20Metadata(_dusd).decimals();
amoManager = AmoManager(_amoManager);
_grantRole(DEFAULT_ADMIN_ROLE, _admin);
grantRole(COLLATERAL_WITHDRAWER_ROLE, _collateralWithdrawer);
grantRole(RECOVERER_ROLE, _recoverer);
approveAmoManager();
}
/**
* @notice Approves the AmoManager to spend dUSD on behalf of this contract
* @dev Only callable by the contract owner or an account with the DEFAULT_ADMIN_ROLE
*/
function approveAmoManager() public onlyRole(DEFAULT_ADMIN_ROLE) {
dusd.approve(address(amoManager), type(uint256).max);
}
/**
* @notice Sets a new AmoManager address
* @param _newAmoManager The address of the new AmoManager
* @dev Only callable by an account with the DEFAULT_ADMIN_ROLE
*/
function setAmoManager(
address _newAmoManager
) external onlyRole(DEFAULT_ADMIN_ROLE) {
if (_newAmoManager == address(0)) revert InvalidAmoManager();
// Reset allowance for old AMO manager
dusd.approve(address(amoManager), 0);
// Set new AMO manager
amoManager = AmoManager(_newAmoManager);
// Approve new AMO manager
approveAmoManager();
}
/* Recovery */
/**
* @notice Recovers ERC20 tokens accidentally sent to the contract
* @param token The address of the token to recover
* @param to The address to send the tokens to
* @param amount The amount of tokens to recover
*/
function recoverERC20(
address token,
address to,
uint256 amount
) external onlyRole(RECOVERER_ROLE) nonReentrant {
if (token == address(dusd) || isCollateralSupported(token)) {
revert CannotRecoverVaultToken(token);
}
IERC20(token).safeTransfer(to, amount);
}
/**
* @notice Recovers ETH accidentally sent to the contract
* @param to The address to send the ETH to
* @param amount The amount of ETH to recover
*/
function recoverETH(
address to,
uint256 amount
) external onlyRole(RECOVERER_ROLE) {
payable(to).sendValue(amount);
}
/* Virtual functions */
/**
* @notice Calculates the total value of non-dUSD collateral assets in the vault
* @return The total value of collateral assets denominated in the base currency (e.g., USD)
* @dev Must be implemented by derived contracts
*/
function totalCollateralValue() public view virtual returns (uint256);
/**
* @notice Calculates the total value of dUSD holdings in the vault
* @return The total value of dUSD holdings denominated in the base currency (e.g., USD)
* @dev Must be implemented by derived contracts
*/
function totalDusdValue() public view virtual returns (uint256);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol)
pragma solidity ^0.8.0;
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library MathUpgradeable {
enum Rounding {
Down, // Toward negative infinity
Up, // Toward infinity
Zero // Toward zero
}
/**
* @dev Returns the largest of two numbers.
*/
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return 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 up instead
* of rounding down.
*/
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b - 1) / b can overflow on addition, so we distribute.
return a == 0 ? 0 : (a - 1) / b + 1;
}
/**
* @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
* @dev 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^256 and mod 2^256 - 1, then use
// use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
// variables such that product = prod1 * 2^256 + prod0.
uint256 prod0; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly {
let mm := mulmod(x, y, not(0))
prod0 := mul(x, y)
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^256. Also prevents denominator == 0.
require(denominator > prod1, "Math: mulDiv 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.
// Does not overflow because the denominator cannot be zero at this stage in the function.
uint256 twos = denominator & (~denominator + 1);
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^256 / 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^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
// that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
// four bits. That is, denominator * inv = 1 mod 2^4.
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^8
inverse *= 2 - denominator * inverse; // inverse mod 2^16
inverse *= 2 - denominator * inverse; // inverse mod 2^32
inverse *= 2 - denominator * inverse; // inverse mod 2^64
inverse *= 2 - denominator * inverse; // inverse mod 2^128
inverse *= 2 - denominator * inverse; // inverse mod 2^256
// 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^256. Since the preconditions guarantee that the outcome is
// less than 2^256, 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;
}
}
/**
* @notice 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) {
uint256 result = mulDiv(x, y, denominator);
if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
result += 1;
}
return result;
}
/**
* @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
*
* Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
*/
function sqrt(uint256 a) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
// For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
//
// We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
// `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
//
// This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
// → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
// → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
//
// Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
uint256 result = 1 << (log2(a) >> 1);
// At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
// since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
// every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
// into the expected uint128 result.
unchecked {
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
return min(result, a / result);
}
}
/**
* @notice Calculates sqrt(a), following the selected rounding direction.
*/
function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = sqrt(a);
return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
}
}
/**
* @dev Return the log in base 2, rounded down, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 128;
}
if (value >> 64 > 0) {
value >>= 64;
result += 64;
}
if (value >> 32 > 0) {
value >>= 32;
result += 32;
}
if (value >> 16 > 0) {
value >>= 16;
result += 16;
}
if (value >> 8 > 0) {
value >>= 8;
result += 8;
}
if (value >> 4 > 0) {
value >>= 4;
result += 4;
}
if (value >> 2 > 0) {
value >>= 2;
result += 2;
}
if (value >> 1 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 2, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log2(value);
return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 10, rounded down, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >= 10 ** 64) {
value /= 10 ** 64;
result += 64;
}
if (value >= 10 ** 32) {
value /= 10 ** 32;
result += 32;
}
if (value >= 10 ** 16) {
value /= 10 ** 16;
result += 16;
}
if (value >= 10 ** 8) {
value /= 10 ** 8;
result += 8;
}
if (value >= 10 ** 4) {
value /= 10 ** 4;
result += 4;
}
if (value >= 10 ** 2) {
value /= 10 ** 2;
result += 2;
}
if (value >= 10 ** 1) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 10, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log10(value);
return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 256, rounded down, of a positive value.
* Returns 0 if given 0.
*
* Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
*/
function log256(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 16;
}
if (value >> 64 > 0) {
value >>= 64;
result += 8;
}
if (value >> 32 > 0) {
value >>= 32;
result += 4;
}
if (value >> 16 > 0) {
value >>= 16;
result += 2;
}
if (value >> 8 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 256, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log256(value);
return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0);
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SignedMath.sol)
pragma solidity ^0.8.0;
/**
* @dev Standard signed math utilities missing in the Solidity language.
*/
library SignedMathUpgradeable {
/**
* @dev Returns the largest of two signed numbers.
*/
function max(int256 a, int256 b) internal pure returns (int256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two signed numbers.
*/
function min(int256 a, int256 b) internal pure returns (int256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two signed numbers without overflow.
* The result is rounded towards zero.
*/
function average(int256 a, int256 b) internal pure returns (int256) {
// Formula from the book "Hacker's Delight"
int256 x = (a & b) + ((a ^ b) >> 1);
return x + (int256(uint256(x) >> 255) & (a ^ b));
}
/**
* @dev Returns the absolute unsigned value of a signed value.
*/
function abs(int256 n) internal pure returns (uint256) {
unchecked {
// must be unchecked in order to support `n = type(int256).min`
return uint256(n >= 0 ? n : -n);
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165Upgradeable {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC5267.sol)
pragma solidity ^0.8.0;
interface IERC5267Upgradeable {
/**
* @dev MAY be emitted to signal that the domain could have changed.
*/
event EIP712DomainChanged();
/**
* @dev returns the fields and values that describe the domain separator used by this contract for EIP-712
* signature.
*/
function eip712Domain()
external
view
returns (
bytes1 fields,
string memory name,
string memory version,
uint256 chainId,
address verifyingContract,
bytes32 salt,
uint256[] memory extensions
);
}{
"remappings": [
"@openzeppelin/contracts/=lib/openzeppelin-contracts-5/contracts/",
"@openzeppelin/contracts-5/=lib/openzeppelin-contracts-5/contracts/",
"@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/",
"@solmate/=lib/solmate/src/",
"@rari-capital/solmate/=lib/solmate/src/",
"@uniswap/v2-core/=lib/v2-core/contracts/",
"@uniswap/v3-core/=lib/v3-core/contracts/",
"@uniswap/v3-periphery/=lib/v3-periphery/contracts/",
"@morphodao/morpho-core-v1/=lib/morpho-core-v1/",
"@curvefi/api/=lib/curve-api/",
"forge-std/=lib/forge-std/src/",
"src/=src/",
"lib/=lib/",
"contracts/=src/",
"ds-test/=lib/solmate/lib/ds-test/src/",
"erc4626-tests/=lib/openzeppelin-contracts-5/lib/erc4626-tests/",
"openzeppelin-contracts-5/=lib/openzeppelin-contracts-5/",
"openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/",
"openzeppelin-contracts/=lib/openzeppelin-contracts-5/contracts/",
"openzeppelin/=lib/openzeppelin-contracts-upgradeable/contracts/",
"solmate/=lib/solmate/src/",
"v2-core/=lib/v2-core/contracts/",
"v3-core/=lib/v3-core/",
"v3-periphery/=lib/v3-periphery/contracts/",
"weird-erc20/=lib/solmate/lib/weird-erc20/src/",
"halmos-cheatcodes/=lib/openzeppelin-contracts-5/lib/halmos-cheatcodes/src/",
"openzeppelin-contracts-3.4.2/=lib/openzeppelin-contracts-3.4.2/contracts/",
"openzeppelin-contracts-4.6/=lib/openzeppelin-contracts-4.6/"
],
"optimizer": {
"enabled": true,
"runs": 200
},
"metadata": {
"useLiteralContent": false,
"bytecodeHash": "ipfs",
"appendCBOR": true
},
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"evmVersion": "shanghai",
"viaIR": true
}Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
Contract ABI
API[{"inputs":[{"internalType":"address","name":"_dstable","type":"address"},{"internalType":"address","name":"_fraxswapRouter","type":"address"},{"internalType":"address","name":"_issuer","type":"address"},{"internalType":"address","name":"_redeemer","type":"address"},{"internalType":"address","name":"_operator","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AccessControlBadConfirmation","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"bytes32","name":"neededRole","type":"bytes32"}],"name":"AccessControlUnauthorizedAccount","type":"error"},{"inputs":[{"internalType":"address","name":"target","type":"address"}],"name":"AddressEmptyCode","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"AddressInsufficientBalance","type":"error"},{"inputs":[{"internalType":"address","name":"asset","type":"address"}],"name":"AssetRedemptionPaused","type":"error"},{"inputs":[],"name":"DeadlineExceeded","type":"error"},{"inputs":[],"name":"EnforcedPause","type":"error"},{"inputs":[],"name":"ExpectedPause","type":"error"},{"inputs":[],"name":"FailedInnerCall","type":"error"},{"inputs":[{"internalType":"uint256","name":"requested","type":"uint256"},{"internalType":"uint256","name":"maximum","type":"uint256"}],"name":"FlashLoanAmountExceedsMaximum","type":"error"},{"inputs":[],"name":"FlashLoanRepaymentFailed","type":"error"},{"inputs":[{"internalType":"bytes","name":"reason","type":"bytes"}],"name":"FraxswapSwapFailed","type":"error"},{"inputs":[{"internalType":"uint256","name":"expected","type":"uint256"},{"internalType":"uint256","name":"actual","type":"uint256"}],"name":"InsufficientCollateralReceived","type":"error"},{"inputs":[{"internalType":"uint256","name":"expected","type":"uint256"},{"internalType":"uint256","name":"actual","type":"uint256"}],"name":"InsufficientCollateralRedeemed","type":"error"},{"inputs":[{"internalType":"uint256","name":"expected","type":"uint256"},{"internalType":"uint256","name":"actual","type":"uint256"}],"name":"InsufficientDStableReceived","type":"error"},{"inputs":[{"internalType":"uint256","name":"expected","type":"uint256"},{"internalType":"uint256","name":"actual","type":"uint256"}],"name":"InsufficientDusdIssued","type":"error"},{"inputs":[{"internalType":"uint256","name":"expected","type":"uint256"},{"internalType":"uint256","name":"actual","type":"uint256"}],"name":"InsufficientProfit","type":"error"},{"inputs":[],"name":"InvalidArbitrageParameters","type":"error"},{"inputs":[],"name":"InvalidCollateralAsset","type":"error"},{"inputs":[{"internalType":"uint24","name":"feeTier","type":"uint24"}],"name":"InvalidFeeTier","type":"error"},{"inputs":[],"name":"InvalidFlashLoanInitiator","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"InvalidIntermediateToken","type":"error"},{"inputs":[],"name":"InvalidPathLength","type":"error"},{"inputs":[],"name":"InvalidSwapPathTokens","type":"error"},{"inputs":[],"name":"NoSwapPathProvided","type":"error"},{"inputs":[],"name":"ReentrancyGuardReentrantCall","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"SafeERC20FailedOperation","type":"error"},{"inputs":[{"internalType":"uint256","name":"requestedSlippage","type":"uint256"},{"internalType":"uint256","name":"maxSlippage","type":"uint256"}],"name":"SlippageTooHigh","type":"error"},{"inputs":[],"name":"UnauthorizedFlashLoan","type":"error"},{"inputs":[{"internalType":"bytes","name":"reason","type":"bytes"}],"name":"UniswapExactInputFailed","type":"error"},{"inputs":[],"name":"ZeroAddress","type":"error"},{"inputs":[],"name":"ZeroCollateralAmount","type":"error"},{"inputs":[],"name":"ZeroDStableAmount","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"tokenIn","type":"address"},{"indexed":true,"internalType":"address","name":"tokenOut","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountIn","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountOut","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"profit","type":"uint256"},{"indexed":false,"internalType":"string","name":"strategy","type":"string"}],"name":"ArbitrageExecuted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"tokenIn","type":"address"},{"indexed":true,"internalType":"address","name":"tokenOut","type":"address"},{"indexed":false,"internalType":"uint256","name":"flashLoanAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountIn","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountOut","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"profit","type":"uint256"},{"indexed":false,"internalType":"string","name":"strategy","type":"string"}],"name":"FlashLoanArbitrageExecuted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"collateralAsset","type":"address"},{"indexed":false,"internalType":"uint256","name":"collateralIn","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"dusdIssued","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"dusdRedeemed","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"collateralOut","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"profit","type":"uint256"},{"indexed":false,"internalType":"string","name":"strategy","type":"string"}],"name":"IssueRedeemArbitrageExecuted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"oldIssuer","type":"address"},{"indexed":true,"internalType":"address","name":"newIssuer","type":"address"}],"name":"IssuerUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"oldRedeemer","type":"address"},{"indexed":true,"internalType":"address","name":"newRedeemer","type":"address"}],"name":"RedeemerUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"inputs":[],"name":"ARBITRAGE_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"HUNDRED_PERCENT_BPS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_SLIPPAGE_BPS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"OPERATOR_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"fee","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"_executeFlashLoanRedeemSwapIssue","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"fee","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"_executeFlashLoanSwapIssue","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"dstable","outputs":[{"internalType":"contract ERC20StablecoinUpgradeable","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"tokenIn","type":"address"},{"internalType":"address","name":"tokenOut","type":"address"},{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"minAmountOut","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint256","name":"slippageBps","type":"uint256"},{"internalType":"address","name":"profitTo","type":"address"},{"internalType":"bytes","name":"fraxswapRoute","type":"bytes"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"},{"internalType":"bool","name":"approveMax","type":"bool"}],"internalType":"struct FraxtalArbitrageHelper.ArbitrageParams","name":"params","type":"tuple"}],"name":"executeArbitrage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"tokenIn","type":"address"},{"internalType":"address","name":"tokenOut","type":"address"},{"internalType":"uint256","name":"flashLoanAmount","type":"uint256"},{"internalType":"uint256","name":"minAmountOut","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint256","name":"slippageBps","type":"uint256"},{"internalType":"address","name":"profitTo","type":"address"},{"internalType":"bytes","name":"fraxswapRoute","type":"bytes"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"},{"internalType":"bool","name":"approveMax","type":"bool"},{"internalType":"address","name":"collateralAsset","type":"address"},{"internalType":"uint256","name":"minDusdFromIssue","type":"uint256"}],"internalType":"struct FraxtalArbitrageHelper.FlashLoanArbitrageParams","name":"params","type":"tuple"}],"name":"executeFlashLoanArbitrage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"collateralAsset","type":"address"},{"internalType":"uint256","name":"flashLoanAmount","type":"uint256"},{"internalType":"uint256","name":"minCollateralFromRedeem","type":"uint256"},{"internalType":"uint256","name":"minDusdFromIssue","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint256","name":"slippageBps","type":"uint256"},{"internalType":"address","name":"profitTo","type":"address"},{"internalType":"bytes","name":"fraxswapRoute","type":"bytes"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"},{"internalType":"bool","name":"approveMax","type":"bool"}],"internalType":"struct FraxtalArbitrageHelper.FlashLoanRedeemSwapIssueParams","name":"params","type":"tuple"}],"name":"executeFlashLoanRedeemSwapIssue","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"collateralAsset","type":"address"},{"internalType":"uint256","name":"flashLoanAmount","type":"uint256"},{"internalType":"uint256","name":"minCollateralFromSwap","type":"uint256"},{"internalType":"uint256","name":"minDusdFromIssue","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint256","name":"slippageBps","type":"uint256"},{"internalType":"address","name":"profitTo","type":"address"},{"internalType":"bytes","name":"fraxswapRoute","type":"bytes"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"},{"internalType":"bool","name":"approveMax","type":"bool"}],"internalType":"struct FraxtalArbitrageHelper.FlashLoanSwapIssueParams","name":"params","type":"tuple"}],"name":"executeFlashLoanSwapIssue","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"collateralAsset","type":"address"},{"internalType":"uint256","name":"collateralAmount","type":"uint256"},{"internalType":"uint256","name":"minDusdIssued","type":"uint256"},{"internalType":"uint256","name":"minCollateralRedeemed","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint256","name":"slippageBps","type":"uint256"},{"internalType":"address","name":"profitTo","type":"address"},{"internalType":"bytes","name":"fraxswapRoute","type":"bytes"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"},{"internalType":"bool","name":"approveMax","type":"bool"}],"internalType":"struct FraxtalArbitrageHelper.IssueSwapRedeemParams","name":"params","type":"tuple"}],"name":"executeIssueSwapRedeem","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"bytes","name":"packedData","type":"bytes"},{"internalType":"address","name":"tokenIn","type":"address"},{"internalType":"address","name":"tokenOut","type":"address"},{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"minAmountOut","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"internalType":"struct FraxtalArbitrageHelper.PackedArbitrageParams","name":"params","type":"tuple"}],"name":"executePackedArbitrage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"fraxswapRouter","outputs":[{"internalType":"contract IFraxswapRouterMultihop","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getMaxSlippageBps","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"issuer","outputs":[{"internalType":"contract IssuerV2","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"initiator","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"fee","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"onFlashLoan","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"pause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"redeemer","outputs":[{"internalType":"contract RedeemerV2","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"callerConfirmation","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"rescueETH","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"rescueTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_issuer","type":"address"}],"name":"setIssuer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_redeemer","type":"address"}],"name":"setRedeemer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"unpause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]Contract Creation Code
60c034620002ef57601f620047f238819003918201601f19168301916001600160401b03831184841017620002f35780849260a094604052833981010312620002ef576200004d8162000307565b906200005c6020820162000307565b916200006b6040830162000307565b91620000886080620000806060840162000307565b920162000307565b6001805560025490936001600160a01b0393841680158015620002e4575b8015620002d9575b8015620002ce575b8015620002c3575b620002b15760805294831660a0526001600160a81b03191660089490941b610100600160a81b031693909317600255600380546001600160a01b0319169390911692909217909155620001299062000116336200031c565b5062000122816200039a565b506200043a565b506040516142fc9081620004d682396080518181816102020152818161045a0152818161049601528181610617015281816106d501528181610765015281816107990152818161084b01528181610c3a01528181611230015281816112f20152818161136a015281816113a6015281816116160152818161166d015281816116eb0152818161197801528181611a8601528181611b8201528181611d3601528181611d6d01528181611e3a015281816121e401528181612618015281816126590152818161281b015281816128a10152818161295801528181612a4a01528181612bc901528181612c2001528181612cd001528181612df201528181612eb101528181612f410152612fe4015260a05181818161095a0152818161099401528181610a5d01528181610e5d015281816115f2015281816116490152818161178b015281816124210152818161245d01528181612532015281816126b501528181612ba501528181612bfc01528181612d47015281816131c1015281816133cb01526141c20152f35b60405163d92e233d60e01b8152600490fd5b5084861615620000be565b5084841615620000b6565b5084821615620000ae565b5084871615620000a6565b5f80fd5b634e487b7160e01b5f52604160045260245ffd5b51906001600160a01b0382168203620002ef57565b6001600160a01b03165f8181527fad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5602052604081205490919060ff166200039657818052816020526040822081835260205260408220600160ff1982541617905533915f80516020620047d28339815191528180a4600190565b5090565b6001600160a01b03165f8181527fee57cd81e84075558e8fcc182a1f4393f91fc97f963a136e66b7f949a62f319f60205260408120549091907f97667070c54ef182b0f5858b034beac1b6f3089aa2d3188bb1e8929f4fa9b9299060ff166200043557808352826020526040832082845260205260408320600160ff198254161790555f80516020620047d2833981519152339380a4600190565b505090565b6001600160a01b03165f8181527f71d66f16066d309d0e3c98c7bdbd7d02163436c8b639a716036c8d557942ef5960205260408120549091907fb1ac20a777be3038119e44f95ac0fef6a80dda7484ca8661110ad49ddc2bb8179060ff166200043557808352826020526040832082845260205260408320600160ff198254161790555f80516020620047d2833981519152339380a460019056fe608080604052600436101561001c575b50361561001a575f80fd5b005b5f90813560e01c90816301ffc9a7146133fa57508063033032a1146133b5578063099a04e51461332d57806318ea111d146131315780631d1438481461310457806321b950c314612b7b57806323e30c8b1461219b578063248a9ca31461216f5780632ba29d38146121465780632f2ff15d146121095780633307ce841461209357806336568abe1461204b5780633f4ba83a14611fe057806355cc4e5714611f5f5780635c975abb14611f3c5780635fd38a0d14611f015780638456cb5914611ea65780638c2f52ae146119f057806391d14854146119a757806397fe30ef146119625780639849079514611945578063a217fddf14611929578063a316c56e14611099578063cea9d26f1461104c578063d547741f1461100b578063e229cd76146103f2578063e756279c14610cc0578063e825fcde14610ba7578063ec5da3451461040f578063ed6d60be146103f2578063f5b541a6146103b75763f6f7e51f0361000f57346103b457610192366134a9565b61019a6135f0565b6101a26138f3565b6101aa613911565b608081013542116103a2576001600160a01b0390816101c882613a71565b1615610390576020918282013590811561037e5760a08301356107d0808211610360575050806101fa60c08501613a71565b161561034e577f0000000000000000000000000000000000000000000000000000000000000000169160405163613255ab60e01b81528360048201528481602481875afa908115610343578691610312575b508083116102f457506102a49284926102856102779360405194859187808401526040830190613eb3565b03601f1981018552846137dc565b86604051809681958294632e7ff4ef60e11b84528630600486016138bf565b03925af180156102e9576102bb575b826001805580f35b816102da92903d106102e2575b6102d281836137dc565b810190613869565b505f806102b3565b503d6102c8565b6040513d85823e3d90fd5b8260449160405191632da547fb60e01b835260048301526024820152fd5b90508481813d831161033c575b61032981836137dc565b8101031261033857515f61024c565b5f80fd5b503d61031f565b6040513d88823e3d90fd5b60405163d92e233d60e01b8152600490fd5b6044925060405191633b5d56ed60e11b835260048301526024820152fd5b604051630fc4d9c960e11b8152600490fd5b60405163a9213b9b60e01b8152600490fd5b60405163559895a360e01b8152600490fd5b80fd5b50346103b457806003193601126103b45760206040517f97667070c54ef182b0f5858b034beac1b6f3089aa2d3188bb1e8929f4fa9b9298152f35b50346103b457806003193601126103b45760206040516107d08152f35b50346103b45761043861042136613508565b61043094929394303314613f99565b810190613fd5565b6080810191825142116103a25760035461047f906001600160a01b03908116907f000000000000000000000000000000000000000000000000000000000000000016613ae2565b6003546104bb9085906001600160a01b03908116907f000000000000000000000000000000000000000000000000000000000000000016613bc4565b81516040516370a0823160e01b815230600482015290602090829060249082906001600160a01b03165afa908115610343578691610b75575b50600354835160408501805190939289926001600160a01b0391821692911690823b156109075760405163d878016160e01b8152600481018b90526001600160a01b0392909216602483015260448201529082908290606490829084905af180156108fc57610b5d575b505083516040516370a0823160e01b81523060048201529190602090839060249082906001600160a01b03165afa8015610b52578890610b1e575b6105a39250613a85565b9051808210610b015750809360e0840180515161093d575b505082516002546105dc925060081c6001600160a01b039081169116613ae2565b81516002546105fc91859160081c6001600160a01b039081169116613bc4565b6040516370a0823160e01b81523060048201526020816024817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa90811561034357869161090b575b5060025483516060850151889260081c6001600160a01b03908116921690823b1561090757604051630ffea21360e21b8152600481018990526001600160a01b0392909216602483015260448201529082908290606490829084905af180156108fc576108e4575b50506040516370a0823160e01b8152306004820152906020826024817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa80156108d95787906108a5575b6107169250613a85565b906060830151808310610887575061072e9085613f8c565b91828210610875577f4095541f16523f676ae1764a678013124f8fe1cca280cf8d0cdd9b3c694bd6c293610100936107cd906107937f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031680613ae2565b6107c7817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031680613bc4565b84613a85565b9182610832575b60018060a01b039051169560405193818552602085015260408401526060830152608082015260c060a0820152601b60c08201527f466c6173684c6f616e2d52656465656d2d537761702d4973737565000000000060e0820152a280f35b60c08101516108709084906001600160a01b03908116907f000000000000000000000000000000000000000000000000000000000000000016613e75565b6107d4565b604051634bfebe4560e01b8152600490fd5b604490836040519163034a66bb60e61b835260048301526024820152fd5b506020823d6020116108d1575b816108bf602093836137dc565b8101031261033857610716915161070c565b3d91506108b2565b6040513d89823e3d90fd5b6108ed90613777565b6108f857855f6106b7565b8580fd5b6040513d84823e3d90fd5b8380fd5b90506020813d602011610935575b81610926602093836137dc565b8101031261033857515f61064f565b3d9150610919565b8451939550610a5893602093929190610982906001600160a01b037f000000000000000000000000000000000000000000000000000000000000000081169116613ae2565b85516109bc9084906001600160a01b037f000000000000000000000000000000000000000000000000000000000000000081169116613bc4565b60018060a01b03865116916109d560a0880151856142a4565b9051610160880151151560ff6101008a015116916101208a0151936101408b015195519660405198610a068a61375b565b818a528a8a01526040890152606088015230608088015260a087015260c086015260e08501526101008401526101208301526101408201526040518093819263d2c9041160e01b835260048301613de1565b0381887f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165af1859181610acd575b50610ac457610ac0610a9f613d1d565b6040516346d9bb5360e01b8152602060048201529182916024830190613881565b0390fd5b915f80806105bb565b9091506020813d602011610af9575b81610ae9602093836137dc565b810103126103385751905f610a8f565b3d9150610adc565b604491604051916313e4ffab60e21b835260048301526024820152fd5b506020823d602011610b4a575b81610b38602093836137dc565b81010312610338576105a39151610599565b3d9150610b2b565b6040513d8a823e3d90fd5b610b6690613777565b610b7157865f61055e565b8680fd5b90506020813d602011610b9f575b81610b90602093836137dc565b8101031261033857515f6104f4565b3d9150610b83565b50346103b457610bb6366134a9565b610bbe6135f0565b610bc66138f3565b610bce613911565b608081013542116103a2576001600160a01b039081610bec82613a71565b1615610390576020918282013590811561037e5760a08301356107d080821161036057505080610c1e60c08501613a71565b161561034e57610c3160e0840184613daf565b905015610cae577f0000000000000000000000000000000000000000000000000000000000000000169160405163613255ab60e01b81528360048201528481602481875afa90811561034357869161031257508083116102f457506102a49284926102856102779360405194859187808401526040830190613eb3565b60405163f114948960e01b8152600490fd5b50346103b457600319602036820112611007576001600160401b03600435116110075760c0600435600401916004353603011261100757610cff613595565b610d076138f3565b610d0f613911565b81610160604051610d1f8161378a565b8281528260208201528260408201528260608201528260808201528260a08201528260c0820152606060e08201528261010082015282610120820152826101408201520152610d72602460043501613a71565b90610de5610d8e610d87604460043501613a71565b9280613daf565b9260405194610d9c8661378a565b6001600160a01b03908116865216602085015260646004358181013560408701526084810135606087015260a40135608086015260a08501523360c0850190815292369161394f565b60e083015282610100830152826101208301528261014083015282610160830152610e0f826140b2565b6020828101516040516370a0823160e01b808252306004830152929091829060249082906001600160a01b03165afa908115610fca578591610fd5575b508351610ea2906001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000811691610e8b91839116613ae2565b8551604087015191906001600160a01b0316613bc4565b610eab8461410c565b602084810151604051938452306004850152839060249082906001600160a01b03165afa8015610fca578590610f96575b610ee69250613a85565b906060830151808310610f78575082516020840151604085015192517f46b2107d05bd48594fd93879b48e5037b2e2c6997667e30ef789e0ddba059bb793610f40936001600160a01b03928316938793908116911661424a565b8351602085015160409586015195516001600160a01b0391821696919092169491928392610f6e9284613aa6565b0390a36001805580f35b60449083604051916352388bed60e11b835260048301526024820152fd5b506020823d602011610fc2575b81610fb0602093836137dc565b8101031261033857610ee69151610edc565b3d9150610fa3565b6040513d87823e3d90fd5b90506020813d602011610fff575b81610ff0602093836137dc565b8101031261033857515f610e4c565b3d9150610fe3565b5080fd5b50346103b45760403660031901126103b45761104860043561102b61347f565b9080845283602052611043600160408620015461364b565b6136e8565b5080f35b50346103b45760603660031901126103b457611066613469565b61106e61347f565b9061107761353e565b6001600160a01b03918083161561034e57611096926044359216613e75565b80f35b50346103b4576110a8366134a9565b6110b0613595565b6110b86138f3565b6110c0613911565b608081013542116103a2576001600160a01b036110dc82613a71565b161561039057602081013515611917576107d08060a0830135116118f557506001600160a01b0361110f60c08301613a71565b161561034e5760249060206001600160a01b0361112b83613a71565b16604051938480926370a0823160e01b82523060048301525afa9182156102e95783926118c1575b506001600160a01b0361116582613a71565b166040516323b872dd60e01b602082015233602482015230604482015260208301356064820152606481528060a08101106001600160401b0360a0830111176118ad576111b89160a08201604052613cac565b6111e26001600160a01b036111cc83613a71565b60025460081c6001600160a01b03169116613ae2565b6112156001600160a01b036111f683613a71565b60025460208501359260089190911c6001600160a01b03169116613bc4565b6040516370a0823160e01b81523060048201526020816024817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa9081156118a2578491611870575b50600254849060081c6001600160a01b031661128384613a71565b90803b156115df5760408051630ffea21360e21b8152602087013560048201526001600160a01b03939093166024840152850135604483015282908290606490829084905af180156108fc5761185c575b50506040516370a0823160e01b8152306004820152906020826024817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa8015610fca578590611828575b6113339250613a85565b916040820135831061180757829061134e60e0840184613daf565b90506115e3575b60035461138f906001600160a01b03908116907f000000000000000000000000000000000000000000000000000000000000000016613ae2565b6003546113cb9083906001600160a01b03908116907f000000000000000000000000000000000000000000000000000000000000000016613bc4565b6003546001600160a01b031690856113e285613a71565b606086013593803b156115df5760405163d878016160e01b8152600481018790526001600160a01b039290921660248301526044820185905282908290606490829084905af180156108fc576115cb575b50602491905060206001600160a01b0361144c87613a71565b16604051938480926370a0823160e01b82523060048301525afa80156108d9578790611597575b61147d9250613a85565b90808210610b01575091610100917f4095541f16523f676ae1764a678013124f8fe1cca280cf8d0cdd9b3c694bd6c293602083013581115f14611590576114c8602084013582613a85565b915b82611563575b602084013582101561153e575b6001600160a01b036114ee85613a71565b169660206040519501358552602085015260408401526060830152608082015260c060a0820152601160c08201527049737375652d537761702d52656465656d60781b60e0820152a26001805580f35b61155e6020850135336001600160a01b0361155888613a71565b16613e75565b6114dd565b61158b836001600160a01b0361157887613a71565b1661158560c08801613a71565b90613e75565b6114d0565b86916114ca565b506020823d6020116115c3575b816115b1602093836137dc565b810103126103385761147d9151611473565b3d91506115a4565b6115d490613777565b6108f857855f611433565b8280fd5b905061163b6001600160a01b037f00000000000000000000000000000000000000000000000000000000000000008116907f000000000000000000000000000000000000000000000000000000000000000016613ae2565b611692836001600160a01b037f00000000000000000000000000000000000000000000000000000000000000008116907f000000000000000000000000000000000000000000000000000000000000000016613bc4565b6116a060a0830135846142a4565b61016083013580151581036108f8576101008401359160ff83168303610b71576117869261176860209360ff6116d960e08a018a613daf565b949092604051966116e98861375b565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168089528989018e905260408901526060880152306080808901919091528b013560a0880152151560c08701521660e085015261012080890135610100860152610140808a01359186019190915292369161394f565b908201526040518093819263d2c9041160e01b835260048301613de1565b0381887f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165af18591816117d3575b506117cd57610ac0610a9f613d1d565b90611355565b9091506020813d6020116117ff575b816117ef602093836137dc565b810103126103385751905f6117bd565b3d91506117e2565b50604491604080519263034a66bb60e61b8452013560048301526024820152fd5b506020823d602011611854575b81611842602093836137dc565b81010312610338576113339151611329565b3d9150611835565b61186590613777565b61090757835f6112d4565b90506020813d60201161189a575b8161188b602093836137dc565b8101031261033857515f611268565b3d915061187e565b6040513d86823e3d90fd5b634e487b7160e01b5f52604160045260245ffd5b9091506020813d6020116118ed575b816118dd602093836137dc565b810103126103385751905f611153565b3d91506118d0565b6044925060a060405192633b5d56ed60e11b8452013560048301526024820152fd5b604051632006714760e01b8152600490fd5b50346103b457806003193601126103b457602090604051908152f35b50346103b457806003193601126103b45760206040516127108152f35b50346103b457806003193601126103b4576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b50346103b45760403660031901126103b45760406119c361347f565b91600435815280602052209060018060a01b03165f52602052602060ff60405f2054166040519015158152f35b50346103b457600319602036820112611007576001600160401b0360043511611007576101c090600435360301126103b457611a2a6135f0565b611a326138f3565b611a3a613911565b6084600435013542116103a257604460043501351561037e576001600160a01b03611a686004803501613a71565b161561034e57611a7c600435600401613a71565b6001600160a01b037f00000000000000000000000000000000000000000000000000000000000000008116911603611e1e576001600160a01b03611ac4600435602401613a71565b161561034e576107d08060a4600435013511611e8257506001600160a01b03611af160043560c401613a71565b161561034e57611b0b60e460043501600435600401613daf565b905015610cae576001600160a01b03611b2960043561018401613a71565b1615611e3057611b3e61018460043501613a71565b6001600160a01b03611b54600435602401613a71565b166001600160a01b0390911603610390576101a4600435013515611e1e575b60405163613255ab60e01b81527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031660048201819052602090829060249082905afa9081156108fc578291611dec575b50806044600435013511611dc957506040516020818101819052611d68916001600160a01b03611bfe6004803501613495565b1660408201526001600160a01b03611c1a600435602401613495565b16606082015260043560448101356080830152606481013560a0830152608481013560c083015260a481013560e08301526001600160a01b0390611c609060c401613495565b16610100820152611d1881611c96611c8260e4600435016004356004016137fd565b6101c061012085015261020084019161382e565b60ff611ca76101046004350161384e565b16610140830152600435610124810135610160840152610144810135610180840152611cd6906101640161385c565b15156101a08301526001600160a01b03611cf560043561018401613495565b166101c08301526004356101a401356101e083015203601f1981018352826137dc565b604051632e7ff4ef60e11b81529283918291906004803560440135907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031690309086016138bf565b0381857f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165af180156108fc57611daa575b506001805580f35b611dc29060203d6020116102e2576102d281836137dc565b505f611da2565b60449060405190632da547fb60e01b825282600435013560048301526024820152fd5b90506020813d602011611e16575b81611e07602093836137dc565b8101031261033857515f611bcb565b3d9150611dfa565b60405163b83a8a7b60e01b8152600490fd5b6001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000811690611e6a602460043501613a71565b1614611b735760405163b83a8a7b60e01b8152600490fd5b60449060405190633b5d56ed60e11b825260a4600435013560048301526024820152fd5b50346103b457806003193601126103b457611ebf61353e565b611ec76138f3565b600160ff1960025416176002557f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2586020604051338152a180f35b50346103b457806003193601126103b45760206040517fb1ac20a777be3038119e44f95ac0fef6a80dda7484ca8661110ad49ddc2bb8178152f35b50346103b457806003193601126103b457602060ff600254166040519015158152f35b50346103b45760203660031901126103b457611f79613469565b611f8161353e565b6001600160a01b0381811691821561034e5760028054610100600160a81b03198116600893841b610100600160a81b031617909155901c167f6d32c1367cc28eab61cca04e424f151519f02908af46776ba8373eaea407baa98380a380f35b50346103b457806003193601126103b457611ff961353e565b60025460ff8116156120395760ff19166002557f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa6020604051338152a180f35b604051638dfc202b60e01b8152600490fd5b50346103b45760403660031901126103b45761206561347f565b336001600160a01b0382160361208157611048906004356136e8565b60405163334bd91960e11b8152600490fd5b50346103b45760203660031901126103b4576120ad613469565b6120b561353e565b6001600160a01b0390811690811561034e57600354826bffffffffffffffffffffffff60a01b821617600355167f634105f7bf7f464a9e13df9c8d255054a18bc8497dc837921dd7ccb3e02e623e8380a380f35b50346103b45760403660031901126103b45761104860043561212961347f565b9080845283602052612141600160408620015461364b565b61366c565b50346103b457806003193601126103b4576003546040516001600160a01b039091168152602090f35b50346103b45760203660031901126103b457600160406020926004358152808452200154604051908152f35b50346103b45760a03660031901126103b4576121b5613469565b906121be61347f565b506001600160401b03916084358381116115df576121e09036906004016134db565b90917f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03163303612b6957306001600160a01b0390911603612b5757303b156115df576040516321b950c360e01b81528380828061224f868860643560443560048601613f70565b038183305af19182612b43575b5050612b2657303b156115df5760405163ec5da34560e01b81528380828061228e868860643560443560048601613f70565b038183305af19182612b2f575b5050612b265760208282810103126115df57813590848211610907576101c094858385018386010312612b2257604051958601868110828211176118ad576040526122e7838501613495565b86526122f7602084860101613495565b602087015283830160408181013590880152606080820135908801526080808201359088015260a080820135908801526123339060c001613495565b60c087015260e0838501013511610907576101a0919061235d90840184830160e081013501613985565b60e08601526123716101008285010161384e565b61010086015261012081840101356101208601526101408184010135610140860152610160926123a4848383010161385c565b848701526123b761018083830101613495565b6101808701520101356101a0840152608083015142116103a2576020838101516040516370a0823160e01b81523060048201529190829060249082906001600160a01b03165afa80156102e957612af7575b50825161252d91602091612449906001600160a01b037f000000000000000000000000000000000000000000000000000000000000000081169116613ae2565b845161248590604435906001600160a01b037f000000000000000000000000000000000000000000000000000000000000000081169116613bc4565b60018060a01b038551169060018060a01b03838701511690606087015190608088015190880151151560ff6101008a015116906101208a0151926101408b01519460e08c015196604051986124d98a61375b565b89526044358a8a01526040890152606088015230608088015260a087015260c086015260e08501526101008401526101208301526101408201526040518093819263d2c9041160e01b835260048301613de1565b0381857f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165af19081612acc575b5061257357610ac0610a9f613d1d565b6020828101516040516370a0823160e01b81523060048201529190829060249082906001600160a01b03165afa9081156108fc578291612a9a575b506060830151808210612a7d575061018083015181906001600160a01b031680151580612a42575b612845575b50506125eb606435604435613f8c565b8110610875577fd0049207b8f1bc71652f1b174d8daeee4d4561409e757b9b0cd137a98bf725cc906126467f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031680613ae2565b612687612657606435604435613f8c565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031680613bc4565b61269e60643561269960443584613a85565b613a85565b9081612802575b84516126dd906001600160a01b037f000000000000000000000000000000000000000000000000000000000000000081169116613ae2565b6101808501516001600160a01b0316806127e0575b5084516020860151610180909601516001600160a01b0396871696918216955016156127ae57612780604051612727816137a6565b601781527f4672617874616c2d466c6173684c6f616e2d497373756500000000000000000060208201525b604051938493604435855260443560208601526040850152606084015260a0608084015260a0830190613881565b0390a35b60206040517f439148f0bbc682ca079e46d6e2c2f0c1e3b820f1a291b069d8882abf8cf18dd98152f35b6127806040516127bd816137a6565b6011815270233930bc3a30b616a33630b9b42637b0b760791b6020820152612752565b6002546127fc9160089190911c6001600160a01b031690613ae2565b5f6126f2565b60c08501516128409083906001600160a01b03908116907f000000000000000000000000000000000000000000000000000000000000000016613e75565b6126a5565b6101a085015160025490935090919061286a9060081c6001600160a01b031683613ae2565b60025461288590829060081c6001600160a01b031684613bc4565b6040516370a0823160e01b8152306004820152916020836024817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa928315610fca578593612a0e575b5060025460081c6001600160a01b031691823b156108f857604051630ffea21360e21b815260048101919091526001600160a01b03919091166024820152604481018490529084908290606490829084905af180156118a2579084916129fa575b50506040516370a0823160e01b8152306004820152906020826024817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa80156118a25784906129c6575b6129999250613a85565b908082106129a957505f806125db565b6044916040519163034a66bb60e61b835260048301526024820152fd5b506020823d6020116129f2575b816129e0602093836137dc565b8101031261033857612999915161298f565b3d91506129d3565b612a0390613777565b6115df57825f61293a565b9092506020813d602011612a3a575b81612a2a602093836137dc565b810103126103385751915f6128d9565b3d9150612a1d565b5060208501517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03908116911614156125d6565b604491604051916352388bed60e11b835260048301526024820152fd5b90506020813d602011612ac4575b81612ab5602093836137dc565b8101031261033857515f6125ae565b3d9150612aa8565b602090813d8311612af0575b612ae281836137dc565b81010312610338575f612563565b503d612ad8565b602090813d8311612b1b575b612b0d81836137dc565b81010312610338575f612409565b503d612b03565b8480fd5b50505050612784565b612b3890613777565b61090757835f61229b565b612b4c90613777565b61090757835f61225c565b6040516377be635b60e11b8152600490fd5b60405163d2958ad160e01b8152600490fd5b50346103b457612b8d61042136613508565b608081015142116103a257612bee6001600160a01b037f00000000000000000000000000000000000000000000000000000000000000008116907f000000000000000000000000000000000000000000000000000000000000000016613ae2565b612c45836001600160a01b037f00000000000000000000000000000000000000000000000000000000000000008116907f000000000000000000000000000000000000000000000000000000000000000016613bc4565b80516040516370a0823160e01b815230600482015290602090829060249082906001600160a01b03165afa8015610fca576130d9575b50612d42602060018060a01b0383511660408401516080850151610160860151151560ff61010088015116610120880151916101408901519360e08a01519560405197612cc78961375b565b60018060a01b037f00000000000000000000000000000000000000000000000000000000000000001689528d8a8a01526040890152606088015230608088015260a087015260c086015260e08501526101008401526101208301526101408201526040518093819263d2c9041160e01b835260048301613de1565b0381887f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165af18591816130a5575b50612d8957610ac0610a9f613d1d565b91604082015180841061308757508151600254612db7916001600160a01b0360089290921c82169116613ae2565b8151600254612dd791859160081c6001600160a01b039081169116613bc4565b6040516370a0823160e01b81523060048201526020816024817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa908115610343578691613055575b50600254835160608501516001600160a01b0391821692899260089190911c16803b156115df57604051630ffea21360e21b8152600481018990526001600160a01b0394909416602485015260448401919091528290606490829084905af180156108d957613042575b506040516370a0823160e01b8152306004820152906020826024817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa80156108d957879061300e575b612ef29250613a85565b9060608301518083106108875750612f0a9085613f8c565b91828210610875577f4095541f16523f676ae1764a678013124f8fe1cca280cf8d0cdd9b3c694bd6c29361010093612f6f906107937f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031680613ae2565b9182612fcb575b60018060a01b039051169560405193818552602085015260408401526060830152608082015260c060a0820152601460c082015273466c6173684c6f616e2d537761702d497373756560601b60e0820152a280f35b60c08101516130099084906001600160a01b03908116907f000000000000000000000000000000000000000000000000000000000000000016613e75565b612f76565b506020823d60201161303a575b81613028602093836137dc565b8101031261033857612ef29151612ee8565b3d915061301b565b61304e90969196613777565b945f612e94565b90506020813d60201161307f575b81613070602093836137dc565b810103126108f857515f612e2a565b3d9150613063565b604490846040519163079edc7d60e41b835260048301526024820152fd5b9091506020813d6020116130d1575b816130c1602093836137dc565b810103126108f85751905f612d79565b3d91506130b4565b602090813d83116130fd575b6130ef81836137dc565b81010312610907575f612c7b565b503d6130e5565b50346103b457806003193601126103b45760025460405160089190911c6001600160a01b03168152602090f35b50346103b457613140366134a9565b613148613595565b6131506138f3565b613158613911565b61316a61316536836139a0565b6140b2565b6020818101916001600160a01b03908161318385613a71565b1660405184816024816370a0823160e01b958682523060048301525afa9081156108d9578791613300575b50836131b984613a71565b16946131e8857f0000000000000000000000000000000000000000000000000000000000000000168097613ae2565b613203856131f586613a71565b166040860135978891613bc4565b61321561321036866139a0565b61410c565b808561322089613a71565b169360246040518096819382523060048301525afa908115610b525788916132d0575b5061324e9250613a85565b906060810135808310610f78575090610f6e826132be6132b86132b26132957f46b2107d05bd48594fd93879b48e5037b2e2c6997667e30ef789e0ddba059bb79897613a71565b868a6132a08d613a71565b6132ac60c08901613a71565b9361424a565b92613a71565b97613a71565b92846040519586951698169684613aa6565b905082813d83116132f9575b6132e681836137dc565b81010312610b715761324e91515f613243565b503d6132dc565b90508481813d8311613326575b61331781836137dc565b81010312610b7157515f6131ae565b503d61330d565b50346103b45760403660031901126103b457613347613469565b61334f61353e565b6001600160a01b0316801561034e578180808093602435905af1613371613d1d565b501561337a5780f35b60405162461bcd60e51b815260206004820152601360248201527211551217d514905394d1915497d19052531151606a1b6044820152606490fd5b50346103b457806003193601126103b4576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b9050346110075760203660031901126110075760043563ffffffff60e01b81168091036115df57602092506323e30c8b60e01b811490811561343e575b5015158152f35b637965db0b60e01b811491508115613458575b505f613437565b6301ffc9a760e01b1490505f613451565b600435906001600160a01b038216820361033857565b602435906001600160a01b038216820361033857565b35906001600160a01b038216820361033857565b6003199060208183011261033857600435916001600160401b0383116103385782610180920301126103385760040190565b9181601f84011215610338578235916001600160401b038311610338576020838186019501011161033857565b6060600319820112610338576004359160243591604435906001600160401b0382116103385761353a916004016134db565b9091565b335f9081527fad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5602052604081205460ff16156135775750565b6044906040519063e2517d3f60e01b82523360048301526024820152fd5b335f9081527f71d66f16066d309d0e3c98c7bdbd7d02163436c8b639a716036c8d557942ef5960205260409020547fb1ac20a777be3038119e44f95ac0fef6a80dda7484ca8661110ad49ddc2bb8179060ff16156135775750565b335f9081527fee57cd81e84075558e8fcc182a1f4393f91fc97f963a136e66b7f949a62f319f60205260409020547f97667070c54ef182b0f5858b034beac1b6f3089aa2d3188bb1e8929f4fa9b9299060ff16156135775750565b805f525f60205260405f20335f5260205260ff60405f205416156135775750565b905f9180835282602052604083209160018060a01b03169182845260205260ff604084205416155f146136e357808352826020526040832082845260205260408320600160ff198254161790557f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d339380a4600190565b505090565b905f9180835282602052604083209160018060a01b03169182845260205260ff6040842054165f146136e35780835282602052604083208284526020526040832060ff1981541690557ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b339380a4600190565b61016081019081106001600160401b038211176118ad57604052565b6001600160401b0381116118ad57604052565b61018081019081106001600160401b038211176118ad57604052565b604081019081106001600160401b038211176118ad57604052565b608081019081106001600160401b038211176118ad57604052565b90601f801991011681019081106001600160401b038211176118ad57604052565b9035601e19823603018112156103385701602081359101916001600160401b03821161033857813603831361033857565b908060209392818452848401375f828201840152601f01601f1916010190565b359060ff8216820361033857565b3590811515820361033857565b90816020910312610338575180151581036103385790565b91908251928382525f5b8481106138ab575050825f602080949584010152601f8019910116010190565b60208183018101518483018201520161388b565b6001600160a01b0391821681529116602082015260408101919091526080606082018190526138f092910190613881565b90565b60ff600254166138ff57565b60405163d93c066560e01b8152600490fd5b600260015414613922576002600155565b604051633ee5aeb560e01b8152600490fd5b6001600160401b0381116118ad57601f01601f191660200190565b92919261395b82613934565b9161396960405193846137dc565b829481845281830111610338578281602093845f960137010152565b9080601f83011215610338578160206138f09335910161394f565b9190916101808184031261033857604051906139bb8261378a565b81936139c682613495565b83526139d460208301613495565b602084015260408201356040840152606082013560608401526080820135608084015260a082013560a0840152613a0d60c08301613495565b60c084015260e0820135906001600160401b03821161033857613a31918301613985565b60e0830152610100613a4481830161384e565b9083015261012080820135908301526101408082013590830152613a6c61016080920161385c565b910152565b356001600160a01b03811681036103385790565b91908203918211613a9257565b634e487b7160e01b5f52601160045260245ffd5b9160c0939183526020830152604082015260806060820152601060808201526f04672617874616c2d4d756c7469686f760841b60a08201520190565b60405190602082019263095ea7b360e01b938481525f8060018060a01b038094169384602488015281604488015260448752613b1d876137c1565b85169286519082855af190613b30613d1d565b82613b92575b5081613b87575b5015613b4a575b50505050565b613b7e93613b799160405191602083015260248201525f604482015260448152613b73816137c1565b82613cac565b613cac565b5f808080613b44565b90503b15155f613b3d565b80519192508115918215613baa575b5050905f613b36565b613bbd9250602080918301019101613869565b5f80613ba1565b60405163095ea7b360e01b602082018181526001600160a01b0385166024840152604480840196909652948252909290613bff6064856137dc565b83516001600160a01b0395848716915f9182919082855af190613c20613d1d565b82613c7a575b5081613c6f575b5015613c3b575b5050505050565b613c6594613b79926040519260208401521660248201525f604482015260448152613b73816137c1565b5f80808080613c34565b90503b15155f613c2d565b80519192508115918215613c92575b5050905f613c26565b613ca59250602080918301019101613869565b5f80613c89565b5f80613cd49260018060a01b03169360208151910182865af1613ccd613d1d565b9083613d4c565b8051908115159182613d02575b5050613cea5750565b60249060405190635274afe760e01b82526004820152fd5b613d159250602080918301019101613869565b155f80613ce1565b3d15613d47573d90613d2e82613934565b91613d3c60405193846137dc565b82523d5f602084013e565b606090565b90613d735750805115613d6157805190602001fd5b604051630a12f52160e11b8152600490fd5b81511580613da6575b613d84575090565b604051639996b31560e01b81526001600160a01b039091166004820152602490fd5b50803b15613d7c565b903590601e198136030182121561033857018035906001600160401b0382116103385760200191813603831361033857565b6101806138f0926020835260018060a01b03808251166020850152602082015160408501528060408301511660608501526060820151608085015260808201511660a084015260a081015160c084015260c0810151151560e084015260ff60e08201511661010090818501528101516101209081850152810151906101409182850152015191610160808201520190613881565b60405163a9059cbb60e01b60208201526001600160a01b03929092166024830152604480830193909352918152613eb191613b796064836137dc565b565b906001600160a01b0380613ec684613495565b1682526020830135602083015260408301356040830152606083013560608301526080830135608083015260a083013560a0830152613f0760c08401613495565b1660c0820152613f2e613f1d60e08401846137fd565b6101808060e086015284019161382e565b9161010060ff613f3f82840161384e565b169083015261012080820135908301526101408082013590830152613f6861016080920161385c565b151591015290565b6138f0949260609282526020820152816040820152019161382e565b91908201809211613a9257565b15613fa057565b60405162461bcd60e51b815260206004820152600d60248201526c496e7465726e616c206f6e6c7960981b6044820152606490fd5b6020818303126103385780356001600160401b039182821161033857019061018082840312610338576040519261400b8461378a565b61401483613495565b84526020830135602085015260408301356040850152606083013560608501526080830135608085015260a083013560a085015261405460c08401613495565b60c085015260e083013591821161033857614070918301613985565b60e083015261010061408381830161384e565b90830152610120808201359083015261014080820135908301526140ab61016080920161385c565b9082015290565b608081015142116103a25780516001600160a01b039081161580156140fe575b61034e5760a08201516107d080821161036057505060c0820151161561034e5760e001515115610cae57565b5080602083015116156140d2565b905f60018060a01b0380845116936141be604082015192602096878095858483809801511660608201519060808301516101608401511515906101009160ff8387015116916101209586880151956101409960e08b8b01519a01519b6040519d8e6141768161375b565b528d015260408c015260608b01523060808b015260a08a015260c089015260e088015286015284015282015260405195868094819363d2c9041160e01b835260048301613de1565b03927f0000000000000000000000000000000000000000000000000000000000000000165af19182614221575b505061421d57506141fa613d1d565b90610ac06040519283926346d9bb5360e01b845260048401526024830190613881565b9050565b813d8311614243575b61423481836137dc565b8101031261033857825f6141eb565b503d61422a565b6001600160a01b0391821693929116831461426757505050505f90565b5f918181111561429b5761427b9250613a85565b80925b8161428a575b50505090565b61429392613e75565b5f8181614284565b5050809261427e565b61271091820390828211613a9257818102918183041490151715613a9257049056fea2646970667358221220939a0f1654e07ff9cdd3dd3500361eabbedf9f57ba5cb9d0c617bf1528a1660d64736f6c634300081400332f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d000000000000000000000000788d96f655735f52c676a133f4dfc53cec614d4a00000000000000000000000046d2487cdbea04411c49e6c55ace805bfa8f5de5000000000000000000000000d95f1cfd9559b6e4d75488938de161a147f9fbca000000000000000000000000acd97c7d23a0534d79439b8d065d6d9d772ec9490000000000000000000000009ce33be219e7d45d36c08e2097e4c1d156fe99c5
Deployed Bytecode
0x608080604052600436101561001c575b50361561001a575f80fd5b005b5f90813560e01c90816301ffc9a7146133fa57508063033032a1146133b5578063099a04e51461332d57806318ea111d146131315780631d1438481461310457806321b950c314612b7b57806323e30c8b1461219b578063248a9ca31461216f5780632ba29d38146121465780632f2ff15d146121095780633307ce841461209357806336568abe1461204b5780633f4ba83a14611fe057806355cc4e5714611f5f5780635c975abb14611f3c5780635fd38a0d14611f015780638456cb5914611ea65780638c2f52ae146119f057806391d14854146119a757806397fe30ef146119625780639849079514611945578063a217fddf14611929578063a316c56e14611099578063cea9d26f1461104c578063d547741f1461100b578063e229cd76146103f2578063e756279c14610cc0578063e825fcde14610ba7578063ec5da3451461040f578063ed6d60be146103f2578063f5b541a6146103b75763f6f7e51f0361000f57346103b457610192366134a9565b61019a6135f0565b6101a26138f3565b6101aa613911565b608081013542116103a2576001600160a01b0390816101c882613a71565b1615610390576020918282013590811561037e5760a08301356107d0808211610360575050806101fa60c08501613a71565b161561034e577f000000000000000000000000788d96f655735f52c676a133f4dfc53cec614d4a169160405163613255ab60e01b81528360048201528481602481875afa908115610343578691610312575b508083116102f457506102a49284926102856102779360405194859187808401526040830190613eb3565b03601f1981018552846137dc565b86604051809681958294632e7ff4ef60e11b84528630600486016138bf565b03925af180156102e9576102bb575b826001805580f35b816102da92903d106102e2575b6102d281836137dc565b810190613869565b505f806102b3565b503d6102c8565b6040513d85823e3d90fd5b8260449160405191632da547fb60e01b835260048301526024820152fd5b90508481813d831161033c575b61032981836137dc565b8101031261033857515f61024c565b5f80fd5b503d61031f565b6040513d88823e3d90fd5b60405163d92e233d60e01b8152600490fd5b6044925060405191633b5d56ed60e11b835260048301526024820152fd5b604051630fc4d9c960e11b8152600490fd5b60405163a9213b9b60e01b8152600490fd5b60405163559895a360e01b8152600490fd5b80fd5b50346103b457806003193601126103b45760206040517f97667070c54ef182b0f5858b034beac1b6f3089aa2d3188bb1e8929f4fa9b9298152f35b50346103b457806003193601126103b45760206040516107d08152f35b50346103b45761043861042136613508565b61043094929394303314613f99565b810190613fd5565b6080810191825142116103a25760035461047f906001600160a01b03908116907f000000000000000000000000788d96f655735f52c676a133f4dfc53cec614d4a16613ae2565b6003546104bb9085906001600160a01b03908116907f000000000000000000000000788d96f655735f52c676a133f4dfc53cec614d4a16613bc4565b81516040516370a0823160e01b815230600482015290602090829060249082906001600160a01b03165afa908115610343578691610b75575b50600354835160408501805190939289926001600160a01b0391821692911690823b156109075760405163d878016160e01b8152600481018b90526001600160a01b0392909216602483015260448201529082908290606490829084905af180156108fc57610b5d575b505083516040516370a0823160e01b81523060048201529190602090839060249082906001600160a01b03165afa8015610b52578890610b1e575b6105a39250613a85565b9051808210610b015750809360e0840180515161093d575b505082516002546105dc925060081c6001600160a01b039081169116613ae2565b81516002546105fc91859160081c6001600160a01b039081169116613bc4565b6040516370a0823160e01b81523060048201526020816024817f000000000000000000000000788d96f655735f52c676a133f4dfc53cec614d4a6001600160a01b03165afa90811561034357869161090b575b5060025483516060850151889260081c6001600160a01b03908116921690823b1561090757604051630ffea21360e21b8152600481018990526001600160a01b0392909216602483015260448201529082908290606490829084905af180156108fc576108e4575b50506040516370a0823160e01b8152306004820152906020826024817f000000000000000000000000788d96f655735f52c676a133f4dfc53cec614d4a6001600160a01b03165afa80156108d95787906108a5575b6107169250613a85565b906060830151808310610887575061072e9085613f8c565b91828210610875577f4095541f16523f676ae1764a678013124f8fe1cca280cf8d0cdd9b3c694bd6c293610100936107cd906107937f000000000000000000000000788d96f655735f52c676a133f4dfc53cec614d4a6001600160a01b031680613ae2565b6107c7817f000000000000000000000000788d96f655735f52c676a133f4dfc53cec614d4a6001600160a01b031680613bc4565b84613a85565b9182610832575b60018060a01b039051169560405193818552602085015260408401526060830152608082015260c060a0820152601b60c08201527f466c6173684c6f616e2d52656465656d2d537761702d4973737565000000000060e0820152a280f35b60c08101516108709084906001600160a01b03908116907f000000000000000000000000788d96f655735f52c676a133f4dfc53cec614d4a16613e75565b6107d4565b604051634bfebe4560e01b8152600490fd5b604490836040519163034a66bb60e61b835260048301526024820152fd5b506020823d6020116108d1575b816108bf602093836137dc565b8101031261033857610716915161070c565b3d91506108b2565b6040513d89823e3d90fd5b6108ed90613777565b6108f857855f6106b7565b8580fd5b6040513d84823e3d90fd5b8380fd5b90506020813d602011610935575b81610926602093836137dc565b8101031261033857515f61064f565b3d9150610919565b8451939550610a5893602093929190610982906001600160a01b037f00000000000000000000000046d2487cdbea04411c49e6c55ace805bfa8f5de581169116613ae2565b85516109bc9084906001600160a01b037f00000000000000000000000046d2487cdbea04411c49e6c55ace805bfa8f5de581169116613bc4565b60018060a01b03865116916109d560a0880151856142a4565b9051610160880151151560ff6101008a015116916101208a0151936101408b015195519660405198610a068a61375b565b818a528a8a01526040890152606088015230608088015260a087015260c086015260e08501526101008401526101208301526101408201526040518093819263d2c9041160e01b835260048301613de1565b0381887f00000000000000000000000046d2487cdbea04411c49e6c55ace805bfa8f5de56001600160a01b03165af1859181610acd575b50610ac457610ac0610a9f613d1d565b6040516346d9bb5360e01b8152602060048201529182916024830190613881565b0390fd5b915f80806105bb565b9091506020813d602011610af9575b81610ae9602093836137dc565b810103126103385751905f610a8f565b3d9150610adc565b604491604051916313e4ffab60e21b835260048301526024820152fd5b506020823d602011610b4a575b81610b38602093836137dc565b81010312610338576105a39151610599565b3d9150610b2b565b6040513d8a823e3d90fd5b610b6690613777565b610b7157865f61055e565b8680fd5b90506020813d602011610b9f575b81610b90602093836137dc565b8101031261033857515f6104f4565b3d9150610b83565b50346103b457610bb6366134a9565b610bbe6135f0565b610bc66138f3565b610bce613911565b608081013542116103a2576001600160a01b039081610bec82613a71565b1615610390576020918282013590811561037e5760a08301356107d080821161036057505080610c1e60c08501613a71565b161561034e57610c3160e0840184613daf565b905015610cae577f000000000000000000000000788d96f655735f52c676a133f4dfc53cec614d4a169160405163613255ab60e01b81528360048201528481602481875afa90811561034357869161031257508083116102f457506102a49284926102856102779360405194859187808401526040830190613eb3565b60405163f114948960e01b8152600490fd5b50346103b457600319602036820112611007576001600160401b03600435116110075760c0600435600401916004353603011261100757610cff613595565b610d076138f3565b610d0f613911565b81610160604051610d1f8161378a565b8281528260208201528260408201528260608201528260808201528260a08201528260c0820152606060e08201528261010082015282610120820152826101408201520152610d72602460043501613a71565b90610de5610d8e610d87604460043501613a71565b9280613daf565b9260405194610d9c8661378a565b6001600160a01b03908116865216602085015260646004358181013560408701526084810135606087015260a40135608086015260a08501523360c0850190815292369161394f565b60e083015282610100830152826101208301528261014083015282610160830152610e0f826140b2565b6020828101516040516370a0823160e01b808252306004830152929091829060249082906001600160a01b03165afa908115610fca578591610fd5575b508351610ea2906001600160a01b037f00000000000000000000000046d2487cdbea04411c49e6c55ace805bfa8f5de5811691610e8b91839116613ae2565b8551604087015191906001600160a01b0316613bc4565b610eab8461410c565b602084810151604051938452306004850152839060249082906001600160a01b03165afa8015610fca578590610f96575b610ee69250613a85565b906060830151808310610f78575082516020840151604085015192517f46b2107d05bd48594fd93879b48e5037b2e2c6997667e30ef789e0ddba059bb793610f40936001600160a01b03928316938793908116911661424a565b8351602085015160409586015195516001600160a01b0391821696919092169491928392610f6e9284613aa6565b0390a36001805580f35b60449083604051916352388bed60e11b835260048301526024820152fd5b506020823d602011610fc2575b81610fb0602093836137dc565b8101031261033857610ee69151610edc565b3d9150610fa3565b6040513d87823e3d90fd5b90506020813d602011610fff575b81610ff0602093836137dc565b8101031261033857515f610e4c565b3d9150610fe3565b5080fd5b50346103b45760403660031901126103b45761104860043561102b61347f565b9080845283602052611043600160408620015461364b565b6136e8565b5080f35b50346103b45760603660031901126103b457611066613469565b61106e61347f565b9061107761353e565b6001600160a01b03918083161561034e57611096926044359216613e75565b80f35b50346103b4576110a8366134a9565b6110b0613595565b6110b86138f3565b6110c0613911565b608081013542116103a2576001600160a01b036110dc82613a71565b161561039057602081013515611917576107d08060a0830135116118f557506001600160a01b0361110f60c08301613a71565b161561034e5760249060206001600160a01b0361112b83613a71565b16604051938480926370a0823160e01b82523060048301525afa9182156102e95783926118c1575b506001600160a01b0361116582613a71565b166040516323b872dd60e01b602082015233602482015230604482015260208301356064820152606481528060a08101106001600160401b0360a0830111176118ad576111b89160a08201604052613cac565b6111e26001600160a01b036111cc83613a71565b60025460081c6001600160a01b03169116613ae2565b6112156001600160a01b036111f683613a71565b60025460208501359260089190911c6001600160a01b03169116613bc4565b6040516370a0823160e01b81523060048201526020816024817f000000000000000000000000788d96f655735f52c676a133f4dfc53cec614d4a6001600160a01b03165afa9081156118a2578491611870575b50600254849060081c6001600160a01b031661128384613a71565b90803b156115df5760408051630ffea21360e21b8152602087013560048201526001600160a01b03939093166024840152850135604483015282908290606490829084905af180156108fc5761185c575b50506040516370a0823160e01b8152306004820152906020826024817f000000000000000000000000788d96f655735f52c676a133f4dfc53cec614d4a6001600160a01b03165afa8015610fca578590611828575b6113339250613a85565b916040820135831061180757829061134e60e0840184613daf565b90506115e3575b60035461138f906001600160a01b03908116907f000000000000000000000000788d96f655735f52c676a133f4dfc53cec614d4a16613ae2565b6003546113cb9083906001600160a01b03908116907f000000000000000000000000788d96f655735f52c676a133f4dfc53cec614d4a16613bc4565b6003546001600160a01b031690856113e285613a71565b606086013593803b156115df5760405163d878016160e01b8152600481018790526001600160a01b039290921660248301526044820185905282908290606490829084905af180156108fc576115cb575b50602491905060206001600160a01b0361144c87613a71565b16604051938480926370a0823160e01b82523060048301525afa80156108d9578790611597575b61147d9250613a85565b90808210610b01575091610100917f4095541f16523f676ae1764a678013124f8fe1cca280cf8d0cdd9b3c694bd6c293602083013581115f14611590576114c8602084013582613a85565b915b82611563575b602084013582101561153e575b6001600160a01b036114ee85613a71565b169660206040519501358552602085015260408401526060830152608082015260c060a0820152601160c08201527049737375652d537761702d52656465656d60781b60e0820152a26001805580f35b61155e6020850135336001600160a01b0361155888613a71565b16613e75565b6114dd565b61158b836001600160a01b0361157887613a71565b1661158560c08801613a71565b90613e75565b6114d0565b86916114ca565b506020823d6020116115c3575b816115b1602093836137dc565b810103126103385761147d9151611473565b3d91506115a4565b6115d490613777565b6108f857855f611433565b8280fd5b905061163b6001600160a01b037f00000000000000000000000046d2487cdbea04411c49e6c55ace805bfa8f5de58116907f000000000000000000000000788d96f655735f52c676a133f4dfc53cec614d4a16613ae2565b611692836001600160a01b037f00000000000000000000000046d2487cdbea04411c49e6c55ace805bfa8f5de58116907f000000000000000000000000788d96f655735f52c676a133f4dfc53cec614d4a16613bc4565b6116a060a0830135846142a4565b61016083013580151581036108f8576101008401359160ff83168303610b71576117869261176860209360ff6116d960e08a018a613daf565b949092604051966116e98861375b565b7f000000000000000000000000788d96f655735f52c676a133f4dfc53cec614d4a6001600160a01b03168089528989018e905260408901526060880152306080808901919091528b013560a0880152151560c08701521660e085015261012080890135610100860152610140808a01359186019190915292369161394f565b908201526040518093819263d2c9041160e01b835260048301613de1565b0381887f00000000000000000000000046d2487cdbea04411c49e6c55ace805bfa8f5de56001600160a01b03165af18591816117d3575b506117cd57610ac0610a9f613d1d565b90611355565b9091506020813d6020116117ff575b816117ef602093836137dc565b810103126103385751905f6117bd565b3d91506117e2565b50604491604080519263034a66bb60e61b8452013560048301526024820152fd5b506020823d602011611854575b81611842602093836137dc565b81010312610338576113339151611329565b3d9150611835565b61186590613777565b61090757835f6112d4565b90506020813d60201161189a575b8161188b602093836137dc565b8101031261033857515f611268565b3d915061187e565b6040513d86823e3d90fd5b634e487b7160e01b5f52604160045260245ffd5b9091506020813d6020116118ed575b816118dd602093836137dc565b810103126103385751905f611153565b3d91506118d0565b6044925060a060405192633b5d56ed60e11b8452013560048301526024820152fd5b604051632006714760e01b8152600490fd5b50346103b457806003193601126103b457602090604051908152f35b50346103b457806003193601126103b45760206040516127108152f35b50346103b457806003193601126103b4576040517f000000000000000000000000788d96f655735f52c676a133f4dfc53cec614d4a6001600160a01b03168152602090f35b50346103b45760403660031901126103b45760406119c361347f565b91600435815280602052209060018060a01b03165f52602052602060ff60405f2054166040519015158152f35b50346103b457600319602036820112611007576001600160401b0360043511611007576101c090600435360301126103b457611a2a6135f0565b611a326138f3565b611a3a613911565b6084600435013542116103a257604460043501351561037e576001600160a01b03611a686004803501613a71565b161561034e57611a7c600435600401613a71565b6001600160a01b037f000000000000000000000000788d96f655735f52c676a133f4dfc53cec614d4a8116911603611e1e576001600160a01b03611ac4600435602401613a71565b161561034e576107d08060a4600435013511611e8257506001600160a01b03611af160043560c401613a71565b161561034e57611b0b60e460043501600435600401613daf565b905015610cae576001600160a01b03611b2960043561018401613a71565b1615611e3057611b3e61018460043501613a71565b6001600160a01b03611b54600435602401613a71565b166001600160a01b0390911603610390576101a4600435013515611e1e575b60405163613255ab60e01b81527f000000000000000000000000788d96f655735f52c676a133f4dfc53cec614d4a6001600160a01b031660048201819052602090829060249082905afa9081156108fc578291611dec575b50806044600435013511611dc957506040516020818101819052611d68916001600160a01b03611bfe6004803501613495565b1660408201526001600160a01b03611c1a600435602401613495565b16606082015260043560448101356080830152606481013560a0830152608481013560c083015260a481013560e08301526001600160a01b0390611c609060c401613495565b16610100820152611d1881611c96611c8260e4600435016004356004016137fd565b6101c061012085015261020084019161382e565b60ff611ca76101046004350161384e565b16610140830152600435610124810135610160840152610144810135610180840152611cd6906101640161385c565b15156101a08301526001600160a01b03611cf560043561018401613495565b166101c08301526004356101a401356101e083015203601f1981018352826137dc565b604051632e7ff4ef60e11b81529283918291906004803560440135907f000000000000000000000000788d96f655735f52c676a133f4dfc53cec614d4a6001600160a01b031690309086016138bf565b0381857f000000000000000000000000788d96f655735f52c676a133f4dfc53cec614d4a6001600160a01b03165af180156108fc57611daa575b506001805580f35b611dc29060203d6020116102e2576102d281836137dc565b505f611da2565b60449060405190632da547fb60e01b825282600435013560048301526024820152fd5b90506020813d602011611e16575b81611e07602093836137dc565b8101031261033857515f611bcb565b3d9150611dfa565b60405163b83a8a7b60e01b8152600490fd5b6001600160a01b037f000000000000000000000000788d96f655735f52c676a133f4dfc53cec614d4a811690611e6a602460043501613a71565b1614611b735760405163b83a8a7b60e01b8152600490fd5b60449060405190633b5d56ed60e11b825260a4600435013560048301526024820152fd5b50346103b457806003193601126103b457611ebf61353e565b611ec76138f3565b600160ff1960025416176002557f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2586020604051338152a180f35b50346103b457806003193601126103b45760206040517fb1ac20a777be3038119e44f95ac0fef6a80dda7484ca8661110ad49ddc2bb8178152f35b50346103b457806003193601126103b457602060ff600254166040519015158152f35b50346103b45760203660031901126103b457611f79613469565b611f8161353e565b6001600160a01b0381811691821561034e5760028054610100600160a81b03198116600893841b610100600160a81b031617909155901c167f6d32c1367cc28eab61cca04e424f151519f02908af46776ba8373eaea407baa98380a380f35b50346103b457806003193601126103b457611ff961353e565b60025460ff8116156120395760ff19166002557f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa6020604051338152a180f35b604051638dfc202b60e01b8152600490fd5b50346103b45760403660031901126103b45761206561347f565b336001600160a01b0382160361208157611048906004356136e8565b60405163334bd91960e11b8152600490fd5b50346103b45760203660031901126103b4576120ad613469565b6120b561353e565b6001600160a01b0390811690811561034e57600354826bffffffffffffffffffffffff60a01b821617600355167f634105f7bf7f464a9e13df9c8d255054a18bc8497dc837921dd7ccb3e02e623e8380a380f35b50346103b45760403660031901126103b45761104860043561212961347f565b9080845283602052612141600160408620015461364b565b61366c565b50346103b457806003193601126103b4576003546040516001600160a01b039091168152602090f35b50346103b45760203660031901126103b457600160406020926004358152808452200154604051908152f35b50346103b45760a03660031901126103b4576121b5613469565b906121be61347f565b506001600160401b03916084358381116115df576121e09036906004016134db565b90917f000000000000000000000000788d96f655735f52c676a133f4dfc53cec614d4a6001600160a01b03163303612b6957306001600160a01b0390911603612b5757303b156115df576040516321b950c360e01b81528380828061224f868860643560443560048601613f70565b038183305af19182612b43575b5050612b2657303b156115df5760405163ec5da34560e01b81528380828061228e868860643560443560048601613f70565b038183305af19182612b2f575b5050612b265760208282810103126115df57813590848211610907576101c094858385018386010312612b2257604051958601868110828211176118ad576040526122e7838501613495565b86526122f7602084860101613495565b602087015283830160408181013590880152606080820135908801526080808201359088015260a080820135908801526123339060c001613495565b60c087015260e0838501013511610907576101a0919061235d90840184830160e081013501613985565b60e08601526123716101008285010161384e565b61010086015261012081840101356101208601526101408184010135610140860152610160926123a4848383010161385c565b848701526123b761018083830101613495565b6101808701520101356101a0840152608083015142116103a2576020838101516040516370a0823160e01b81523060048201529190829060249082906001600160a01b03165afa80156102e957612af7575b50825161252d91602091612449906001600160a01b037f00000000000000000000000046d2487cdbea04411c49e6c55ace805bfa8f5de581169116613ae2565b845161248590604435906001600160a01b037f00000000000000000000000046d2487cdbea04411c49e6c55ace805bfa8f5de581169116613bc4565b60018060a01b038551169060018060a01b03838701511690606087015190608088015190880151151560ff6101008a015116906101208a0151926101408b01519460e08c015196604051986124d98a61375b565b89526044358a8a01526040890152606088015230608088015260a087015260c086015260e08501526101008401526101208301526101408201526040518093819263d2c9041160e01b835260048301613de1565b0381857f00000000000000000000000046d2487cdbea04411c49e6c55ace805bfa8f5de56001600160a01b03165af19081612acc575b5061257357610ac0610a9f613d1d565b6020828101516040516370a0823160e01b81523060048201529190829060249082906001600160a01b03165afa9081156108fc578291612a9a575b506060830151808210612a7d575061018083015181906001600160a01b031680151580612a42575b612845575b50506125eb606435604435613f8c565b8110610875577fd0049207b8f1bc71652f1b174d8daeee4d4561409e757b9b0cd137a98bf725cc906126467f000000000000000000000000788d96f655735f52c676a133f4dfc53cec614d4a6001600160a01b031680613ae2565b612687612657606435604435613f8c565b7f000000000000000000000000788d96f655735f52c676a133f4dfc53cec614d4a6001600160a01b031680613bc4565b61269e60643561269960443584613a85565b613a85565b9081612802575b84516126dd906001600160a01b037f00000000000000000000000046d2487cdbea04411c49e6c55ace805bfa8f5de581169116613ae2565b6101808501516001600160a01b0316806127e0575b5084516020860151610180909601516001600160a01b0396871696918216955016156127ae57612780604051612727816137a6565b601781527f4672617874616c2d466c6173684c6f616e2d497373756500000000000000000060208201525b604051938493604435855260443560208601526040850152606084015260a0608084015260a0830190613881565b0390a35b60206040517f439148f0bbc682ca079e46d6e2c2f0c1e3b820f1a291b069d8882abf8cf18dd98152f35b6127806040516127bd816137a6565b6011815270233930bc3a30b616a33630b9b42637b0b760791b6020820152612752565b6002546127fc9160089190911c6001600160a01b031690613ae2565b5f6126f2565b60c08501516128409083906001600160a01b03908116907f000000000000000000000000788d96f655735f52c676a133f4dfc53cec614d4a16613e75565b6126a5565b6101a085015160025490935090919061286a9060081c6001600160a01b031683613ae2565b60025461288590829060081c6001600160a01b031684613bc4565b6040516370a0823160e01b8152306004820152916020836024817f000000000000000000000000788d96f655735f52c676a133f4dfc53cec614d4a6001600160a01b03165afa928315610fca578593612a0e575b5060025460081c6001600160a01b031691823b156108f857604051630ffea21360e21b815260048101919091526001600160a01b03919091166024820152604481018490529084908290606490829084905af180156118a2579084916129fa575b50506040516370a0823160e01b8152306004820152906020826024817f000000000000000000000000788d96f655735f52c676a133f4dfc53cec614d4a6001600160a01b03165afa80156118a25784906129c6575b6129999250613a85565b908082106129a957505f806125db565b6044916040519163034a66bb60e61b835260048301526024820152fd5b506020823d6020116129f2575b816129e0602093836137dc565b8101031261033857612999915161298f565b3d91506129d3565b612a0390613777565b6115df57825f61293a565b9092506020813d602011612a3a575b81612a2a602093836137dc565b810103126103385751915f6128d9565b3d9150612a1d565b5060208501517f000000000000000000000000788d96f655735f52c676a133f4dfc53cec614d4a6001600160a01b03908116911614156125d6565b604491604051916352388bed60e11b835260048301526024820152fd5b90506020813d602011612ac4575b81612ab5602093836137dc565b8101031261033857515f6125ae565b3d9150612aa8565b602090813d8311612af0575b612ae281836137dc565b81010312610338575f612563565b503d612ad8565b602090813d8311612b1b575b612b0d81836137dc565b81010312610338575f612409565b503d612b03565b8480fd5b50505050612784565b612b3890613777565b61090757835f61229b565b612b4c90613777565b61090757835f61225c565b6040516377be635b60e11b8152600490fd5b60405163d2958ad160e01b8152600490fd5b50346103b457612b8d61042136613508565b608081015142116103a257612bee6001600160a01b037f00000000000000000000000046d2487cdbea04411c49e6c55ace805bfa8f5de58116907f000000000000000000000000788d96f655735f52c676a133f4dfc53cec614d4a16613ae2565b612c45836001600160a01b037f00000000000000000000000046d2487cdbea04411c49e6c55ace805bfa8f5de58116907f000000000000000000000000788d96f655735f52c676a133f4dfc53cec614d4a16613bc4565b80516040516370a0823160e01b815230600482015290602090829060249082906001600160a01b03165afa8015610fca576130d9575b50612d42602060018060a01b0383511660408401516080850151610160860151151560ff61010088015116610120880151916101408901519360e08a01519560405197612cc78961375b565b60018060a01b037f000000000000000000000000788d96f655735f52c676a133f4dfc53cec614d4a1689528d8a8a01526040890152606088015230608088015260a087015260c086015260e08501526101008401526101208301526101408201526040518093819263d2c9041160e01b835260048301613de1565b0381887f00000000000000000000000046d2487cdbea04411c49e6c55ace805bfa8f5de56001600160a01b03165af18591816130a5575b50612d8957610ac0610a9f613d1d565b91604082015180841061308757508151600254612db7916001600160a01b0360089290921c82169116613ae2565b8151600254612dd791859160081c6001600160a01b039081169116613bc4565b6040516370a0823160e01b81523060048201526020816024817f000000000000000000000000788d96f655735f52c676a133f4dfc53cec614d4a6001600160a01b03165afa908115610343578691613055575b50600254835160608501516001600160a01b0391821692899260089190911c16803b156115df57604051630ffea21360e21b8152600481018990526001600160a01b0394909416602485015260448401919091528290606490829084905af180156108d957613042575b506040516370a0823160e01b8152306004820152906020826024817f000000000000000000000000788d96f655735f52c676a133f4dfc53cec614d4a6001600160a01b03165afa80156108d957879061300e575b612ef29250613a85565b9060608301518083106108875750612f0a9085613f8c565b91828210610875577f4095541f16523f676ae1764a678013124f8fe1cca280cf8d0cdd9b3c694bd6c29361010093612f6f906107937f000000000000000000000000788d96f655735f52c676a133f4dfc53cec614d4a6001600160a01b031680613ae2565b9182612fcb575b60018060a01b039051169560405193818552602085015260408401526060830152608082015260c060a0820152601460c082015273466c6173684c6f616e2d537761702d497373756560601b60e0820152a280f35b60c08101516130099084906001600160a01b03908116907f000000000000000000000000788d96f655735f52c676a133f4dfc53cec614d4a16613e75565b612f76565b506020823d60201161303a575b81613028602093836137dc565b8101031261033857612ef29151612ee8565b3d915061301b565b61304e90969196613777565b945f612e94565b90506020813d60201161307f575b81613070602093836137dc565b810103126108f857515f612e2a565b3d9150613063565b604490846040519163079edc7d60e41b835260048301526024820152fd5b9091506020813d6020116130d1575b816130c1602093836137dc565b810103126108f85751905f612d79565b3d91506130b4565b602090813d83116130fd575b6130ef81836137dc565b81010312610907575f612c7b565b503d6130e5565b50346103b457806003193601126103b45760025460405160089190911c6001600160a01b03168152602090f35b50346103b457613140366134a9565b613148613595565b6131506138f3565b613158613911565b61316a61316536836139a0565b6140b2565b6020818101916001600160a01b03908161318385613a71565b1660405184816024816370a0823160e01b958682523060048301525afa9081156108d9578791613300575b50836131b984613a71565b16946131e8857f00000000000000000000000046d2487cdbea04411c49e6c55ace805bfa8f5de5168097613ae2565b613203856131f586613a71565b166040860135978891613bc4565b61321561321036866139a0565b61410c565b808561322089613a71565b169360246040518096819382523060048301525afa908115610b525788916132d0575b5061324e9250613a85565b906060810135808310610f78575090610f6e826132be6132b86132b26132957f46b2107d05bd48594fd93879b48e5037b2e2c6997667e30ef789e0ddba059bb79897613a71565b868a6132a08d613a71565b6132ac60c08901613a71565b9361424a565b92613a71565b97613a71565b92846040519586951698169684613aa6565b905082813d83116132f9575b6132e681836137dc565b81010312610b715761324e91515f613243565b503d6132dc565b90508481813d8311613326575b61331781836137dc565b81010312610b7157515f6131ae565b503d61330d565b50346103b45760403660031901126103b457613347613469565b61334f61353e565b6001600160a01b0316801561034e578180808093602435905af1613371613d1d565b501561337a5780f35b60405162461bcd60e51b815260206004820152601360248201527211551217d514905394d1915497d19052531151606a1b6044820152606490fd5b50346103b457806003193601126103b4576040517f00000000000000000000000046d2487cdbea04411c49e6c55ace805bfa8f5de56001600160a01b03168152602090f35b9050346110075760203660031901126110075760043563ffffffff60e01b81168091036115df57602092506323e30c8b60e01b811490811561343e575b5015158152f35b637965db0b60e01b811491508115613458575b505f613437565b6301ffc9a760e01b1490505f613451565b600435906001600160a01b038216820361033857565b602435906001600160a01b038216820361033857565b35906001600160a01b038216820361033857565b6003199060208183011261033857600435916001600160401b0383116103385782610180920301126103385760040190565b9181601f84011215610338578235916001600160401b038311610338576020838186019501011161033857565b6060600319820112610338576004359160243591604435906001600160401b0382116103385761353a916004016134db565b9091565b335f9081527fad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5602052604081205460ff16156135775750565b6044906040519063e2517d3f60e01b82523360048301526024820152fd5b335f9081527f71d66f16066d309d0e3c98c7bdbd7d02163436c8b639a716036c8d557942ef5960205260409020547fb1ac20a777be3038119e44f95ac0fef6a80dda7484ca8661110ad49ddc2bb8179060ff16156135775750565b335f9081527fee57cd81e84075558e8fcc182a1f4393f91fc97f963a136e66b7f949a62f319f60205260409020547f97667070c54ef182b0f5858b034beac1b6f3089aa2d3188bb1e8929f4fa9b9299060ff16156135775750565b805f525f60205260405f20335f5260205260ff60405f205416156135775750565b905f9180835282602052604083209160018060a01b03169182845260205260ff604084205416155f146136e357808352826020526040832082845260205260408320600160ff198254161790557f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d339380a4600190565b505090565b905f9180835282602052604083209160018060a01b03169182845260205260ff6040842054165f146136e35780835282602052604083208284526020526040832060ff1981541690557ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b339380a4600190565b61016081019081106001600160401b038211176118ad57604052565b6001600160401b0381116118ad57604052565b61018081019081106001600160401b038211176118ad57604052565b604081019081106001600160401b038211176118ad57604052565b608081019081106001600160401b038211176118ad57604052565b90601f801991011681019081106001600160401b038211176118ad57604052565b9035601e19823603018112156103385701602081359101916001600160401b03821161033857813603831361033857565b908060209392818452848401375f828201840152601f01601f1916010190565b359060ff8216820361033857565b3590811515820361033857565b90816020910312610338575180151581036103385790565b91908251928382525f5b8481106138ab575050825f602080949584010152601f8019910116010190565b60208183018101518483018201520161388b565b6001600160a01b0391821681529116602082015260408101919091526080606082018190526138f092910190613881565b90565b60ff600254166138ff57565b60405163d93c066560e01b8152600490fd5b600260015414613922576002600155565b604051633ee5aeb560e01b8152600490fd5b6001600160401b0381116118ad57601f01601f191660200190565b92919261395b82613934565b9161396960405193846137dc565b829481845281830111610338578281602093845f960137010152565b9080601f83011215610338578160206138f09335910161394f565b9190916101808184031261033857604051906139bb8261378a565b81936139c682613495565b83526139d460208301613495565b602084015260408201356040840152606082013560608401526080820135608084015260a082013560a0840152613a0d60c08301613495565b60c084015260e0820135906001600160401b03821161033857613a31918301613985565b60e0830152610100613a4481830161384e565b9083015261012080820135908301526101408082013590830152613a6c61016080920161385c565b910152565b356001600160a01b03811681036103385790565b91908203918211613a9257565b634e487b7160e01b5f52601160045260245ffd5b9160c0939183526020830152604082015260806060820152601060808201526f04672617874616c2d4d756c7469686f760841b60a08201520190565b60405190602082019263095ea7b360e01b938481525f8060018060a01b038094169384602488015281604488015260448752613b1d876137c1565b85169286519082855af190613b30613d1d565b82613b92575b5081613b87575b5015613b4a575b50505050565b613b7e93613b799160405191602083015260248201525f604482015260448152613b73816137c1565b82613cac565b613cac565b5f808080613b44565b90503b15155f613b3d565b80519192508115918215613baa575b5050905f613b36565b613bbd9250602080918301019101613869565b5f80613ba1565b60405163095ea7b360e01b602082018181526001600160a01b0385166024840152604480840196909652948252909290613bff6064856137dc565b83516001600160a01b0395848716915f9182919082855af190613c20613d1d565b82613c7a575b5081613c6f575b5015613c3b575b5050505050565b613c6594613b79926040519260208401521660248201525f604482015260448152613b73816137c1565b5f80808080613c34565b90503b15155f613c2d565b80519192508115918215613c92575b5050905f613c26565b613ca59250602080918301019101613869565b5f80613c89565b5f80613cd49260018060a01b03169360208151910182865af1613ccd613d1d565b9083613d4c565b8051908115159182613d02575b5050613cea5750565b60249060405190635274afe760e01b82526004820152fd5b613d159250602080918301019101613869565b155f80613ce1565b3d15613d47573d90613d2e82613934565b91613d3c60405193846137dc565b82523d5f602084013e565b606090565b90613d735750805115613d6157805190602001fd5b604051630a12f52160e11b8152600490fd5b81511580613da6575b613d84575090565b604051639996b31560e01b81526001600160a01b039091166004820152602490fd5b50803b15613d7c565b903590601e198136030182121561033857018035906001600160401b0382116103385760200191813603831361033857565b6101806138f0926020835260018060a01b03808251166020850152602082015160408501528060408301511660608501526060820151608085015260808201511660a084015260a081015160c084015260c0810151151560e084015260ff60e08201511661010090818501528101516101209081850152810151906101409182850152015191610160808201520190613881565b60405163a9059cbb60e01b60208201526001600160a01b03929092166024830152604480830193909352918152613eb191613b796064836137dc565b565b906001600160a01b0380613ec684613495565b1682526020830135602083015260408301356040830152606083013560608301526080830135608083015260a083013560a0830152613f0760c08401613495565b1660c0820152613f2e613f1d60e08401846137fd565b6101808060e086015284019161382e565b9161010060ff613f3f82840161384e565b169083015261012080820135908301526101408082013590830152613f6861016080920161385c565b151591015290565b6138f0949260609282526020820152816040820152019161382e565b91908201809211613a9257565b15613fa057565b60405162461bcd60e51b815260206004820152600d60248201526c496e7465726e616c206f6e6c7960981b6044820152606490fd5b6020818303126103385780356001600160401b039182821161033857019061018082840312610338576040519261400b8461378a565b61401483613495565b84526020830135602085015260408301356040850152606083013560608501526080830135608085015260a083013560a085015261405460c08401613495565b60c085015260e083013591821161033857614070918301613985565b60e083015261010061408381830161384e565b90830152610120808201359083015261014080820135908301526140ab61016080920161385c565b9082015290565b608081015142116103a25780516001600160a01b039081161580156140fe575b61034e5760a08201516107d080821161036057505060c0820151161561034e5760e001515115610cae57565b5080602083015116156140d2565b905f60018060a01b0380845116936141be604082015192602096878095858483809801511660608201519060808301516101608401511515906101009160ff8387015116916101209586880151956101409960e08b8b01519a01519b6040519d8e6141768161375b565b528d015260408c015260608b01523060808b015260a08a015260c089015260e088015286015284015282015260405195868094819363d2c9041160e01b835260048301613de1565b03927f00000000000000000000000046d2487cdbea04411c49e6c55ace805bfa8f5de5165af19182614221575b505061421d57506141fa613d1d565b90610ac06040519283926346d9bb5360e01b845260048401526024830190613881565b9050565b813d8311614243575b61423481836137dc565b8101031261033857825f6141eb565b503d61422a565b6001600160a01b0391821693929116831461426757505050505f90565b5f918181111561429b5761427b9250613a85565b80925b8161428a575b50505090565b61429392613e75565b5f8181614284565b5050809261427e565b61271091820390828211613a9257818102918183041490151715613a9257049056fea2646970667358221220939a0f1654e07ff9cdd3dd3500361eabbedf9f57ba5cb9d0c617bf1528a1660d64736f6c63430008140033
Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)
000000000000000000000000788d96f655735f52c676a133f4dfc53cec614d4a00000000000000000000000046d2487cdbea04411c49e6c55ace805bfa8f5de5000000000000000000000000d95f1cfd9559b6e4d75488938de161a147f9fbca000000000000000000000000acd97c7d23a0534d79439b8d065d6d9d772ec9490000000000000000000000009ce33be219e7d45d36c08e2097e4c1d156fe99c5
-----Decoded View---------------
Arg [0] : _dstable (address): 0x788D96f655735f52c676A133f4dFC53cEC614d4A
Arg [1] : _fraxswapRouter (address): 0x46D2487CdbeA04411C49e6c55aCE805bfA8f5dE5
Arg [2] : _issuer (address): 0xd95f1cFD9559B6e4D75488938DE161a147F9FbCa
Arg [3] : _redeemer (address): 0xACD97c7d23A0534d79439B8d065D6D9D772Ec949
Arg [4] : _operator (address): 0x9cE33bE219E7d45D36C08E2097e4C1d156FE99C5
-----Encoded View---------------
5 Constructor Arguments found :
Arg [0] : 000000000000000000000000788d96f655735f52c676a133f4dfc53cec614d4a
Arg [1] : 00000000000000000000000046d2487cdbea04411c49e6c55ace805bfa8f5de5
Arg [2] : 000000000000000000000000d95f1cfd9559b6e4d75488938de161a147f9fbca
Arg [3] : 000000000000000000000000acd97c7d23a0534d79439b8d065d6d9d772ec949
Arg [4] : 0000000000000000000000009ce33be219e7d45d36c08e2097e4c1d156fe99c5
Loading...
Loading
Loading...
Loading
Loading...
Loading
Net Worth in USD
$0.00
Net Worth in FRAX
0
Multichain Portfolio | 35 Chains
| Chain | Token | Portfolio % | Price | Amount | Value |
|---|
Loading...
Loading
Loading...
Loading
Loading...
Loading
[ Download: CSV Export ]
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.