Blockchain / Global / Address
SHC Volatile WSOMI/USDC.eToken
0xb05fdB355e7e249A2913b20F10586dfbe2a0b0D4
- Balance
- 0 SOMI
- Transactions sent
- 1
- Type
- Contract · 18,529 bytes of code
ERC-20
SHC Volatile WSOMI/USDC.e
- Symbol
- SHC-VOLATILE-WSOMI-USDC.e
- Decimals
- 18
- Total supply
- 7,368.604283 SHC-VOLATILE-WSOMI-USDC.e
MangroveVaultV2 Verified
- Compiler
- solc 0.8.28+commit.7893614a
- Language
- Solidity
- Optimization
- Yes, 10 runs
- EVM version
- prague
- License
- —
- Verified
- 2026-10-12
src/MangroveVaultV2.sol
and 42 more files
Solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
// External dependencies (Mangrove strategies)
import {KandelManagementRebalancing, KandelManagement} from "./base/KandelManagementRebalancing.sol";
import {AbstractKandelSeeder} from
"@mgv-strats/src/strategies/offer_maker/market_making/kandel/abstract/AbstractKandelSeeder.sol";
// External dependencies (solady)
import {ERC20} from "lib/solady/src/tokens/ERC20.sol";
import {FixedPointMathLib} from "lib/solady/src/utils/FixedPointMathLib.sol";
import {SafeTransferLib} from "lib/solady/src/utils/SafeTransferLib.sol";
// External dependencies (Mangrove core)
import {TickLib, Tick} from "@mgv/lib/core/TickLib.sol";
// Internal dependencies
import {OracleData, OracleLib} from "./libraries/OracleLib.sol";
/**
* @title MangroveVaultV2
* @notice A vault that provides liquidity to Mangrove through a Kandel market-making strategy
* @dev This contract combines ERC20 vault functionality with Kandel market-making strategy management.
* Users can deposit base and quote tokens to mint vault shares, which represent their proportional
* ownership of the vault's assets. The vault actively manages liquidity using a Kandel strategy
* with oracle-validated price ranges and includes management fee accrual capabilities.
* @author Mangrove
*/
contract MangroveVaultV2 is KandelManagementRebalancing, ERC20 {
using FixedPointMathLib for uint256;
using OracleLib for OracleData;
using SafeTransferLib for address;
/*//////////////////////////////////////////////////////////////
ERRORS
//////////////////////////////////////////////////////////////*/
/// @notice Thrown when initial mint amounts don't result in a valid tick within oracle constraints
error InvalidInitialMintAmounts();
/// @notice Thrown when the mint operation would result in fewer shares than the minimum required
error InsufficientSharesOut();
/// @notice Thrown when the burn operation would result in less tokens than the minimum required
error BurnSlippageExceeded();
/// @notice Thrown when the paused state is not changed
error PausedStateNotChanged();
/// @notice Thrown when the vault is paused
error Paused();
/// @notice Thrown when the max mint amounts are exceeded
error MaxMintAmountsExceeded();
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
/**
* @notice Emitted when management fees are accrued and minted to the fee recipient
* @param feeShares The number of vault shares minted as management fees
*/
event AccruedFees(uint256 feeShares);
/**
* @notice Emitted when tokens are received by the vault
* @param baseIn The amount of base tokens received
* @param quoteIn The amount of quote tokens received
* @param totalBaseBalance The total base token balance after receiving tokens
* @param totalQuoteBalance The total quote token balance after receiving tokens
*/
event ReceivedTokens(uint256 baseIn, uint256 quoteIn, uint256 totalBaseBalance, uint256 totalQuoteBalance);
/**
* @notice Emitted when tokens are sent from the vault
* @param baseOut The amount of base tokens sent
* @param quoteOut The amount of quote tokens sent
* @param totalBaseBalance The total base token balance after sending tokens
* @param totalQuoteBalance The total quote token balance after sending tokens
*/
event SentTokens(uint256 baseOut, uint256 quoteOut, uint256 totalBaseBalance, uint256 totalQuoteBalance);
/**
* @notice Emitted when the paused state is changed
* @param paused The new paused state
*/
event SetPaused(bool paused);
/**
* @notice Emitted when the max mint amounts are set
* @param maxBase The new max base amount
* @param maxQuote The new max quote amount
*/
event SetMaxMintAmounts(uint128 maxBase, uint128 maxQuote);
/*//////////////////////////////////////////////////////////////
CONSTANTS
//////////////////////////////////////////////////////////////*/
/// @notice Minimum liquidity that remains locked in the contract to prevent inflation attacks
uint256 private constant MINIMUM_LIQUIDITY = 1e3;
/// @notice Precision factor for management fee calculations (accounts for 1e5 precision and seconds per year)
uint256 internal constant MANAGEMENT_FEE_PRECISION = 1e5 * 365 days;
/*//////////////////////////////////////////////////////////////
IMMUTABLE VARIABLES
//////////////////////////////////////////////////////////////*/
/// @notice The name of the ERC20 token
string internal name_;
/// @notice The symbol of the ERC20 token
string internal symbol_;
/// @notice Offset multiplier used in initial share calculations to ensure reasonable share amounts
uint256 internal immutable QUOTE_OFFSET;
/*//////////////////////////////////////////////////////////////
STRUCTS
//////////////////////////////////////////////////////////////*/
/**
* @notice Parameters required to initialize the MangroveVaultV2 contract
* @param seeder The Kandel seeder contract used to deploy the Kandel instance
* @param base The base token address for the trading pair
* @param quote The quote token address for the trading pair
* @param tickSpacing The tick spacing for the trading pair
* @param manager The initial manager address
* @param managementFee The management fee in basis points (10000 = 100%)
* @param oracle The initial oracle configuration
* @param owner The owner address (inherited from OracleRange)
* @param guardian The guardian address (inherited from OracleRange)
* @param name The name for the ERC20 vault token
* @param symbol The symbol for the ERC20 vault token
* @param quoteOffsetDecimals The number of decimals used to calculate the quote offset multiplier
*/
struct VaultInitParams {
AbstractKandelSeeder seeder;
address base;
address quote;
uint256 tickSpacing;
address manager;
uint16 managementFee;
OracleData oracle;
address owner;
address guardian;
string name;
string symbol;
uint8 quoteOffsetDecimals;
}
struct MaxMintAmounts {
uint128 maxBase;
uint128 maxQuote;
}
/*//////////////////////////////////////////////////////////////
STATE VARIABLES
//////////////////////////////////////////////////////////////*/
MaxMintAmounts public maxMintAmounts;
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
/**
* @notice Initializes the MangroveVaultV2 contract with the provided parameters
* @param _params The initialization parameters containing all required configuration
* @dev Sets up the vault as an ERC20 token and initializes the underlying Kandel management system.
* The quote offset is calculated as 2 * 10^quoteOffsetDecimals to ensure reasonable share amounts
* during initial minting operations.
*/
constructor(VaultInitParams memory _params)
KandelManagementRebalancing(
_params.seeder,
_params.base,
_params.quote,
_params.tickSpacing,
_params.manager,
_params.managementFee,
_params.oracle,
_params.owner,
_params.guardian
)
{
name_ = _params.name;
symbol_ = _params.symbol;
QUOTE_OFFSET = 2 * 10 ** _params.quoteOffsetDecimals;
maxMintAmounts = MaxMintAmounts({maxBase: type(uint128).max, maxQuote: type(uint128).max});
}
/*//////////////////////////////////////////////////////////////
ERC20 METADATA FUNCTIONS
//////////////////////////////////////////////////////////////*/
/**
* @notice Returns the name of the vault token
* @return The name string of the ERC20 token
*/
function name() public view override returns (string memory) {
return name_;
}
/**
* @notice Returns the symbol of the vault token
* @return The symbol string of the ERC20 token
*/
function symbol() public view override returns (string memory) {
return symbol_;
}
/*//////////////////////////////////////////////////////////////
VAULT CALCULATION FUNCTIONS
//////////////////////////////////////////////////////////////*/
/**
* @notice Calculates the amount of shares and tokens required for a mint operation
* @param baseAmountMax The maximum amount of base tokens the user is willing to deposit
* @param quoteAmountMax The maximum amount of quote tokens the user is willing to deposit
* @return sharesOut The number of vault shares that will be minted
* @return baseIn The amount of base tokens that will be deposited
* @return quoteIn The amount of quote tokens that will be deposited
* @dev For the first mint (when total supply is 0), both max amounts are used and shares are calculated
* based on the total value with quote offset. For subsequent mints, the deposit amounts are
* proportional to existing balances, and the limiting factor determines the actual amounts used.
*/
function getMintAmounts(uint256 baseAmountMax, uint256 quoteAmountMax)
public
view
returns (uint256 sharesOut, uint256 baseIn, uint256 quoteIn)
{
(uint256 baseBalance, uint256 quoteBalance) = totalBalances();
if (baseBalance + baseAmountMax > maxMintAmounts.maxBase || quoteBalance + quoteAmountMax > maxMintAmounts.maxQuote)
{
revert MaxMintAmountsExceeded();
}
uint256 supply = totalSupply();
supply += _accruedFeeShares(state);
if (supply == 0) {
baseIn = baseAmountMax;
quoteIn = quoteAmountMax;
if (!oracle.acceptsInitialMint(baseIn + baseBalance, quoteIn + quoteBalance)) revert InvalidInitialMintAmounts();
sharesOut = (quoteIn + quoteBalance) * QUOTE_OFFSET;
} else {
sharesOut =
FixedPointMathLib.min(baseAmountMax.mulDiv(supply, baseBalance), quoteAmountMax.mulDiv(supply, quoteBalance));
baseIn = sharesOut.mulDivUp(baseBalance, supply);
quoteIn = sharesOut.mulDivUp(quoteBalance, supply);
}
}
/**
* @notice Returns current fee configuration and pending fee shares
* @return managementFee The current management fee in basis points
* @return feeRecipient The address that receives management fees
* @return pendingFeeShares The number of fee shares that can be claimed
* @dev This is a view function that provides transparency into fee accrual without triggering fee collection
*/
function feeData() external view returns (uint256 managementFee, address feeRecipient, uint256 pendingFeeShares) {
State memory s = state;
return (s.managementFee, s.feeRecipient, _accruedFeeShares(s));
}
/*//////////////////////////////////////////////////////////////
VAULT OPERATIONS
//////////////////////////////////////////////////////////////*/
/**
* @notice Mints vault shares by depositing base and quote tokens
* @param to The address that will receive the minted shares
* @param baseAmountMax The maximum amount of base tokens to deposit
* @param quoteAmountMax The maximum amount of quote tokens to deposit
* @param minSharesOut The minimum number of shares that must be received
* @return sharesOut The actual number of shares minted
* @return baseIn The actual amount of base tokens deposited
* @return quoteIn The actual amount of quote tokens deposited
* @dev This function transfers tokens from the caller, accrues any pending fees, and mints shares.
* For the first mint, minimum liquidity is permanently locked in the contract.
* Reverts if the calculated shares are below the minimum requirement.
*/
function mint(address to, uint256 baseAmountMax, uint256 quoteAmountMax, uint256 minSharesOut)
external
nonReentrant
returns (uint256 sharesOut, uint256 baseIn, uint256 quoteIn)
{
_checkPaused();
_onBeforeMint();
(sharesOut, baseIn, quoteIn) = getMintAmounts(baseAmountMax, quoteAmountMax);
if (sharesOut < minSharesOut) revert InsufficientSharesOut();
_accrueFees();
BASE.safeTransferFrom(msg.sender, address(this), baseIn);
QUOTE.safeTransferFrom(msg.sender, address(this), quoteIn);
// Emit received tokens event with current balances
(uint256 totalBaseBalance, uint256 totalQuoteBalance) = totalBalances();
emit ReceivedTokens(baseIn, quoteIn, totalBaseBalance, totalQuoteBalance);
if (totalSupply() == 0) {
_mint(address(this), MINIMUM_LIQUIDITY);
}
_mint(to, sharesOut);
if (state.inKandel) _sendTokenToKandel();
}
/**
* @notice Burns vault shares to withdraw base and quote tokens
* @param from The address from which shares will be burned
* @param receiver The address that will receive the withdrawn tokens
* @param shares The number of vault shares to burn
* @param minBaseOut The minimum amount of base tokens that must be received
* @param minQuoteOut The minimum amount of quote tokens that must be received
* @return baseOut The actual amount of base tokens withdrawn
* @return quoteOut The actual amount of quote tokens withdrawn
* @dev This function burns shares from the specified address (requires allowance if not self),
* calculates proportional token amounts, and transfers tokens to the receiver.
* May withdraw funds from the Kandel strategy if local balance is insufficient.
* Reverts if the calculated token amounts are below the minimum requirements.
*/
function burn(address from, address receiver, uint256 shares, uint256 minBaseOut, uint256 minQuoteOut)
external
nonReentrant
returns (uint256 baseOut, uint256 quoteOut)
{
_checkPaused();
_onBeforeBurn();
_accrueFees();
if (from != msg.sender) {
_spendAllowance(from, msg.sender, shares);
}
uint256 supply = totalSupply();
(uint256 baseBalance, uint256 quoteBalance) = totalBalances();
baseOut = shares.mulDiv(baseBalance, supply);
quoteOut = shares.mulDiv(quoteBalance, supply);
if (baseOut < minBaseOut) revert BurnSlippageExceeded();
if (quoteOut < minQuoteOut) revert BurnSlippageExceeded();
(baseOut, quoteOut) = _sendTokensTo(baseOut, quoteOut, receiver);
// Emit sent tokens event with current balances
emit SentTokens(baseOut, quoteOut, baseBalance - baseOut, quoteBalance - quoteOut);
_burn(from, shares);
}
/*//////////////////////////////////////////////////////////////
MANAGEMENT FUNCTIONS
//////////////////////////////////////////////////////////////*/
/**
* @inheritdoc KandelManagement
* @dev Overrides the parent implementation to ensure fees are properly accrued before changes
*/
function setFeeData(address _feeRecipient, uint16 _managementFee) public override onlyOwner {
_accrueFees();
super.setFeeData(_feeRecipient, _managementFee);
}
/**
* @notice Sets the paused state of the vault
* @param _paused The new paused state
* @dev Only the owner can set the paused state
*/
function setPaused(bool _paused) public onlyOwner {
bool paused = state.paused;
if (paused == _paused) revert PausedStateNotChanged();
state.paused = _paused;
emit SetPaused(_paused);
}
/**
* @notice Sets the max mint amounts for the vault
* @param maxBase The new max base amount
* @param maxQuote The new max quote amount
* @dev Only the owner can set the max mint amounts
*/
function setMaxMintAmounts(uint128 maxBase, uint128 maxQuote) external onlyOwner {
maxMintAmounts = MaxMintAmounts({maxBase: maxBase, maxQuote: maxQuote});
emit SetMaxMintAmounts(maxBase, maxQuote);
}
/*//////////////////////////////////////////////////////////////
INTERNAL FUNCTIONS
//////////////////////////////////////////////////////////////*/
/**
* @notice Internal function to send tokens to a recipient, withdrawing from Kandel if necessary
* @param baseAmount The amount of base tokens to send
* @param quoteAmount The amount of quote tokens to send
* @param to The recipient address
* @return sentBase The actual amount of base tokens sent (may be less than requested if insufficient balance)
* @return sentQuote The actual amount of quote tokens sent (may be less than requested if insufficient balance)
* @dev If the local balance is insufficient, this function will attempt to withdraw funds from
* the Kandel strategy before sending. The actual sent amount may be less than requested
* if there are insufficient total funds available.
*/
function _sendTokensTo(uint256 baseAmount, uint256 quoteAmount, address to)
internal
returns (uint256 sentBase, uint256 sentQuote)
{
unchecked {
uint256 localBaseBalance = BASE.balanceOf(address(this));
uint256 localQuoteBalance = QUOTE.balanceOf(address(this));
uint256 baseToWithdraw = baseAmount > localBaseBalance ? baseAmount - localBaseBalance : 0;
uint256 quoteToWithdraw = quoteAmount > localQuoteBalance ? quoteAmount - localQuoteBalance : 0;
if (baseToWithdraw > 0 || quoteToWithdraw > 0) {
KANDEL.withdrawFunds(baseToWithdraw, quoteToWithdraw, address(this));
localBaseBalance = BASE.balanceOf(address(this));
localQuoteBalance = QUOTE.balanceOf(address(this));
}
sentBase = FixedPointMathLib.min(baseAmount, localBaseBalance);
sentQuote = FixedPointMathLib.min(quoteAmount, localQuoteBalance);
BASE.safeTransfer(to, sentBase);
QUOTE.safeTransfer(to, sentQuote);
}
}
/**
* @notice Internal function to accrue and mint management fees
* @dev Calculates accrued fees based on time elapsed and management fee rate, mints shares
* to the fee recipient, and updates the last timestamp. Only accrues fees if there are
* any to be collected and updates timestamp to prevent future over-accrual.
*/
function _accrueFees() internal {
State memory s = state;
uint256 feeShares = _accruedFeeShares(s);
if (feeShares > 0) {
_mint(s.feeRecipient, feeShares);
emit AccruedFees(feeShares);
}
if (s.lastTimestamp < block.timestamp) {
state.lastTimestamp = uint40(block.timestamp);
}
}
/**
* @notice Internal view function to calculate accrued management fee shares
* @param s The current state struct containing fee configuration and last update timestamp
* @return feeShares The number of fee shares that have accrued since the last update
* @dev Calculates fees using the formula: (supply * managementFee * timeElapsed) / MANAGEMENT_FEE_PRECISION
* Returns 0 if no management fee is set or no time has elapsed since last update.
*/
function _accruedFeeShares(State memory s) internal view returns (uint256 feeShares) {
if (s.managementFee > 0) {
uint256 currentTime = block.timestamp;
if (currentTime > s.lastTimestamp) {
uint256 supply = totalSupply();
uint256 spanned = currentTime - s.lastTimestamp;
feeShares = supply.mulDiv(s.managementFee * spanned, MANAGEMENT_FEE_PRECISION);
}
}
}
/**
* @notice Internal view function to check if the vault is paused
* @dev Reverts if the vault is paused
*/
function _checkPaused() internal view {
if (state.paused) revert Paused();
}
/**
* @notice Hook function called before minting vault shares
* @dev This is a virtual function that can be overridden by inheriting contracts
* to implement custom logic that should be executed before the mint operation.
* Called after paused state check but before calculating mint amounts and
* transferring tokens. The function is executed in the context of the mint
* transaction, so it has access to msg.sender and other transaction data.
*/
function _onBeforeMint() internal virtual {}
/**
* @notice Hook function called before burning vault shares
* @dev This is a virtual function that can be overridden by inheriting contracts
* to implement custom logic that should be executed before the burn operation.
* Called after paused state check but before accruing fees, checking allowance,
* and burning shares. The function is executed in the context of the burn
* transaction, so it has access to msg.sender and other transaction data.
*/
function _onBeforeBurn() internal virtual {}
}
Other files
lib/solady/src/tokens/ERC20.sol
// SPDX-License-Identifier: MIT pragma solidity ^0.8.4; /// @notice Simple ERC20 + EIP-2612 implementation. /// @author Solady (https://github.com/vectorized/solady/blob/main/src/tokens/ERC20.sol) /// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol) /// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol) /// /// @dev Note: /// - The ERC20 standard allows minting and transferring to and from the zero address, /// minting and transferring zero tokens, as well as self-approvals. /// For performance, this implementation WILL NOT revert for such actions. /// Please add any checks with overrides if desired. /// - The `permit` function uses the ecrecover precompile (0x1). /// /// If you are overriding: /// - NEVER violate the ERC20 invariant: /// the total sum of all balances must be equal to `totalSupply()`. /// - Check that the overridden function is actually used in the function you want to /// change the behavior of. Much of the code has been manually inlined for performance. abstract contract ERC20 { /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* CUSTOM ERRORS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev The total supply has overflowed. error TotalSupplyOverflow(); /// @dev The allowance has overflowed. error AllowanceOverflow(); /// @dev The allowance has underflowed. error AllowanceUnderflow(); /// @dev Insufficient balance. error InsufficientBalance(); /// @dev Insufficient allowance. error InsufficientAllowance(); /// @dev The permit is invalid. error InvalidPermit(); /// @dev The permit has expired. error PermitExpired(); /// @dev The allowance of Permit2 is fixed at infinity. error Permit2AllowanceIsFixedAtInfinity(); /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* EVENTS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Emitted when `amount` tokens is transferred from `from` to `to`. event Transfer(address indexed from, address indexed to, uint256 amount); /// @dev Emitted when `amount` tokens is approved by `owner` to be used by `spender`. event Approval(address indexed owner, address indexed spender, uint256 amount); /// @dev `keccak256(bytes("Transfer(address,address,uint256)"))`. uint256 private constant _TRANSFER_EVENT_SIGNATURE = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef; /// @dev `keccak256(bytes("Approval(address,address,uint256)"))`. uint256 private constant _APPROVAL_EVENT_SIGNATURE = 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925; /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* STORAGE */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev The storage slot for the total supply. uint256 private constant _TOTAL_SUPPLY_SLOT = 0x05345cdf77eb68f44c; /// @dev The balance slot of `owner` is given by: /// ``` /// mstore(0x0c, _BALANCE_SLOT_SEED) /// mstore(0x00, owner) /// let balanceSlot := keccak256(0x0c, 0x20) /// ``` uint256 private constant _BALANCE_SLOT_SEED = 0x87a211a2; /// @dev The allowance slot of (`owner`, `spender`) is given by: /// ``` /// mstore(0x20, spender) /// mstore(0x0c, _ALLOWANCE_SLOT_SEED) /// mstore(0x00, owner) /// let allowanceSlot := keccak256(0x0c, 0x34) /// ``` uint256 private constant _ALLOWANCE_SLOT_SEED = 0x7f5e9f20; /// @dev The nonce slot of `owner` is given by: /// ``` /// mstore(0x0c, _NONCES_SLOT_SEED) /// mstore(0x00, owner) /// let nonceSlot := keccak256(0x0c, 0x20) /// ``` uint256 private constant _NONCES_SLOT_SEED = 0x38377508; /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* CONSTANTS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev `(_NONCES_SLOT_SEED << 16) | 0x1901`. uint256 private constant _NONCES_SLOT_SEED_WITH_SIGNATURE_PREFIX = 0x383775081901; /// @dev `keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")`. bytes32 private constant _DOMAIN_TYPEHASH = 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f; /// @dev `keccak256("1")`. /// If you need to use a different version, override `_versionHash`. bytes32 private constant _DEFAULT_VERSION_HASH = 0xc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6; /// @dev `keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)")`. bytes32 private constant _PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9; /// @dev The canonical Permit2 address. /// For signature-based allowance granting for single transaction ERC20 `transferFrom`. /// Enabled by default. To disable, override `_givePermit2InfiniteAllowance()`. /// [Github](https://github.com/Uniswap/permit2) /// [Etherscan](https://etherscan.io/address/0x000000000022D473030F116dDEE9F6B43aC78BA3) address internal constant _PERMIT2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3; /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* ERC20 METADATA */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Returns the name of the token. function name() public view virtual returns (string memory); /// @dev Returns the symbol of the token. function symbol() public view virtual returns (string memory); /// @dev Returns the decimals places of the token. function decimals() public view virtual returns (uint8) { return 18; } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* ERC20 */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Returns the amount of tokens in existence. function totalSupply() public view virtual returns (uint256 result) { /// @solidity memory-safe-assembly assembly { result := sload(_TOTAL_SUPPLY_SLOT) } } /// @dev Returns the amount of tokens owned by `owner`. function balanceOf(address owner) public view virtual returns (uint256 result) { /// @solidity memory-safe-assembly assembly { mstore(0x0c, _BALANCE_SLOT_SEED) mstore(0x00, owner) result := sload(keccak256(0x0c, 0x20)) } } /// @dev Returns the amount of tokens that `spender` can spend on behalf of `owner`. function allowance(address owner, address spender) public view virtual returns (uint256 result) { if (_givePermit2InfiniteAllowance()) { if (spender == _PERMIT2) return type(uint256).max; } /// @solidity memory-safe-assembly assembly { mstore(0x20, spender) mstore(0x0c, _ALLOWANCE_SLOT_SEED) mstore(0x00, owner) result := sload(keccak256(0x0c, 0x34)) } } /// @dev Sets `amount` as the allowance of `spender` over the caller's tokens. /// /// Emits a {Approval} event. function approve(address spender, uint256 amount) public virtual returns (bool) { if (_givePermit2InfiniteAllowance()) { /// @solidity memory-safe-assembly assembly { // If `spender == _PERMIT2 && amount != type(uint256).max`. if iszero(or(xor(shr(96, shl(96, spender)), _PERMIT2), iszero(not(amount)))) { mstore(0x00, 0x3f68539a) // `Permit2AllowanceIsFixedAtInfinity()`. revert(0x1c, 0x04) } } } /// @solidity memory-safe-assembly assembly { // Compute the allowance slot and store the amount. mstore(0x20, spender) mstore(0x0c, _ALLOWANCE_SLOT_SEED) mstore(0x00, caller()) sstore(keccak256(0x0c, 0x34), amount) // Emit the {Approval} event. mstore(0x00, amount) log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, caller(), shr(96, mload(0x2c))) } return true; } /// @dev Transfer `amount` tokens from the caller to `to`. /// /// Requirements: /// - `from` must at least have `amount`. /// /// Emits a {Transfer} event. function transfer(address to, uint256 amount) public virtual returns (bool) { _beforeTokenTransfer(msg.sender, to, amount); /// @solidity memory-safe-assembly assembly { // Compute the balance slot and load its value. mstore(0x0c, _BALANCE_SLOT_SEED) mstore(0x00, caller()) let fromBalanceSlot := keccak256(0x0c, 0x20) let fromBalance := sload(fromBalanceSlot) // Revert if insufficient balance. if gt(amount, fromBalance) { mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. revert(0x1c, 0x04) } // Subtract and store the updated balance. sstore(fromBalanceSlot, sub(fromBalance, amount)) // Compute the balance slot of `to`. mstore(0x00, to) let toBalanceSlot := keccak256(0x0c, 0x20) // Add and store the updated balance of `to`. // Will not overflow because the sum of all user balances // cannot exceed the maximum uint256 value. sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) // Emit the {Transfer} event. mstore(0x20, amount) log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, caller(), shr(96, mload(0x0c))) } _afterTokenTransfer(msg.sender, to, amount); return true; } /// @dev Transfers `amount` tokens from `from` to `to`. /// /// Note: Does not update the allowance if it is the maximum uint256 value. /// /// Requirements: /// - `from` must at least have `amount`. /// - The caller must have at least `amount` of allowance to transfer the tokens of `from`. /// /// Emits a {Transfer} event. function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) { _beforeTokenTransfer(from, to, amount); // Code duplication is for zero-cost abstraction if possible. if (_givePermit2InfiniteAllowance()) { /// @solidity memory-safe-assembly assembly { let from_ := shl(96, from) if iszero(eq(caller(), _PERMIT2)) { // Compute the allowance slot and load its value. mstore(0x20, caller()) mstore(0x0c, or(from_, _ALLOWANCE_SLOT_SEED)) let allowanceSlot := keccak256(0x0c, 0x34) let allowance_ := sload(allowanceSlot) // If the allowance is not the maximum uint256 value. if not(allowance_) { // Revert if the amount to be transferred exceeds the allowance. if gt(amount, allowance_) { mstore(0x00, 0x13be252b) // `InsufficientAllowance()`. revert(0x1c, 0x04) } // Subtract and store the updated allowance. sstore(allowanceSlot, sub(allowance_, amount)) } } // Compute the balance slot and load its value. mstore(0x0c, or(from_, _BALANCE_SLOT_SEED)) let fromBalanceSlot := keccak256(0x0c, 0x20) let fromBalance := sload(fromBalanceSlot) // Revert if insufficient balance. if gt(amount, fromBalance) { mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. revert(0x1c, 0x04) } // Subtract and store the updated balance. sstore(fromBalanceSlot, sub(fromBalance, amount)) // Compute the balance slot of `to`. mstore(0x00, to) let toBalanceSlot := keccak256(0x0c, 0x20) // Add and store the updated balance of `to`. // Will not overflow because the sum of all user balances // cannot exceed the maximum uint256 value. sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) // Emit the {Transfer} event. mstore(0x20, amount) log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, from_), shr(96, mload(0x0c))) } } else { /// @solidity memory-safe-assembly assembly { let from_ := shl(96, from) // Compute the allowance slot and load its value. mstore(0x20, caller()) mstore(0x0c, or(from_, _ALLOWANCE_SLOT_SEED)) let allowanceSlot := keccak256(0x0c, 0x34) let allowance_ := sload(allowanceSlot) // If the allowance is not the maximum uint256 value. if not(allowance_) { // Revert if the amount to be transferred exceeds the allowance. if gt(amount, allowance_) { mstore(0x00, 0x13be252b) // `InsufficientAllowance()`. revert(0x1c, 0x04) } // Subtract and store the updated allowance. sstore(allowanceSlot, sub(allowance_, amount)) } // Compute the balance slot and load its value. mstore(0x0c, or(from_, _BALANCE_SLOT_SEED)) let fromBalanceSlot := keccak256(0x0c, 0x20) let fromBalance := sload(fromBalanceSlot) // Revert if insufficient balance. if gt(amount, fromBalance) { mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. revert(0x1c, 0x04) } // Subtract and store the updated balance. sstore(fromBalanceSlot, sub(fromBalance, amount)) // Compute the balance slot of `to`. mstore(0x00, to) let toBalanceSlot := keccak256(0x0c, 0x20) // Add and store the updated balance of `to`. // Will not overflow because the sum of all user balances // cannot exceed the maximum uint256 value. sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) // Emit the {Transfer} event. mstore(0x20, amount) log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, from_), shr(96, mload(0x0c))) } } _afterTokenTransfer(from, to, amount); return true; } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* EIP-2612 */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev For more performance, override to return the constant value /// of `keccak256(bytes(name()))` if `name()` will never change. function _constantNameHash() internal view virtual returns (bytes32 result) {} /// @dev If you need a different value, override this function. function _versionHash() internal view virtual returns (bytes32 result) { result = _DEFAULT_VERSION_HASH; } /// @dev For inheriting contracts to increment the nonce. function _incrementNonce(address owner) internal virtual { /// @solidity memory-safe-assembly assembly { mstore(0x0c, _NONCES_SLOT_SEED) mstore(0x00, owner) let nonceSlot := keccak256(0x0c, 0x20) sstore(nonceSlot, add(1, sload(nonceSlot))) } } /// @dev Returns the current nonce for `owner`. /// This value is used to compute the signature for EIP-2612 permit. function nonces(address owner) public view virtual returns (uint256 result) { /// @solidity memory-safe-assembly assembly { // Compute the nonce slot and load its value. mstore(0x0c, _NONCES_SLOT_SEED) mstore(0x00, owner) result := sload(keccak256(0x0c, 0x20)) } } /// @dev Sets `value` as the allowance of `spender` over the tokens of `owner`, /// authorized by a signed approval by `owner`. /// /// Emits a {Approval} event. function permit( address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) public virtual { if (_givePermit2InfiniteAllowance()) { /// @solidity memory-safe-assembly assembly { // If `spender == _PERMIT2 && value != type(uint256).max`. if iszero(or(xor(shr(96, shl(96, spender)), _PERMIT2), iszero(not(value)))) { mstore(0x00, 0x3f68539a) // `Permit2AllowanceIsFixedAtInfinity()`. revert(0x1c, 0x04) } } } bytes32 nameHash = _constantNameHash(); // We simply calculate it on-the-fly to allow for cases where the `name` may change. if (nameHash == bytes32(0)) nameHash = keccak256(bytes(name())); bytes32 versionHash = _versionHash(); /// @solidity memory-safe-assembly assembly { // Revert if the block timestamp is greater than `deadline`. if gt(timestamp(), deadline) { mstore(0x00, 0x1a15a3cc) // `PermitExpired()`. revert(0x1c, 0x04) } let m := mload(0x40) // Grab the free memory pointer. // Clean the upper 96 bits. owner := shr(96, shl(96, owner)) spender := shr(96, shl(96, spender)) // Compute the nonce slot and load its value. mstore(0x0e, _NONCES_SLOT_SEED_WITH_SIGNATURE_PREFIX) mstore(0x00, owner) let nonceSlot := keccak256(0x0c, 0x20) let nonceValue := sload(nonceSlot) // Prepare the domain separator. mstore(m, _DOMAIN_TYPEHASH) mstore(add(m, 0x20), nameHash) mstore(add(m, 0x40), versionHash) mstore(add(m, 0x60), chainid()) mstore(add(m, 0x80), address()) mstore(0x2e, keccak256(m, 0xa0)) // Prepare the struct hash. mstore(m, _PERMIT_TYPEHASH) mstore(add(m, 0x20), owner) mstore(add(m, 0x40), spender) mstore(add(m, 0x60), value) mstore(add(m, 0x80), nonceValue) mstore(add(m, 0xa0), deadline) mstore(0x4e, keccak256(m, 0xc0)) // Prepare the ecrecover calldata. mstore(0x00, keccak256(0x2c, 0x42)) mstore(0x20, and(0xff, v)) mstore(0x40, r) mstore(0x60, s) let t := staticcall(gas(), 1, 0x00, 0x80, 0x20, 0x20) // If the ecrecover fails, the returndatasize will be 0x00, // `owner` will be checked if it equals the hash at 0x00, // which evaluates to false (i.e. 0), and we will revert. // If the ecrecover succeeds, the returndatasize will be 0x20, // `owner` will be compared against the returned address at 0x20. if iszero(eq(mload(returndatasize()), owner)) { mstore(0x00, 0xddafbaef) // `InvalidPermit()`. revert(0x1c, 0x04) } // Increment and store the updated nonce. sstore(nonceSlot, add(nonceValue, t)) // `t` is 1 if ecrecover succeeds. // Compute the allowance slot and store the value. // The `owner` is already at slot 0x20. mstore(0x40, or(shl(160, _ALLOWANCE_SLOT_SEED), spender)) sstore(keccak256(0x2c, 0x34), value) // Emit the {Approval} event. log3(add(m, 0x60), 0x20, _APPROVAL_EVENT_SIGNATURE, owner, spender) mstore(0x40, m) // Restore the free memory pointer. mstore(0x60, 0) // Restore the zero pointer. } } /// @dev Returns the EIP-712 domain separator for the EIP-2612 permit. function DOMAIN_SEPARATOR() public view virtual returns (bytes32 result) { bytes32 nameHash = _constantNameHash(); // We simply calculate it on-the-fly to allow for cases where the `name` may change. if (nameHash == bytes32(0)) nameHash = keccak256(bytes(name())); bytes32 versionHash = _versionHash(); /// @solidity memory-safe-assembly assembly { let m := mload(0x40) // Grab the free memory pointer. mstore(m, _DOMAIN_TYPEHASH) mstore(add(m, 0x20), nameHash) mstore(add(m, 0x40), versionHash) mstore(add(m, 0x60), chainid()) mstore(add(m, 0x80), address()) result := keccak256(m, 0xa0) } } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* INTERNAL MINT FUNCTIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Mints `amount` tokens to `to`, increasing the total supply. /// /// Emits a {Transfer} event. function _mint(address to, uint256 amount) internal virtual { _beforeTokenTransfer(address(0), to, amount); /// @solidity memory-safe-assembly assembly { let totalSupplyBefore := sload(_TOTAL_SUPPLY_SLOT) let totalSupplyAfter := add(totalSupplyBefore, amount) // Revert if the total supply overflows. if lt(totalSupplyAfter, totalSupplyBefore) { mstore(0x00, 0xe5cfe957) // `TotalSupplyOverflow()`. revert(0x1c, 0x04) } // Store the updated total supply. sstore(_TOTAL_SUPPLY_SLOT, totalSupplyAfter) // Compute the balance slot and load its value. mstore(0x0c, _BALANCE_SLOT_SEED) mstore(0x00, to) let toBalanceSlot := keccak256(0x0c, 0x20) // Add and store the updated balance. sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) // Emit the {Transfer} event. mstore(0x20, amount) log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, 0, shr(96, mload(0x0c))) } _afterTokenTransfer(address(0), to, amount); } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* INTERNAL BURN FUNCTIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Burns `amount` tokens from `from`, reducing the total supply. /// /// Emits a {Transfer} event. function _burn(address from, uint256 amount) internal virtual { _beforeTokenTransfer(from, address(0), amount); /// @solidity memory-safe-assembly assembly { // Compute the balance slot and load its value. mstore(0x0c, _BALANCE_SLOT_SEED) mstore(0x00, from) let fromBalanceSlot := keccak256(0x0c, 0x20) let fromBalance := sload(fromBalanceSlot) // Revert if insufficient balance. if gt(amount, fromBalance) { mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. revert(0x1c, 0x04) } // Subtract and store the updated balance. sstore(fromBalanceSlot, sub(fromBalance, amount)) // Subtract and store the updated total supply. sstore(_TOTAL_SUPPLY_SLOT, sub(sload(_TOTAL_SUPPLY_SLOT), amount)) // Emit the {Transfer} event. mstore(0x00, amount) log3(0x00, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, shl(96, from)), 0) } _afterTokenTransfer(from, address(0), amount); } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* INTERNAL TRANSFER FUNCTIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Moves `amount` of tokens from `from` to `to`. function _transfer(address from, address to, uint256 amount) internal virtual { _beforeTokenTransfer(from, to, amount); /// @solidity memory-safe-assembly assembly { let from_ := shl(96, from) // Compute the balance slot and load its value. mstore(0x0c, or(from_, _BALANCE_SLOT_SEED)) let fromBalanceSlot := keccak256(0x0c, 0x20) let fromBalance := sload(fromBalanceSlot) // Revert if insufficient balance. if gt(amount, fromBalance) { mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. revert(0x1c, 0x04) } // Subtract and store the updated balance. sstore(fromBalanceSlot, sub(fromBalance, amount)) // Compute the balance slot of `to`. mstore(0x00, to) let toBalanceSlot := keccak256(0x0c, 0x20) // Add and store the updated balance of `to`. // Will not overflow because the sum of all user balances // cannot exceed the maximum uint256 value. sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) // Emit the {Transfer} event. mstore(0x20, amount) log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, from_), shr(96, mload(0x0c))) } _afterTokenTransfer(from, to, amount); } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* INTERNAL ALLOWANCE FUNCTIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Updates the allowance of `owner` for `spender` based on spent `amount`. function _spendAllowance(address owner, address spender, uint256 amount) internal virtual { if (_givePermit2InfiniteAllowance()) { if (spender == _PERMIT2) return; // Do nothing, as allowance is infinite. } /// @solidity memory-safe-assembly assembly { // Compute the allowance slot and load its value. mstore(0x20, spender) mstore(0x0c, _ALLOWANCE_SLOT_SEED) mstore(0x00, owner) let allowanceSlot := keccak256(0x0c, 0x34) let allowance_ := sload(allowanceSlot) // If the allowance is not the maximum uint256 value. if not(allowance_) { // Revert if the amount to be transferred exceeds the allowance. if gt(amount, allowance_) { mstore(0x00, 0x13be252b) // `InsufficientAllowance()`. revert(0x1c, 0x04) } // Subtract and store the updated allowance. sstore(allowanceSlot, sub(allowance_, amount)) } } } /// @dev Sets `amount` as the allowance of `spender` over the tokens of `owner`. /// /// Emits a {Approval} event. function _approve(address owner, address spender, uint256 amount) internal virtual { if (_givePermit2InfiniteAllowance()) { /// @solidity memory-safe-assembly assembly { // If `spender == _PERMIT2 && amount != type(uint256).max`. if iszero(or(xor(shr(96, shl(96, spender)), _PERMIT2), iszero(not(amount)))) { mstore(0x00, 0x3f68539a) // `Permit2AllowanceIsFixedAtInfinity()`. revert(0x1c, 0x04) } } } /// @solidity memory-safe-assembly assembly { let owner_ := shl(96, owner) // Compute the allowance slot and store the amount. mstore(0x20, spender) mstore(0x0c, or(owner_, _ALLOWANCE_SLOT_SEED)) sstore(keccak256(0x0c, 0x34), amount) // Emit the {Approval} event. mstore(0x00, amount) log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, shr(96, owner_), shr(96, mload(0x2c))) } } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* HOOKS TO OVERRIDE */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Hook that is called before any transfer of tokens. /// This includes minting and burning. 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. function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {} /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* PERMIT2 */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Returns whether to fix the Permit2 contract's allowance at infinity. /// /// This value should be kept constant after contract initialization, /// or else the actual allowance values may not match with the {Approval} events. /// For best performance, return a compile-time constant for zero-cost abstraction. function _givePermit2InfiniteAllowance() internal view virtual returns (bool) { return true; } }lib/mangrove-strats/src/strategies/routers/abstract/AbstractRouter.sol
// SPDX-License-Identifier: MIT pragma solidity ^0.8.10; import {AccessControlled} from "@mgv-strats/src/strategies/utils/AccessControlled.sol"; import {RoutingOrderLib as RL} from "./RoutingOrderLib.sol"; /// @title AbstractRouter /// @notice Partial implementation and requirements for liquidity routers. abstract contract AbstractRouter is AccessControlled(msg.sender) { ///@notice This modifier verifies that `msg.sender` an allowed caller of this router. modifier onlyBound() { require(isBound(msg.sender), "AccessControlled/Invalid"); _; } ///@notice This modifier verifies that `msg.sender` is the admin or an allowed caller of this router. modifier boundOrAdmin() { require(msg.sender == admin() || isBound(msg.sender), "AccessControlled/Invalid"); _; } ///@notice logging bound maker contract ///@param maker the maker address. This is indexed, so that RPC calls can filter on it. ///@notice by emitting this data, an indexer will be able to keep track of what maker contracts are allowed to call this router. event MakerBind(address indexed maker); ///@notice logging unbound maker contract ///@param maker the maker address. This is indexed, so that RPC calls can filter on it. ///@notice by emitting this data, an indexer will be able to keep track of what maker contracts are allowed to call this router. event MakerUnbind(address indexed maker); ///@notice getter for the `makers: addr => bool` mapping ///@param mkr the address of a maker contract ///@return true if `mkr` is authorized to call this router. function isBound(address mkr) public view virtual returns (bool) { return RL.boundMakerContracts()[mkr]; } ///@notice pulls liquidity from the reserve and sends it to the calling maker contract. ///@param routingOrder the arguments of the pull order ///@param amount of token that needs to be routed ///@param strict if false the router may pull at more than `amount` to msg.sender. Otherwise it pulls at least `amount`. ///@return pulled the amount of `routingOrder.token` that has been sent to `msg.sender` function pull(RL.RoutingOrder calldata routingOrder, uint amount, bool strict) external onlyBound returns (uint pulled) { if (strict && amount == 0) { return 0; } pulled = __pull__(routingOrder, amount, strict); } ///@notice router dependent hook to customize pull orders. ///@param routingOrder the arguments of the pull order ///@param amount of token that needs to be routed ///@param strict if false the router may pull at more than `amount` to msg.sender. Otherwise it pulls at least `amount`. ///@return pulled the amount of `routingOrder.token` that has been sent to `msg.sender` function __pull__(RL.RoutingOrder memory routingOrder, uint amount, bool strict) internal virtual returns (uint); ///@notice pushes liquidity from msg.sender to the reserve ///@param routingOrder the arguments of the push order ///@param amount of token that needs to be routed ///@return pushed the amount of `routingOrder.token` that has been taken from `msg.sender` function push(RL.RoutingOrder calldata routingOrder, uint amount) external onlyBound returns (uint pushed) { if (amount == 0) { return 0; } pushed = __push__(routingOrder, amount); } ///@notice router dependent hook to customize pull orders. ///@param routingOrder the arguments of the pull order ///@param amount of token that needs to be routed ///@return pushed the amount of `routingOrder.token` that has been sent to `msg.sender` function __push__(RL.RoutingOrder memory routingOrder, uint amount) internal virtual returns (uint pushed); ///@notice iterative `push` of the whole maker contract's balance ///@dev minimizes external calls in case several assets needs to be pushed via the router. ///@param routingOrders to be executed function flush(RL.RoutingOrder[] memory routingOrders) external onlyBound { for (uint i = 0; i < routingOrders.length; ++i) { uint amount = routingOrders[i].token.balanceOf(msg.sender); if (amount > 0) { require(__push__(routingOrders[i], amount) == amount, "router/flushFailed"); } } } ///@notice adds a maker contract address to the allowed makers of this router ///@dev this function is callable by router's admin to bootstrap, but later on an allowed maker contract can add another address ///@param makerContract the maker contract address function bind(address makerContract) public onlyAdmin { RL.boundMakerContracts()[makerContract] = true; emit MakerBind(makerContract); } ///@notice removes a maker contract address from the allowed makers of this router ///@param makerContract the maker contract address function _unbind(address makerContract) internal { RL.boundMakerContracts()[makerContract] = false; emit MakerUnbind(makerContract); } ///@notice removes `msg.sender` from the allowed makers of this router function unbind() external onlyBound { _unbind(msg.sender); } ///@notice removes a makerContract from the allowed makers of this router ///@param makerContract the maker contract address function unbind(address makerContract) external onlyAdmin { _unbind(makerContract); } /** * @notice Returns the token balance available for a specific routing order * @param routingOrder The routing order to check the balance for * @return balance The balance of the token in the routing order */ function tokenBalanceOf(RL.RoutingOrder calldata routingOrder) public view virtual returns (uint balance); }src/interfaces/IOracle.sol
// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import {Tick} from "@mgv/lib/core/TickLib.sol"; /** * @title IOracle * @notice Interface for oracle contracts that provide price information in the form of a tick */ interface IOracle { /** * @notice Retrieves the current price tick from the oracle * @return Tick The current price tick * @dev The tick represents the price in a logarithmic scale, as used in Mangrove's order book */ function tick() external view returns (Tick); }lib/mangrove-core/lib/core/OfferExtra.sol
// SPDX-License-Identifier: MIT pragma solidity ^0.8.17; import "@mgv/lib/core/TickTreeLib.sol"; import "@mgv/lib/core/TickLib.sol"; import {Offer,OfferUnpacked,OfferLib} from "@mgv/src/preprocessed/Offer.post.sol"; /* cleanup-mask: 0s at location of fields to hide from maker, 1s elsewhere */ uint constant HIDE_FIELDS_FROM_MAKER_MASK = ~(OfferLib.prev_mask_inv | OfferLib.next_mask_inv); /* Extra functions for the offer */ library OfferExtra { /* Compute wants from tick and gives */ function wants(Offer offer) internal pure returns (uint) { return offer.tick().inboundFromOutboundUp(offer.gives()); } /* Sugar to test offer liveness */ function isLive(Offer offer) internal pure returns (bool resp) { uint gives = offer.gives(); assembly ("memory-safe") { resp := iszero(iszero(gives)) } } /* Get the bin where the offer is stored given an offer list that has `tickSpacing` */ function bin(Offer offer, uint tickSpacing) internal pure returns (Bin) { return offer.tick().nearestBin(tickSpacing); } /* Removes prev/next pointers from an offer before sending it to the maker. Ensures that the maker has no information about the state of the book when it gets called. */ function clearFieldsForMaker(Offer offer) internal pure returns (Offer) { unchecked { return Offer.wrap( Offer.unwrap(offer) & HIDE_FIELDS_FROM_MAKER_MASK); } } } /* Extra functions for the struct version of the offer */ library OfferUnpackedExtra { /* Compute wants from tick and gives */ function wants(OfferUnpacked memory offer) internal pure returns (uint) { return offer.tick.inboundFromOutboundUp(offer.gives); } /* Sugar to test offer liveness */ function isLive(OfferUnpacked memory offer) internal pure returns (bool resp) { uint gives = offer.gives; assembly ("memory-safe") { resp := iszero(iszero(gives)) } } /* Removes prev/next pointers from an offer before sending it to the maker; Ensures that the maker has no information about the state of the book when it gets called. */ function bin(OfferUnpacked memory offer, uint tickSpacing) internal pure returns (Bin) { return offer.tick.nearestBin(tickSpacing); } }lib/mangrove-core/lib/TransferLib.sol
// SPDX-License-Identifier: MIT pragma solidity ^0.8.10; import "@mgv/src/core/MgvLib.sol"; ///@title This library helps with safely interacting with ERC20 tokens ///@notice Transferring 0 or to self will be skipped. ///@notice ERC20 tokens returning bool instead of reverting are handled. library TransferLib { ///@notice This transfer amount of token to recipient address ///@param token Token to be transferred ///@param recipient Address of the recipient the tokens will be transferred to ///@param amount The amount of tokens to be transferred ///@return true if transfer was successful; otherwise, false. function transferToken(IERC20 token, address recipient, uint amount) internal returns (bool) { if (amount == 0) { return true; } if (recipient == address(this)) { return token.balanceOf(recipient) >= amount; } return _transferToken(token, recipient, amount); } ///@notice This transfer amount of token to recipient address ///@param token Token to be transferred ///@param recipient Address of the recipient the tokens will be transferred to ///@param amount The amount of tokens to be transferred ///@return true if transfer was successful; otherwise, false. function _transferToken(IERC20 token, address recipient, uint amount) private returns (bool) { // This low level call will not revert but instead return success=false if callee reverts, so we // verify that it does not revert by checking success, but we also have to check // the returned data if any since some ERC20 tokens to not strictly follow the standard of reverting // but instead return false. (bool success, bytes memory data) = address(token).call(abi.encodeWithSelector(token.transfer.selector, recipient, amount)); return (success && (data.length == 0 || abi.decode(data, (bool)))); } ///@notice This transfer amount of token to recipient address from spender address ///@param token Token to be transferred ///@param spender Address of the spender, where the tokens will be transferred from ///@param recipient Address of the recipient, where the tokens will be transferred to ///@param amount The amount of tokens to be transferred ///@return true if transfer was successful; otherwise, false. function transferTokenFrom(IERC20 token, address spender, address recipient, uint amount) internal returns (bool) { if (amount == 0) { return true; } if (spender == recipient) { return token.balanceOf(spender) >= amount; } // optimization to avoid requiring contract to approve itself if (spender == address(this)) { return _transferToken(token, recipient, amount); } return _transferTokenFrom(token, spender, recipient, amount); } ///@notice This transfer amount of token to recipient address from spender address ///@param token Token to be transferred ///@param spender Address of the spender, where the tokens will be transferred from ///@param recipient Address of the recipient, where the tokens will be transferred to ///@param amount The amount of tokens to be transferred ///@return true if transfer was successful; otherwise, false. function _transferTokenFrom(IERC20 token, address spender, address recipient, uint amount) private returns (bool) { // This low level call will not revert but instead return success=false if callee reverts, so we // verify that it does not revert by checking success, but we also have to check // the returned data if there since some ERC20 tokens to not strictly follow the standard of reverting // but instead return false. (bool success, bytes memory data) = address(token).call(abi.encodeWithSelector(token.transferFrom.selector, spender, recipient, amount)); return (success && (data.length == 0 || abi.decode(data, (bool)))); } ///@notice ERC20 approval, handling non standard approvals that do not return a value ///@param token the ERC20 ///@param spender the address whose allowance is to be given ///@param amount of the allowance ///@return true if approval was successful; otherwise, false. function _approveToken(IERC20 token, address spender, uint amount) private returns (bool) { // This low level call will not revert but instead return success=false if callee reverts, so we // verify that it does not revert by checking success, but we also have to check // the returned data if any since some ERC20 tokens to not strictly follow the standard of reverting // but instead return false. (bool success, bytes memory data) = address(token).call(abi.encodeWithSelector(token.approve.selector, spender, amount)); return (success && (data.length == 0 || abi.decode(data, (bool)))); } ///@notice ERC20 approval, handling non standard approvals that do not return a value ///@param token the ERC20 ///@param spender the address whose allowance is to be given ///@param amount of the allowance ///@return true if approval was successful; otherwise, false. function approveToken(IERC20 token, address spender, uint amount) internal returns (bool) { return _approveToken(token, spender, amount); } }src/base/OracleRange.sol
// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import {Ownable} from "lib/solady/src/auth/Ownable.sol"; import {OracleData, OracleLib} from "../libraries/OracleLib.sol"; import {Tick} from "@mgv/lib/core/TickLib.sol"; /** * @title OracleRange * @notice A contract that manages oracle updates with a timelock mechanism and guardian oversight * @dev This contract implements a two-step oracle update process: * 1. Owner proposes a new oracle configuration * 2. After timelock period expires, owner can accept the proposal * 3. Guardian can reject proposals at any time during the timelock * @author Mangrove */ contract OracleRange is Ownable { using OracleLib for OracleData; /*////////////////////////////////////////////////////////////// ERRORS //////////////////////////////////////////////////////////////*/ /// @notice Thrown when caller is not the guardian error NotGuardian(); /// @notice Thrown when trying to accept an oracle that is still timelocked error OracleTimelocked(); /// @notice Thrown when trying to propose an invalid oracle configuration error InvalidOracle(); /*////////////////////////////////////////////////////////////// EVENTS //////////////////////////////////////////////////////////////*/ /** * @notice Emitted when a new oracle is proposed * @param key The hash of the proposed oracle data * @param oracle The proposed oracle data */ event ProposedOracle(bytes32 indexed key, OracleData oracle); /** * @notice Emitted when a proposed oracle is accepted and becomes active * @param key The hash of the accepted oracle data */ event AcceptedOracle(bytes32 indexed key); /** * @notice Emitted when a proposed oracle is rejected by the guardian * @param key The hash of the rejected oracle data */ event RejectedOracle(bytes32 indexed key); /** * @notice Emitted when a new guardian is designated * @param oldGuardian The address of the previous guardian * @param newGuardian The address of the new guardian */ event GuardianChanged(address indexed oldGuardian, address indexed newGuardian); /*////////////////////////////////////////////////////////////// STATE VARIABLES //////////////////////////////////////////////////////////////*/ /// @notice The currently active oracle configuration OracleData public oracle; /// @notice The proposed oracle configuration awaiting timelock completion OracleData public proposedOracle; /// @notice Address that can reject proposed oracle updates /// @dev Guardian provides an additional security layer against malicious oracle proposals address public guardian; /*////////////////////////////////////////////////////////////// CONSTRUCTOR //////////////////////////////////////////////////////////////*/ /** * @notice Initializes the OracleRange contract * @param _oracle Initial oracle configuration to set as active * @param _owner Address that will own the contract (can propose/accept oracle updates) * @param _guardian Address that can reject oracle proposals * @dev Sets the initial oracle's proposedAt timestamp to current block timestamp * @dev Emits events for initial guardian and oracle state for proper indexing */ constructor(OracleData memory _oracle, address _owner, address _guardian) { // Set proposal timestamp for initial oracle to current time _oracle.proposedAt = uint40(block.timestamp); if (!_oracle.isValid()) revert InvalidOracle(); oracle = _oracle; _initializeOwner(_owner); guardian = _guardian; // Emit events for initial state for indexing purposes bytes32 oracleKey = keccak256(abi.encode(_oracle)); emit GuardianChanged(address(0), _guardian); emit ProposedOracle(oracleKey, _oracle); emit AcceptedOracle(oracleKey); } /*////////////////////////////////////////////////////////////// INTERNAL FUNCTIONS //////////////////////////////////////////////////////////////*/ /** * @notice Internal function to guard owner initialization * @return bool Always returns true to allow owner initialization only once * @dev Required override from Ownable to enable owner initialization in constructor */ function _guardInitializeOwner() internal pure override returns (bool) { return true; } /** * @notice Internal function to check if the caller is the guardian * @dev Reverts with NotGuardian if caller is not the guardian */ function _checkGuardian() internal view { if (msg.sender != guardian) revert NotGuardian(); } /*////////////////////////////////////////////////////////////// EXTERNAL FUNCTIONS //////////////////////////////////////////////////////////////*/ /** * @notice Proposes a new oracle configuration * @param _oracle The new oracle configuration to propose * @dev Only owner can propose. Validates oracle before acceptance and sets proposedAt timestamp * @dev The proposed oracle will be subject to timelock defined in the current oracle * @dev Reverts with InvalidOracle if the oracle configuration cannot provide valid tick values */ function proposeOracle(OracleData memory _oracle) external onlyOwner { // Validate that the oracle can provide valid tick values if (!_oracle.isValid()) revert InvalidOracle(); // Set proposal timestamp to current block time _oracle.proposedAt = uint40(block.timestamp); proposedOracle = _oracle; emit ProposedOracle(keccak256(abi.encode(_oracle)), _oracle); } /** * @notice Accepts the currently proposed oracle and makes it active * @dev Only owner can accept. Reverts if oracle is still timelocked * @dev Once accepted, the proposed oracle becomes the active oracle */ function acceptOracle() external onlyOwner { OracleData memory _oracle = proposedOracle; // Check if timelock period has elapsed if (oracle.timelocked(_oracle.proposedAt)) revert OracleTimelocked(); oracle = _oracle; emit AcceptedOracle(keccak256(abi.encode(_oracle))); } /** * @notice Rejects the currently proposed oracle * @dev Only guardian can reject. Clears the proposed oracle and emits RejectedOracle event * @dev Guardian can reject at any time during the timelock period */ function rejectOracle() external { _checkGuardian(); OracleData memory _oracle = proposedOracle; delete proposedOracle; // Clear the proposed oracle emit RejectedOracle(keccak256(abi.encode(_oracle))); } /** * @notice Sets a new guardian address * @param newGuardian The address of the new guardian * @dev Only the current guardian can designate a new guardian * @dev The new guardian will have the ability to reject oracle proposals */ function setGuardian(address newGuardian) external { _checkGuardian(); address oldGuardian = guardian; guardian = newGuardian; emit GuardianChanged(oldGuardian, newGuardian); } /** * @notice Returns the current tick value along with oracle metadata * @return currentTick The current tick value from the active oracle * @return isStatic Whether the oracle uses a static value or external oracle * @return maxDeviation The maximum allowed deviation in ticks from the oracle price * @dev This function may revert if the external oracle is unavailable or returns invalid data */ function getCurrentTickInfo() external view returns (Tick currentTick, bool isStatic, uint16 maxDeviation) { currentTick = oracle.tick(); isStatic = oracle.isStatic; maxDeviation = oracle.maxDeviation; } }lib/mangrove-core/lib/core/LocalExtra.sol
// SPDX-License-Identifier: MIT pragma solidity ^0.8.17; import {Bin,TickTreeLib,Field} from "@mgv/lib/core/TickTreeLib.sol"; import {Density, DensityLib} from "@mgv/lib/core/DensityLib.sol"; import {Local,LocalUnpacked,LocalLib} from "@mgv/src/preprocessed/Local.post.sol"; // cleanup-mask: 0s at location of fields to hide from maker, 1s elsewhere uint constant HIDE_FIELDS_FROM_MAKER_MASK = ~(LocalLib.binPosInLeaf_mask_inv | LocalLib.level3_mask_inv | LocalLib.level2_mask_inv | LocalLib.level1_mask_inv | LocalLib.root_mask_inv | LocalLib.last_mask_inv); /* Extra functions for local */ library LocalExtra { /* Sets the density in fixed-point 96X32 format (it is stored in floating-point, see `DensityLib` for more information). */ function densityFrom96X32(Local local, uint density96X32) internal pure returns (Local) { unchecked { return local.density(DensityLib.from96X32(density96X32)); }} /* Returns the gasbase in gas (it is stored in kilogas) */ function offer_gasbase(Local local) internal pure returns (uint) { unchecked { return local.kilo_offer_gasbase() * 1e3; }} /* Sets the gasbase in gas (it is stored in kilogas) */ function offer_gasbase(Local local,uint val) internal pure returns (Local) { unchecked { return local.kilo_offer_gasbase(val/1e3); }} /* Returns the bin that contains the best offer in \`local\`'s offer list */ function bestBin(Local local) internal pure returns (Bin) { unchecked { return TickTreeLib.bestBinFromLocal(local); }} /* Erases field that give information about the current structure of the offer list. */ function clearFieldsForMaker(Local local) internal pure returns (Local) { unchecked { return Local.wrap( Local.unwrap(local) & HIDE_FIELDS_FROM_MAKER_MASK); }} } /* Extra functions for the struct version of local */ library LocalUnpackedExtra { /* Sets the density in fixed-point 96X32 format (it is stored in floating-point, see `DensityLib` for more information). */ function densityFrom96X32(LocalUnpacked memory local, uint density96X32) internal pure { unchecked { local.density = DensityLib.from96X32(density96X32); }} /* Returns the gasbase in gas (it is stored in kilogas) */ function offer_gasbase(LocalUnpacked memory local) internal pure returns (uint) { unchecked { return local.kilo_offer_gasbase * 1e3; }} /* Sets the gasbase in gas (it is stored in kilogas) */ function offer_gasbase(LocalUnpacked memory local,uint val) internal pure { unchecked { local.kilo_offer_gasbase = val/1e3; }} /* Returns the bin that contains the best offer in \`local\`'s offer list */ function bestBin(LocalUnpacked memory local) internal pure returns (Bin) { unchecked { return TickTreeLib.bestBinFromBranch(local.binPosInLeaf,local.level3,local.level2,local.level1,local.root); }} }lib/mangrove-strats/src/strategies/offer_maker/market_making/kandel/abstract/HasIndexedBidsAndAsks.sol
// SPDX-License-Identifier: MIT pragma solidity ^0.8.10; import {IHasOfferListOfOfferType, OfferType} from "./TradesBaseQuotePair.sol"; ///@title Adds a [0..length] index <--> offerId map to a strat. ///@dev utilizes the `IHasOfferListOfOfferType` contract. abstract contract HasIndexedBidsAndAsks is IHasOfferListOfOfferType { ///@notice the length of the index has been set. ///@param value the length. ///@notice By emitting this data, an indexer will be able to keep track of what length is used. event SetLength(uint value); ///@notice a new offer of type `ba` with `offerId` was created at price `index` ///@param ba the offer type ///@param index the index ///@param offerId the Mangrove offer id. ///@notice By emitting this data, an indexer will be able to keep track of what offer has what index. event SetIndexMapping(OfferType indexed ba, uint index, uint offerId); ///@notice the length of the map. uint internal length; ///@notice Mangrove's offer id of an ask at a given index. mapping(uint => uint) private askOfferIdOfIndex; ///@notice Mangrove's offer id of a bid at a given index. mapping(uint => uint) private bidOfferIdOfIndex; ///@notice An inverse mapping of askOfferIdOfIndex. E.g., indexOfAskOfferId[42] is the index in askOfferIdOfIndex at which ask of id #42 on Mangrove is stored. mapping(uint => uint) private indexOfAskOfferId; ///@notice An inverse mapping of bidOfferIdOfIndex. E.g., indexOfBidOfferId[42] is the index in bidOfferIdOfIndex at which bid of id #42 on Mangrove is stored. mapping(uint => uint) private indexOfBidOfferId; ///@notice maps index of offers to offer id on Mangrove. ///@param ba the offer type ///@param index the index ///@return offerId the Mangrove offer id. function offerIdOfIndex(OfferType ba, uint index) public view returns (uint offerId) { return ba == OfferType.Ask ? askOfferIdOfIndex[index] : bidOfferIdOfIndex[index]; } ///@notice Maps an offer type and Mangrove offer id to index. ///@param ba the offer type ///@param offerId the Mangrove offer id. ///@return index the index. function indexOfOfferId(OfferType ba, uint offerId) public view returns (uint index) { return ba == OfferType.Ask ? indexOfAskOfferId[offerId] : indexOfBidOfferId[offerId]; } ///@notice Sets the Mangrove offer id for an index and vice versa. ///@param ba the offer type ///@param index the index ///@param offerId the Mangrove offer id. function setIndexMapping(OfferType ba, uint index, uint offerId) internal { if (ba == OfferType.Ask) { indexOfAskOfferId[offerId] = index; askOfferIdOfIndex[index] = offerId; } else { indexOfBidOfferId[offerId] = index; bidOfferIdOfIndex[index] = offerId; } emit SetIndexMapping(ba, index, offerId); } ///@notice sets the length of the map. ///@param length_ the new length. function setLength(uint length_) internal { length = length_; emit SetLength(length_); } }lib/mangrove-strats/src/strategies/MangroveOffer.sol
// SPDX-License-Identifier: MIT pragma solidity ^0.8.10; import {AccessControlled} from "@mgv-strats/src/strategies/utils/AccessControlled.sol"; import {IOfferLogic} from "@mgv-strats/src/strategies/interfaces/IOfferLogic.sol"; import {MgvLib, IERC20, OLKey, OfferDetail} from "@mgv/src/core/MgvLib.sol"; import {IMangrove} from "@mgv/src/IMangrove.sol"; import {AbstractRouter, RL} from "@mgv-strats/src/strategies/routers/abstract/AbstractRouter.sol"; import {TransferLib} from "@mgv/lib/TransferLib.sol"; import {Tick} from "@mgv/lib/core/TickLib.sol"; /// @title This contract is the basic building block for Mangrove strats. /// @notice It contains the mandatory interface expected by Mangrove (`IOfferLogic` is `IMaker`) and enforces additional functions implementations (via `IOfferLogic`). /// @dev Naming scheme: /// `f() public`: can be used, as is, in all descendants of `this` contract /// `_f() internal`: descendant of this contract should provide a public wrapper for this function, with necessary guards. /// `__f__() virtual internal`: descendant of this contract should override this function to specialize it to the needs of the strat. abstract contract MangroveOffer is AccessControlled, IOfferLogic { ///@notice The Mangrove deployment that is allowed to call `this` for trade execution and posthook. IMangrove public immutable MGV; /// @notice router contract which implements routing functions -- this can be 0x in Direct strats AbstractRouter public immutable ROUTER_IMPLEMENTATION; ///@notice The offer was successfully reposted. bytes32 internal constant REPOST_SUCCESS = "offer/updated"; ///@notice New offer successfully created. bytes32 internal constant NEW_OFFER_SUCCESS = "offer/created"; ///@notice The offer was completely filled. bytes32 internal constant COMPLETE_FILL = "offer/filled"; ///@notice Mandatory function to allow `this` to receive native tokens ///@dev this happens when `this` calls `withdrawFromMangrove` with `deprovision=true` or if `this` becomes the recipient of a `makerExecute` bounty. receive() external payable virtual {} /** * @notice `MangroveOffer`'s constructor * @param mgv The Mangrove deployment that is allowed to call `this` for trade execution and posthook. * @param routerImplementation sets the type of router that the router factory can spawn */ constructor(IMangrove mgv, AbstractRouter routerImplementation) AccessControlled(msg.sender) { require(address(mgv) != address(0), "MgvOffer/0xMangrove"); MGV = mgv; ROUTER_IMPLEMENTATION = routerImplementation; // this may be 0x } ///***************************** /// Mandatory callback functions ///***************************** ///@notice `makerExecute` is the callback function to execute all offers that were posted on Mangrove by `this` contract. ///@param order a data structure that recapitulates the taker order and the offer as it was posted on mangrove ///@return ret a bytes32 word to pass information (if needed) to the posthook (under `result.makerData`) ///@dev it may not be overriden although it can be customized using `__lastLook__`, `__put__` and `__get__` hooks. /// NB #1: if `makerExecute` reverts, the offer will be considered to be refusing the trade. /// NB #2: `makerExecute` may return a `bytes32` word to pass information to posthook w/o using storage reads/writes. /// NB #3: Reneging on trade will have the following effects: /// * Offer is removed from the Offer List /// * Offer bounty will be withdrawn from offer provision and sent to the offer taker. The remaining provision will be credited to `this` contract's account on Mangrove function makerExecute(MgvLib.SingleOrder calldata order) external override onlyCaller(address(MGV)) returns (bytes32 ret) { // Invoke hook that implements a last look check during execution - it may renege on trade by reverting. ret = __lastLook__(order); // Invoke hook to put the inbound_tkn, which are brought by the taker, into a specific reserve. require(__put__(order.takerGives, order) == 0, "mgvOffer/abort/putFailed"); // Invoke hook to fetch the outbound_tkn, which are promised to the taker, from a specific reserve. require(__get__(order.takerWants, order) == 0, "mgvOffer/abort/getFailed"); } /// @notice `makerPosthook` is the callback function that is called by Mangrove *after* the offer execution. /// @notice reverting during its execution will not renege on trade. Revert reason (casted to 32 bytes) is then logged by Mangrove in event `PosthookFail`. /// @param order a data structure that recapitulates the taker order and the offer as it was posted on mangrove /// @param result a data structure that gathers information about trade execution /// @dev It cannot be overridden but can be customized via the hooks `__posthookSuccess__`, `__posthookFallback__` and `__handleResidualProvision__` (see below). function makerPosthook(MgvLib.SingleOrder calldata order, MgvLib.OrderResult calldata result) external override onlyCaller(address(MGV)) { if (result.mgvData == "mgv/tradeSuccess") { __posthookSuccess__(order, result.makerData); } else { // logging what went wrong during `makerExecute` emit LogIncident(order.olKey.hash(), order.offerId, result.makerData, result.mgvData); // calling strat specific handlers in case of failure __posthookFallback__(order, result); __handleResidualProvision__(order); } } ///@notice whether this contract has enabled liquidity routing ///@return true if this contracts routes liquidity using an external router function _isRouting() internal view returns (bool) { return address(ROUTER_IMPLEMENTATION) != address(0); } ///@notice takes care of status for reposting residual offer in case of a partial fill and logging of potential issues. ///@param order a recap of the taker order ///@param makerData generated during `makerExecute` so as to log it if necessary ///@param repostStatus from the posthook that handles residual reposting function logRepostStatus(MgvLib.SingleOrder calldata order, bytes32 makerData, bytes32 repostStatus) internal { // reposting below density is not considered an incident in order to allow the contract not to check density before posting residual if ( repostStatus == COMPLETE_FILL || repostStatus == REPOST_SUCCESS || repostStatus == "mgv/writeOffer/density/tooLow" ) { return; } else { // Offer failed to repost for bad reason, logging the incident emit LogIncident(order.olKey.hash(), order.offerId, makerData, repostStatus); } } /// @inheritdoc IOfferLogic function approve(IERC20 token, address spender, uint amount) public override onlyAdmin returns (bool) { require(TransferLib.approveToken(token, spender, amount), "mgvOffer/approve/failed"); return true; } /// @inheritdoc IOfferLogic function withdrawFromMangrove(uint amount, address payable receiver) public onlyAdmin { if (amount == type(uint).max) { amount = MGV.balanceOf(address(this)); } // the require below is necessary if the `receive()` function is overriden require(MGV.withdraw(amount), "mgvOffer/withdrawFail"); (bool noRevert,) = receiver.call{value: amount}(""); // if `receiver` is actually not payable require(noRevert, "mgvOffer/weiTransferFail"); } ///@notice Hook that implements where the inbound token, which are brought by the Offer Taker, should go during Taker Order's execution. ///@param amount of `inbound` tokens that are on `this` contract's balance and still need to be deposited somewhere ///@param order is a recall of the taker order that is at the origin of the current trade. ///@return missingPut (<=`amount`) is the amount of `inbound` tokens whose deposit location has not been decided (possibly because of a failure) during this function execution ///@dev if the last nested call to `__put__` returns a non zero value, trade execution will revert function __put__(uint amount, MgvLib.SingleOrder calldata order) internal virtual returns (uint missingPut); ///@notice Hook that implements where the outbound token, which are promised to the taker, should be fetched from, during Taker Order's execution. ///@param amount of `outbound` tokens that still needs to be brought to the balance of `this` contract when entering this function ///@param order is a recall of the taker order that is at the origin of the current trade. ///@return missingGet (<=`amount`), which is the amount of `outbound` tokens still need to be fetched at the end of this function ///@dev if the last nested call to `__get__` returns a non zero value, trade execution will revert function __get__(uint amount, MgvLib.SingleOrder calldata order) internal virtual returns (uint missingGet); /// @notice Hook that implements a last look check during Taker Order's execution. /// @param order is a recall of the taker order that is at the origin of the current trade. /// @return data is a message that will be passed to posthook provided `makerExecute` does not revert. /// @dev __lastLook__ should revert if trade is to be reneged on. If not, returned `bytes32` are passed to `makerPosthook` in the `makerData` field. /// @custom:hook overrides of this hook should be conservative and call `super.__lastLook__(order)`. function __lastLook__(MgvLib.SingleOrder calldata order) internal virtual returns (bytes32 data) { order; //shh data = bytes32(0); } /// @inheritdoc IOfferLogic function activate(IERC20 token) public virtual override { require(TransferLib.approveToken(token, address(MGV), type(uint).max), "MgvOffer/ActivationFailed"); } ///@notice Post-hook that implements fallback behavior when Taker Order's execution failed unexpectedly. ///@param order is a recall of the taker order that is at the origin of the current trade. ///@param result contains information about trade. ///@return data contains verdict and reason about the executed trade. ///@dev `result.mgvData` is Mangrove's verdict about trade success ///@dev `result.makerData` either contains the first 32 bytes of revert reason if `makerExecute` reverted or the returned `bytes32`. /// @custom:hook overrides of this hook should be conservative and call `super.__posthookFallback__(order, result)` function __posthookFallback__(MgvLib.SingleOrder calldata order, MgvLib.OrderResult calldata result) internal virtual returns (bytes32 data) { order; result; data = bytes32(0); } ///@notice Given the current taker order that (partially) consumes an offer, this hook is used to declare how much `order.olKey.outbound_tkn` the offer gives after it is reposted, while also allowing adjustment to the tick. ///@param order is a recall of the taker order that is being treated. ///@return newGives the new volume of `outbound_tkn` the offer will give if fully taken. ///@return newTick the new tick of the reposted offer. ///@dev default is to require the original amount of tokens minus those that have been sent to the taker during trade execution and keep the tick. function __residualValues__(MgvLib.SingleOrder calldata order) internal virtual returns (uint newGives, Tick newTick) { newGives = order.offer.gives() - order.takerWants; newTick = order.offer.tick(); } ///@notice Hook that defines what needs to be done to the part of an offer provision that was added to the balance of `this` on Mangrove after an offer has failed. ///@param order is a recall of the taker order that failed function __handleResidualProvision__(MgvLib.SingleOrder calldata order) internal virtual { order; // we leave the provision on Mangrove } ///@notice Post-hook that implements default behavior when Taker Order's execution succeeded. ///@param order is a recall of the taker order that is at the origin of the current trade. ///@param makerData is the returned value of the `__lastLook__` hook, triggered during trade execution. The special value `"lastLook/retract"` should be treated as an instruction not to repost the offer on the list. ///@return data can be: /// * `COMPLETE_FILL` when offer was completely filled /// * returned data of `_updateOffer` signalling the status of the reposting attempt. /// @custom:hook overrides of this hook should be conservative and call `super.__posthookSuccess__(order, maker_data)` function __posthookSuccess__(MgvLib.SingleOrder calldata order, bytes32 makerData) internal virtual returns (bytes32 data) { // now trying to repost residual (uint newGives, Tick newTick) = __residualValues__(order); // Density check at each repost would be too gas costly. // We only treat the special case of `gives==0` or `wants==0` (total fill). // Note: wants (given by `inboundFromOutbound`) can be 0 due to rounding given the price. We could repost to get rid of the last gives at 0 wants, // but the maker does not need to give away these tokens for free, so we skip it. // Offer below the density will cause Mangrove to throw so we encapsulate the call to `updateOffer` in order not to revert posthook for posting at dust level. if (newGives == 0 || newTick.inboundFromOutbound(newGives) == 0) { return COMPLETE_FILL; } data = _updateOffer( OfferArgs({ olKey: order.olKey, tick: newTick, gives: newGives, gasreq: order.offerDetail.gasreq(), gasprice: order.offerDetail.gasprice(), noRevert: true, fund: 0 }), order.offerId ); // logs if anything went wrong logRepostStatus(order, makerData, data); } ///@notice Updates the offer specified by `offerId` on Mangrove with the parameters in `args`. ///@param args A memory struct containing the offer parameters to update. ///@param offerId An unsigned integer representing the identifier of the offer to be updated. ///@return status a `bytes32` value representing either `REPOST_SUCCESS` if the update is successful, or an error message if an error occurs and `OfferArgs.noRevert` is `true`. If `OfferArgs.noRevert` is `false`, the function reverts with the error message as the reason. function _updateOffer(OfferArgs memory args, uint offerId) internal virtual returns (bytes32); ///@notice computes the provision that can be redeemed if deprovisioning a certain offer ///@param olKey the offer list key. ///@param offerId the id of the offer ///@return provision the provision that can be redeemed function _provisionOf(OLKey memory olKey, uint offerId) internal view returns (uint provision) { OfferDetail offerDetail = MGV.offerDetails(olKey, offerId); unchecked { provision = offerDetail.gasprice() * 1e6 * (offerDetail.offer_gasbase() + offerDetail.gasreq()); } } }lib/mangrove-strats/src/strategies/offer_maker/market_making/kandel/abstract/TradesBaseQuotePair.sol
// SPDX-License-Identifier: MIT pragma solidity ^0.8.10; import {OLKey} from "@mgv/src/core/MgvLib.sol"; import {IERC20} from "@mgv/lib/IERC20.sol"; ///@title a bid or an ask. enum OfferType { Bid, Ask } ///@title Interface contract for strats needing offer type to offer list mapping. abstract contract IHasOfferListOfOfferType { ///@notice turns an offer type into an (outbound_tkn, inbound_tkn, tickSpacing) pair identifying an offer list. ///@param ba whether one wishes to access the offer lists where asks or bids are posted. ///@return olKey the olKey defining the token pair function offerListOfOfferType(OfferType ba) internal view virtual returns (OLKey memory olKey); ///@notice returns the offer type of the offer list whose outbound token is given in the argument. ///@param outbound_tkn the outbound token ///@return ba the offer type function offerTypeOfOutbound(IERC20 outbound_tkn) internal view virtual returns (OfferType ba); ///@notice returns the outbound token for the offer type ///@param ba the offer type ///@return token the outbound token function outboundOfOfferType(OfferType ba) internal view virtual returns (IERC20 token); } ///@title Adds basic base/quote trading pair for bids and asks and couples it to Mangrove's gives, wants, outbound, inbound terminology. ///@dev Implements the IHasOfferListOfOfferType interface contract. abstract contract TradesBaseQuotePair is IHasOfferListOfOfferType { ///@notice base of the market Kandel is making IERC20 public immutable BASE; ///@notice quote of the market Kandel is making IERC20 public immutable QUOTE; ///@notice tickSpacing of the market Kandel is making uint public immutable TICK_SPACING; ///@notice The traded offer list ///@param olKeyHash of the market Kandel is making ///@notice we only emit this, so that the events for a Kandel is self contained. If one uses the KandelSeeder to deploy, then this information is already available from NewKandel or NewAaveKandel events. event OfferListKey(bytes32 olKeyHash); ///@notice Constructor ///@param olKeyBaseQuote The OLKey for the outbound_tkn base and inbound_tkn quote offer list Kandel will act on, the flipped OLKey is used for the opposite offer list. constructor(OLKey memory olKeyBaseQuote) { BASE = IERC20(olKeyBaseQuote.outbound_tkn); QUOTE = IERC20(olKeyBaseQuote.inbound_tkn); TICK_SPACING = olKeyBaseQuote.tickSpacing; emit OfferListKey(olKeyBaseQuote.hash()); } ///@inheritdoc IHasOfferListOfOfferType function offerListOfOfferType(OfferType ba) internal view override returns (OLKey memory olKey) { return ba == OfferType.Bid ? OLKey(address(QUOTE), address(BASE), TICK_SPACING) : OLKey(address(BASE), address(QUOTE), TICK_SPACING); } ///@inheritdoc IHasOfferListOfOfferType function offerTypeOfOutbound(IERC20 outbound_tkn) internal view override returns (OfferType) { return outbound_tkn == BASE ? OfferType.Ask : OfferType.Bid; } ///@inheritdoc IHasOfferListOfOfferType function outboundOfOfferType(OfferType ba) internal view override returns (IERC20 token) { token = ba == OfferType.Ask ? BASE : QUOTE; } ///@notice returns the dual offer type ///@param ba whether the offer is an ask or a bid ///@return baDual is the dual offer type (ask for bid and conversely) function dual(OfferType ba) internal pure returns (OfferType baDual) { return OfferType((uint(ba) + 1) % 2); } }lib/solady/src/utils/LibCall.sol
// SPDX-License-Identifier: MIT pragma solidity ^0.8.4; /// @notice Library for making calls. /// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/LibCall.sol) /// @author Modified from ExcessivelySafeCall (https://github.com/nomad-xyz/ExcessivelySafeCall) /// /// @dev Note: /// - The arguments of the functions may differ from the libraries. /// Please read the functions carefully before use. library LibCall { /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* CUSTOM ERRORS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev The target of the call is not a contract. error TargetIsNotContract(); /// @dev The data is too short to contain a function selector. error DataTooShort(); /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* CONTRACT CALL OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ // These functions will revert if called on a non-contract // (i.e. address without code). // They will bubble up the revert if the call fails. /// @dev Makes a call to `target`, with `data` and `value`. function callContract(address target, uint256 value, bytes memory data) internal returns (bytes memory result) { /// @solidity memory-safe-assembly assembly { result := mload(0x40) if iszero(call(gas(), target, value, add(data, 0x20), mload(data), codesize(), 0x00)) { // Bubble up the revert if the call reverts. returndatacopy(result, 0x00, returndatasize()) revert(result, returndatasize()) } if iszero(returndatasize()) { if iszero(extcodesize(target)) { mstore(0x00, 0x5a836a5f) // `TargetIsNotContract()`. revert(0x1c, 0x04) } } mstore(result, returndatasize()) // Store the length. let o := add(result, 0x20) returndatacopy(o, 0x00, returndatasize()) // Copy the returndata. mstore(0x40, add(o, returndatasize())) // Allocate the memory. } } /// @dev Makes a call to `target`, with `data`. function callContract(address target, bytes memory data) internal returns (bytes memory result) { /// @solidity memory-safe-assembly assembly { result := mload(0x40) if iszero(call(gas(), target, 0, add(data, 0x20), mload(data), codesize(), 0x00)) { // Bubble up the revert if the call reverts. returndatacopy(result, 0x00, returndatasize()) revert(result, returndatasize()) } if iszero(returndatasize()) { if iszero(extcodesize(target)) { mstore(0x00, 0x5a836a5f) // `TargetIsNotContract()`. revert(0x1c, 0x04) } } mstore(result, returndatasize()) // Store the length. let o := add(result, 0x20) returndatacopy(o, 0x00, returndatasize()) // Copy the returndata. mstore(0x40, add(o, returndatasize())) // Allocate the memory. } } /// @dev Makes a static call to `target`, with `data`. function staticCallContract(address target, bytes memory data) internal view returns (bytes memory result) { /// @solidity memory-safe-assembly assembly { result := mload(0x40) if iszero(staticcall(gas(), target, add(data, 0x20), mload(data), codesize(), 0x00)) { // Bubble up the revert if the call reverts. returndatacopy(result, 0x00, returndatasize()) revert(result, returndatasize()) } if iszero(returndatasize()) { if iszero(extcodesize(target)) { mstore(0x00, 0x5a836a5f) // `TargetIsNotContract()`. revert(0x1c, 0x04) } } mstore(result, returndatasize()) // Store the length. let o := add(result, 0x20) returndatacopy(o, 0x00, returndatasize()) // Copy the returndata. mstore(0x40, add(o, returndatasize())) // Allocate the memory. } } /// @dev Makes a delegate call to `target`, with `data`. function delegateCallContract(address target, bytes memory data) internal returns (bytes memory result) { /// @solidity memory-safe-assembly assembly { result := mload(0x40) if iszero(delegatecall(gas(), target, add(data, 0x20), mload(data), codesize(), 0x00)) { // Bubble up the revert if the call reverts. returndatacopy(result, 0x00, returndatasize()) revert(result, returndatasize()) } if iszero(returndatasize()) { if iszero(extcodesize(target)) { mstore(0x00, 0x5a836a5f) // `TargetIsNotContract()`. revert(0x1c, 0x04) } } mstore(result, returndatasize()) // Store the length. let o := add(result, 0x20) returndatacopy(o, 0x00, returndatasize()) // Copy the returndata. mstore(0x40, add(o, returndatasize())) // Allocate the memory. } } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* TRY CALL OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ // These functions enable gas limited calls to be performed, // with a cap on the number of return data bytes to be copied. // The can be used to ensure that the calling contract will not // run out-of-gas. /// @dev Makes a call to `target`, with `data` and `value`. /// The call is given a gas limit of `gasStipend`, /// and up to `maxCopy` bytes of return data can be copied. function tryCall( address target, uint256 value, uint256 gasStipend, uint16 maxCopy, bytes memory data ) internal returns (bool success, bool exceededMaxCopy, bytes memory result) { /// @solidity memory-safe-assembly assembly { result := mload(0x40) success := call(gasStipend, target, value, add(data, 0x20), mload(data), codesize(), 0x00) let n := returndatasize() if gt(returndatasize(), and(0xffff, maxCopy)) { n := and(0xffff, maxCopy) exceededMaxCopy := 1 } mstore(result, n) // Store the length. let o := add(result, 0x20) returndatacopy(o, 0x00, n) // Copy the returndata. mstore(0x40, add(o, n)) // Allocate the memory. } } /// @dev Makes a call to `target`, with `data`. /// The call is given a gas limit of `gasStipend`, /// and up to `maxCopy` bytes of return data can be copied. function tryStaticCall(address target, uint256 gasStipend, uint16 maxCopy, bytes memory data) internal view returns (bool success, bool exceededMaxCopy, bytes memory result) { /// @solidity memory-safe-assembly assembly { result := mload(0x40) success := staticcall(gasStipend, target, add(data, 0x20), mload(data), codesize(), 0x00) let n := returndatasize() if gt(returndatasize(), and(0xffff, maxCopy)) { n := and(0xffff, maxCopy) exceededMaxCopy := 1 } mstore(result, n) // Store the length. let o := add(result, 0x20) returndatacopy(o, 0x00, n) // Copy the returndata. mstore(0x40, add(o, n)) // Allocate the memory. } } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* OTHER OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Bubbles up the revert. function bubbleUpRevert(bytes memory revertReturnData) internal pure { /// @solidity memory-safe-assembly assembly { revert(add(0x20, revertReturnData), mload(revertReturnData)) } } /// @dev In-place replaces the function selector of encoded contract call data. function setSelector(bytes4 newSelector, bytes memory data) internal pure { /// @solidity memory-safe-assembly assembly { if iszero(gt(mload(data), 0x03)) { mstore(0x00, 0x0acec8bd) // `DataTooShort()`. revert(0x1c, 0x04) } let o := add(data, 0x20) mstore(o, or(shr(32, shl(32, mload(o))), newSelector)) } } }lib/mangrove-core/src/preprocessed/Local.post.sol
// SPDX-License-Identifier: MIT pragma solidity ^0.8.13; /* ************************************************** * GENERATED FILE. DO NOT EDIT. * ************************************************** */ /* since you can't convert bool to uint in an expression without conditionals, * we add a file-level function and rely on compiler optimization */ function uint_of_bool(bool b) pure returns (uint u) { assembly ("memory-safe") { u := b } } import "@mgv/lib/core/Constants.sol"; struct LocalUnpacked { bool active; uint fee; Density density; uint binPosInLeaf; Field level3; Field level2; Field level1; Field root; uint kilo_offer_gasbase; bool lock; uint last; } //some type safety for each struct type Local is uint; using LocalLib for Local global; ////////////// ADDITIONAL DEFINITIONS, IF ANY //////////////// import {Density, DensityLib} from "@mgv/lib/core/DensityLib.sol"; import {Bin,TickTreeLib,Field} from "@mgv/lib/core/TickTreeLib.sol"; /* Globally enable global.method(...) */ import {LocalExtra,LocalUnpackedExtra} from "@mgv/lib/core/LocalExtra.sol"; using LocalExtra for Local global; using LocalUnpackedExtra for LocalUnpacked global; ////////////// END OF ADDITIONAL DEFINITIONS ///////////////// library LocalLib { // number of bits in each field uint constant active_bits = 1; uint constant fee_bits = 8; uint constant density_bits = 9; uint constant binPosInLeaf_bits = 2; uint constant level3_bits = 64; uint constant level2_bits = 64; uint constant level1_bits = 64; uint constant root_bits = 2; uint constant kilo_offer_gasbase_bits = 9; uint constant lock_bits = 1; uint constant last_bits = 32; // number of bits before each field uint constant active_before = 0 + 0; uint constant fee_before = active_before + active_bits; uint constant density_before = fee_before + fee_bits; uint constant binPosInLeaf_before = density_before + density_bits; uint constant level3_before = binPosInLeaf_before + binPosInLeaf_bits; uint constant level2_before = level3_before + level3_bits; uint constant level1_before = level2_before + level2_bits; uint constant root_before = level1_before + level1_bits; uint constant kilo_offer_gasbase_before = root_before + root_bits; uint constant lock_before = kilo_offer_gasbase_before + kilo_offer_gasbase_bits; uint constant last_before = lock_before + lock_bits; // focus-mask: 1s at field location, 0s elsewhere uint constant active_mask_inv = (ONES << 256 - active_bits) >> active_before; uint constant fee_mask_inv = (ONES << 256 - fee_bits) >> fee_before; uint constant density_mask_inv = (ONES << 256 - density_bits) >> density_before; uint constant binPosInLeaf_mask_inv = (ONES << 256 - binPosInLeaf_bits) >> binPosInLeaf_before; uint constant level3_mask_inv = (ONES << 256 - level3_bits) >> level3_before; uint constant level2_mask_inv = (ONES << 256 - level2_bits) >> level2_before; uint constant level1_mask_inv = (ONES << 256 - level1_bits) >> level1_before; uint constant root_mask_inv = (ONES << 256 - root_bits) >> root_before; uint constant kilo_offer_gasbase_mask_inv = (ONES << 256 - kilo_offer_gasbase_bits) >> kilo_offer_gasbase_before; uint constant lock_mask_inv = (ONES << 256 - lock_bits) >> lock_before; uint constant last_mask_inv = (ONES << 256 - last_bits) >> last_before; // cleanup-mask: 0s at field location, 1s elsewhere uint constant active_mask = ~active_mask_inv; uint constant fee_mask = ~fee_mask_inv; uint constant density_mask = ~density_mask_inv; uint constant binPosInLeaf_mask = ~binPosInLeaf_mask_inv; uint constant level3_mask = ~level3_mask_inv; uint constant level2_mask = ~level2_mask_inv; uint constant level1_mask = ~level1_mask_inv; uint constant root_mask = ~root_mask_inv; uint constant kilo_offer_gasbase_mask = ~kilo_offer_gasbase_mask_inv; uint constant lock_mask = ~lock_mask_inv; uint constant last_mask = ~last_mask_inv; // cast-mask: 0s followed by |field| trailing 1s uint constant active_cast_mask = ~(ONES << active_bits); uint constant fee_cast_mask = ~(ONES << fee_bits); uint constant density_cast_mask = ~(ONES << density_bits); uint constant binPosInLeaf_cast_mask = ~(ONES << binPosInLeaf_bits); uint constant level3_cast_mask = ~(ONES << level3_bits); uint constant level2_cast_mask = ~(ONES << level2_bits); uint constant level1_cast_mask = ~(ONES << level1_bits); uint constant root_cast_mask = ~(ONES << root_bits); uint constant kilo_offer_gasbase_cast_mask = ~(ONES << kilo_offer_gasbase_bits); uint constant lock_cast_mask = ~(ONES << lock_bits); uint constant last_cast_mask = ~(ONES << last_bits); // size-related error message string constant active_size_error = "mgv/config/active/1bits"; string constant fee_size_error = "mgv/config/fee/8bits"; string constant density_size_error = "mgv/config/density/9bits"; string constant binPosInLeaf_size_error = "mgv/config/binPosInLeaf/2bits"; string constant level3_size_error = "mgv/config/level3/64bits"; string constant level2_size_error = "mgv/config/level2/64bits"; string constant level1_size_error = "mgv/config/level1/64bits"; string constant root_size_error = "mgv/config/root/2bits"; string constant kilo_offer_gasbase_size_error = "mgv/config/kilo_offer_gasbase/9bits"; string constant lock_size_error = "mgv/config/lock/1bits"; string constant last_size_error = "mgv/config/last/32bits"; // from packed to in-memory struct function to_struct(Local __packed) internal pure returns (LocalUnpacked memory __s) { unchecked { __s.active = ((Local.unwrap(__packed) & active_mask_inv) > 0); __s.fee = uint(Local.unwrap(__packed) << fee_before) >> (256 - fee_bits); __s.density = Density.wrap(uint(Local.unwrap(__packed) << density_before) >> (256 - density_bits)); __s.binPosInLeaf = uint(Local.unwrap(__packed) << binPosInLeaf_before) >> (256 - binPosInLeaf_bits); __s.level3 = Field.wrap(uint(Local.unwrap(__packed) << level3_before) >> (256 - level3_bits)); __s.level2 = Field.wrap(uint(Local.unwrap(__packed) << level2_before) >> (256 - level2_bits)); __s.level1 = Field.wrap(uint(Local.unwrap(__packed) << level1_before) >> (256 - level1_bits)); __s.root = Field.wrap(uint(Local.unwrap(__packed) << root_before) >> (256 - root_bits)); __s.kilo_offer_gasbase = uint(Local.unwrap(__packed) << kilo_offer_gasbase_before) >> (256 - kilo_offer_gasbase_bits); __s.lock = ((Local.unwrap(__packed) & lock_mask_inv) > 0); __s.last = uint(Local.unwrap(__packed) << last_before) >> (256 - last_bits); }} // equality checking function eq(Local __packed1, Local __packed2) internal pure returns (bool) { unchecked { return Local.unwrap(__packed1) == Local.unwrap(__packed2); }} // from packed to a tuple function unpack(Local __packed) internal pure returns (bool __active, uint __fee, Density __density, uint __binPosInLeaf, Field __level3, Field __level2, Field __level1, Field __root, uint __kilo_offer_gasbase, bool __lock, uint __last) { unchecked { __active = ((Local.unwrap(__packed) & active_mask_inv) > 0); __fee = uint(Local.unwrap(__packed) << fee_before) >> (256 - fee_bits); __density = Density.wrap(uint(Local.unwrap(__packed) << density_before) >> (256 - density_bits)); __binPosInLeaf = uint(Local.unwrap(__packed) << binPosInLeaf_before) >> (256 - binPosInLeaf_bits); __level3 = Field.wrap(uint(Local.unwrap(__packed) << level3_before) >> (256 - level3_bits)); __level2 = Field.wrap(uint(Local.unwrap(__packed) << level2_before) >> (256 - level2_bits)); __level1 = Field.wrap(uint(Local.unwrap(__packed) << level1_before) >> (256 - level1_bits)); __root = Field.wrap(uint(Local.unwrap(__packed) << root_before) >> (256 - root_bits)); __kilo_offer_gasbase = uint(Local.unwrap(__packed) << kilo_offer_gasbase_before) >> (256 - kilo_offer_gasbase_bits); __lock = ((Local.unwrap(__packed) & lock_mask_inv) > 0); __last = uint(Local.unwrap(__packed) << last_before) >> (256 - last_bits); }} // getters function active(Local __packed) internal pure returns(bool) { unchecked { return ((Local.unwrap(__packed) & active_mask_inv) > 0); }} // setters function active(Local __packed,bool val) internal pure returns(Local) { unchecked { return Local.wrap((Local.unwrap(__packed) & active_mask) | (uint_of_bool(val) << (256 - active_bits)) >> active_before); }} function fee(Local __packed) internal pure returns(uint) { unchecked { return uint(Local.unwrap(__packed) << fee_before) >> (256 - fee_bits); }} // setters function fee(Local __packed,uint val) internal pure returns(Local) { unchecked { return Local.wrap((Local.unwrap(__packed) & fee_mask) | (val << (256 - fee_bits)) >> fee_before); }} function density(Local __packed) internal pure returns(Density) { unchecked { return Density.wrap(uint(Local.unwrap(__packed) << density_before) >> (256 - density_bits)); }} // setters function density(Local __packed,Density val) internal pure returns(Local) { unchecked { return Local.wrap((Local.unwrap(__packed) & density_mask) | (Density.unwrap(val) << (256 - density_bits)) >> density_before); }} function binPosInLeaf(Local __packed) internal pure returns(uint) { unchecked { return uint(Local.unwrap(__packed) << binPosInLeaf_before) >> (256 - binPosInLeaf_bits); }} // setters function binPosInLeaf(Local __packed,uint val) internal pure returns(Local) { unchecked { return Local.wrap((Local.unwrap(__packed) & binPosInLeaf_mask) | (val << (256 - binPosInLeaf_bits)) >> binPosInLeaf_before); }} function level3(Local __packed) internal pure returns(Field) { unchecked { return Field.wrap(uint(Local.unwrap(__packed) << level3_before) >> (256 - level3_bits)); }} // setters function level3(Local __packed,Field val) internal pure returns(Local) { unchecked { return Local.wrap((Local.unwrap(__packed) & level3_mask) | (Field.unwrap(val) << (256 - level3_bits)) >> level3_before); }} function level2(Local __packed) internal pure returns(Field) { unchecked { return Field.wrap(uint(Local.unwrap(__packed) << level2_before) >> (256 - level2_bits)); }} // setters function level2(Local __packed,Field val) internal pure returns(Local) { unchecked { return Local.wrap((Local.unwrap(__packed) & level2_mask) | (Field.unwrap(val) << (256 - level2_bits)) >> level2_before); }} function level1(Local __packed) internal pure returns(Field) { unchecked { return Field.wrap(uint(Local.unwrap(__packed) << level1_before) >> (256 - level1_bits)); }} // setters function level1(Local __packed,Field val) internal pure returns(Local) { unchecked { return Local.wrap((Local.unwrap(__packed) & level1_mask) | (Field.unwrap(val) << (256 - level1_bits)) >> level1_before); }} function root(Local __packed) internal pure returns(Field) { unchecked { return Field.wrap(uint(Local.unwrap(__packed) << root_before) >> (256 - root_bits)); }} // setters function root(Local __packed,Field val) internal pure returns(Local) { unchecked { return Local.wrap((Local.unwrap(__packed) & root_mask) | (Field.unwrap(val) << (256 - root_bits)) >> root_before); }} function kilo_offer_gasbase(Local __packed) internal pure returns(uint) { unchecked { return uint(Local.unwrap(__packed) << kilo_offer_gasbase_before) >> (256 - kilo_offer_gasbase_bits); }} // setters function kilo_offer_gasbase(Local __packed,uint val) internal pure returns(Local) { unchecked { return Local.wrap((Local.unwrap(__packed) & kilo_offer_gasbase_mask) | (val << (256 - kilo_offer_gasbase_bits)) >> kilo_offer_gasbase_before); }} function lock(Local __packed) internal pure returns(bool) { unchecked { return ((Local.unwrap(__packed) & lock_mask_inv) > 0); }} // setters function lock(Local __packed,bool val) internal pure returns(Local) { unchecked { return Local.wrap((Local.unwrap(__packed) & lock_mask) | (uint_of_bool(val) << (256 - lock_bits)) >> lock_before); }} function last(Local __packed) internal pure returns(uint) { unchecked { return uint(Local.unwrap(__packed) << last_before) >> (256 - last_bits); }} // setters function last(Local __packed,uint val) internal pure returns(Local) { unchecked { return Local.wrap((Local.unwrap(__packed) & last_mask) | (val << (256 - last_bits)) >> last_before); }} // from in-memory struct to packed function t_of_struct(LocalUnpacked memory __s) internal pure returns (Local) { unchecked { return pack(__s.active, __s.fee, __s.density, __s.binPosInLeaf, __s.level3, __s.level2, __s.level1, __s.root, __s.kilo_offer_gasbase, __s.lock, __s.last); }} // from arguments to packed function pack(bool __active, uint __fee, Density __density, uint __binPosInLeaf, Field __level3, Field __level2, Field __level1, Field __root, uint __kilo_offer_gasbase, bool __lock, uint __last) internal pure returns (Local) { unchecked { uint __packed; __packed |= (uint_of_bool(__active) << (256 - active_bits)) >> active_before; __packed |= (__fee << (256 - fee_bits)) >> fee_before; __packed |= (Density.unwrap(__density) << (256 - density_bits)) >> density_before; __packed |= (__binPosInLeaf << (256 - binPosInLeaf_bits)) >> binPosInLeaf_before; __packed |= (Field.unwrap(__level3) << (256 - level3_bits)) >> level3_before; __packed |= (Field.unwrap(__level2) << (256 - level2_bits)) >> level2_before; __packed |= (Field.unwrap(__level1) << (256 - level1_bits)) >> level1_before; __packed |= (Field.unwrap(__root) << (256 - root_bits)) >> root_before; __packed |= (__kilo_offer_gasbase << (256 - kilo_offer_gasbase_bits)) >> kilo_offer_gasbase_before; __packed |= (uint_of_bool(__lock) << (256 - lock_bits)) >> lock_before; __packed |= (__last << (256 - last_bits)) >> last_before; return Local.wrap(__packed); }} // input checking function active_check(bool __active) internal pure returns (bool) { unchecked { return (uint_of_bool(__active) & active_cast_mask) == uint_of_bool(__active); }} function fee_check(uint __fee) internal pure returns (bool) { unchecked { return (__fee & fee_cast_mask) == __fee; }} function density_check(Density __density) internal pure returns (bool) { unchecked { return (Density.unwrap(__density) & density_cast_mask) == Density.unwrap(__density); }} function binPosInLeaf_check(uint __binPosInLeaf) internal pure returns (bool) { unchecked { return (__binPosInLeaf & binPosInLeaf_cast_mask) == __binPosInLeaf; }} function level3_check(Field __level3) internal pure returns (bool) { unchecked { return (Field.unwrap(__level3) & level3_cast_mask) == Field.unwrap(__level3); }} function level2_check(Field __level2) internal pure returns (bool) { unchecked { return (Field.unwrap(__level2) & level2_cast_mask) == Field.unwrap(__level2); }} function level1_check(Field __level1) internal pure returns (bool) { unchecked { return (Field.unwrap(__level1) & level1_cast_mask) == Field.unwrap(__level1); }} function root_check(Field __root) internal pure returns (bool) { unchecked { return (Field.unwrap(__root) & root_cast_mask) == Field.unwrap(__root); }} function kilo_offer_gasbase_check(uint __kilo_offer_gasbase) internal pure returns (bool) { unchecked { return (__kilo_offer_gasbase & kilo_offer_gasbase_cast_mask) == __kilo_offer_gasbase; }} function lock_check(bool __lock) internal pure returns (bool) { unchecked { return (uint_of_bool(__lock) & lock_cast_mask) == uint_of_bool(__lock); }} function last_check(uint __last) internal pure returns (bool) { unchecked { return (__last & last_cast_mask) == __last; }} }lib/mangrove-core/src/preprocessed/Structs.post.sol
// SPDX-License-Identifier: MIT pragma solidity ^0.8.13; /* ************************************************** * GENERATED FILE. DO NOT EDIT. * ************************************************** */ import {Offer, OfferUnpacked, OfferLib} from "./Offer.post.sol"; import {OfferDetail, OfferDetailUnpacked, OfferDetailLib} from "./OfferDetail.post.sol"; import {Global, GlobalUnpacked, GlobalLib} from "./Global.post.sol"; import {Local, LocalUnpacked, LocalLib} from "./Local.post.sol";lib/solady/src/utils/FixedPointMathLib.sol
// SPDX-License-Identifier: MIT pragma solidity ^0.8.4; /// @notice Arithmetic library with operations for fixed-point numbers. /// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/FixedPointMathLib.sol) /// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/FixedPointMathLib.sol) library FixedPointMathLib { /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* CUSTOM ERRORS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev The operation failed, as the output exceeds the maximum value of uint256. error ExpOverflow(); /// @dev The operation failed, as the output exceeds the maximum value of uint256. error FactorialOverflow(); /// @dev The operation failed, due to an overflow. error RPowOverflow(); /// @dev The mantissa is too big to fit. error MantissaOverflow(); /// @dev The operation failed, due to an multiplication overflow. error MulWadFailed(); /// @dev The operation failed, due to an multiplication overflow. error SMulWadFailed(); /// @dev The operation failed, either due to a multiplication overflow, or a division by a zero. error DivWadFailed(); /// @dev The operation failed, either due to a multiplication overflow, or a division by a zero. error SDivWadFailed(); /// @dev The operation failed, either due to a multiplication overflow, or a division by a zero. error MulDivFailed(); /// @dev The division failed, as the denominator is zero. error DivFailed(); /// @dev The full precision multiply-divide operation failed, either due /// to the result being larger than 256 bits, or a division by a zero. error FullMulDivFailed(); /// @dev The output is undefined, as the input is less-than-or-equal to zero. error LnWadUndefined(); /// @dev The input outside the acceptable domain. error OutOfDomain(); /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* CONSTANTS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev The scalar of ETH and most ERC20s. uint256 internal constant WAD = 1e18; /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* SIMPLIFIED FIXED POINT OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Equivalent to `(x * y) / WAD` rounded down. function mulWad(uint256 x, uint256 y) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { // Equivalent to `require(y == 0 || x <= type(uint256).max / y)`. if gt(x, div(not(0), y)) { if y { mstore(0x00, 0xbac65e5b) // `MulWadFailed()`. revert(0x1c, 0x04) } } z := div(mul(x, y), WAD) } } /// @dev Equivalent to `(x * y) / WAD` rounded down. function sMulWad(int256 x, int256 y) internal pure returns (int256 z) { /// @solidity memory-safe-assembly assembly { z := mul(x, y) // Equivalent to `require((x == 0 || z / x == y) && !(x == -1 && y == type(int256).min))`. if iszero(gt(or(iszero(x), eq(sdiv(z, x), y)), lt(not(x), eq(y, shl(255, 1))))) { mstore(0x00, 0xedcd4dd4) // `SMulWadFailed()`. revert(0x1c, 0x04) } z := sdiv(z, WAD) } } /// @dev Equivalent to `(x * y) / WAD` rounded down, but without overflow checks. function rawMulWad(uint256 x, uint256 y) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { z := div(mul(x, y), WAD) } } /// @dev Equivalent to `(x * y) / WAD` rounded down, but without overflow checks. function rawSMulWad(int256 x, int256 y) internal pure returns (int256 z) { /// @solidity memory-safe-assembly assembly { z := sdiv(mul(x, y), WAD) } } /// @dev Equivalent to `(x * y) / WAD` rounded up. function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { z := mul(x, y) // Equivalent to `require(y == 0 || x <= type(uint256).max / y)`. if iszero(eq(div(z, y), x)) { if y { mstore(0x00, 0xbac65e5b) // `MulWadFailed()`. revert(0x1c, 0x04) } } z := add(iszero(iszero(mod(z, WAD))), div(z, WAD)) } } /// @dev Equivalent to `(x * y) / WAD` rounded up, but without overflow checks. function rawMulWadUp(uint256 x, uint256 y) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { z := add(iszero(iszero(mod(mul(x, y), WAD))), div(mul(x, y), WAD)) } } /// @dev Equivalent to `(x * WAD) / y` rounded down. function divWad(uint256 x, uint256 y) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { // Equivalent to `require(y != 0 && x <= type(uint256).max / WAD)`. if iszero(mul(y, lt(x, add(1, div(not(0), WAD))))) { mstore(0x00, 0x7c5f487d) // `DivWadFailed()`. revert(0x1c, 0x04) } z := div(mul(x, WAD), y) } } /// @dev Equivalent to `(x * WAD) / y` rounded down. function sDivWad(int256 x, int256 y) internal pure returns (int256 z) { /// @solidity memory-safe-assembly assembly { z := mul(x, WAD) // Equivalent to `require(y != 0 && ((x * WAD) / WAD == x))`. if iszero(mul(y, eq(sdiv(z, WAD), x))) { mstore(0x00, 0x5c43740d) // `SDivWadFailed()`. revert(0x1c, 0x04) } z := sdiv(z, y) } } /// @dev Equivalent to `(x * WAD) / y` rounded down, but without overflow and divide by zero checks. function rawDivWad(uint256 x, uint256 y) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { z := div(mul(x, WAD), y) } } /// @dev Equivalent to `(x * WAD) / y` rounded down, but without overflow and divide by zero checks. function rawSDivWad(int256 x, int256 y) internal pure returns (int256 z) { /// @solidity memory-safe-assembly assembly { z := sdiv(mul(x, WAD), y) } } /// @dev Equivalent to `(x * WAD) / y` rounded up. function divWadUp(uint256 x, uint256 y) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { // Equivalent to `require(y != 0 && x <= type(uint256).max / WAD)`. if iszero(mul(y, lt(x, add(1, div(not(0), WAD))))) { mstore(0x00, 0x7c5f487d) // `DivWadFailed()`. revert(0x1c, 0x04) } z := add(iszero(iszero(mod(mul(x, WAD), y))), div(mul(x, WAD), y)) } } /// @dev Equivalent to `(x * WAD) / y` rounded up, but without overflow and divide by zero checks. function rawDivWadUp(uint256 x, uint256 y) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { z := add(iszero(iszero(mod(mul(x, WAD), y))), div(mul(x, WAD), y)) } } /// @dev Equivalent to `x` to the power of `y`. /// because `x ** y = (e ** ln(x)) ** y = e ** (ln(x) * y)`. /// Note: This function is an approximation. function powWad(int256 x, int256 y) internal pure returns (int256) { // Using `ln(x)` means `x` must be greater than 0. return expWad((lnWad(x) * y) / int256(WAD)); } /// @dev Returns `exp(x)`, denominated in `WAD`. /// Credit to Remco Bloemen under MIT license: https://2π.com/22/exp-ln /// Note: This function is an approximation. Monotonically increasing. function expWad(int256 x) internal pure returns (int256 r) { unchecked { // When the result is less than 0.5 we return zero. // This happens when `x <= (log(1e-18) * 1e18) ~ -4.15e19`. if (x <= -41446531673892822313) return r; /// @solidity memory-safe-assembly assembly { // When the result is greater than `(2**255 - 1) / 1e18` we can not represent it as // an int. This happens when `x >= floor(log((2**255 - 1) / 1e18) * 1e18) ≈ 135`. if iszero(slt(x, 135305999368893231589)) { mstore(0x00, 0xa37bfec9) // `ExpOverflow()`. revert(0x1c, 0x04) } } // `x` is now in the range `(-42, 136) * 1e18`. Convert to `(-42, 136) * 2**96` // for more intermediate precision and a binary basis. This base conversion // is a multiplication by 1e18 / 2**96 = 5**18 / 2**78. x = (x << 78) / 5 ** 18; // Reduce range of x to (-½ ln 2, ½ ln 2) * 2**96 by factoring out powers // of two such that exp(x) = exp(x') * 2**k, where k is an integer. // Solving this gives k = round(x / log(2)) and x' = x - k * log(2). int256 k = ((x << 96) / 54916777467707473351141471128 + 2 ** 95) >> 96; x = x - k * 54916777467707473351141471128; // `k` is in the range `[-61, 195]`. // Evaluate using a (6, 7)-term rational approximation. // `p` is made monic, we'll multiply by a scale factor later. int256 y = x + 1346386616545796478920950773328; y = ((y * x) >> 96) + 57155421227552351082224309758442; int256 p = y + x - 94201549194550492254356042504812; p = ((p * y) >> 96) + 28719021644029726153956944680412240; p = p * x + (4385272521454847904659076985693276 << 96); // We leave `p` in `2**192` basis so we don't need to scale it back up for the division. int256 q = x - 2855989394907223263936484059900; q = ((q * x) >> 96) + 50020603652535783019961831881945; q = ((q * x) >> 96) - 533845033583426703283633433725380; q = ((q * x) >> 96) + 3604857256930695427073651918091429; q = ((q * x) >> 96) - 14423608567350463180887372962807573; q = ((q * x) >> 96) + 26449188498355588339934803723976023; /// @solidity memory-safe-assembly assembly { // Div in assembly because solidity adds a zero check despite the unchecked. // The q polynomial won't have zeros in the domain as all its roots are complex. // No scaling is necessary because p is already `2**96` too large. r := sdiv(p, q) } // r should be in the range `(0.09, 0.25) * 2**96`. // We now need to multiply r by: // - The scale factor `s ≈ 6.031367120`. // - The `2**k` factor from the range reduction. // - The `1e18 / 2**96` factor for base conversion. // We do this all at once, with an intermediate result in `2**213` // basis, so the final right shift is always by a positive amount. r = int256( (uint256(r) * 3822833074963236453042738258902158003155416615667) >> uint256(195 - k) ); } } /// @dev Returns `ln(x)`, denominated in `WAD`. /// Credit to Remco Bloemen under MIT license: https://2π.com/22/exp-ln /// Note: This function is an approximation. Monotonically increasing. function lnWad(int256 x) internal pure returns (int256 r) { /// @solidity memory-safe-assembly assembly { // We want to convert `x` from `10**18` fixed point to `2**96` fixed point. // We do this by multiplying by `2**96 / 10**18`. But since // `ln(x * C) = ln(x) + ln(C)`, we can simply do nothing here // and add `ln(2**96 / 10**18)` at the end. // Compute `k = log2(x) - 96`, `r = 159 - k = 255 - log2(x) = 255 ^ log2(x)`. r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x)) r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x)))) r := or(r, shl(5, lt(0xffffffff, shr(r, x)))) r := or(r, shl(4, lt(0xffff, shr(r, x)))) r := or(r, shl(3, lt(0xff, shr(r, x)))) // We place the check here for more optimal stack operations. if iszero(sgt(x, 0)) { mstore(0x00, 0x1615e638) // `LnWadUndefined()`. revert(0x1c, 0x04) } // forgefmt: disable-next-item r := xor(r, byte(and(0x1f, shr(shr(r, x), 0x8421084210842108cc6318c6db6d54be)), 0xf8f9f9faf9fdfafbf9fdfcfdfafbfcfef9fafdfafcfcfbfefafafcfbffffffff)) // Reduce range of x to (1, 2) * 2**96 // ln(2^k * x) = k * ln(2) + ln(x) x := shr(159, shl(r, x)) // Evaluate using a (8, 8)-term rational approximation. // `p` is made monic, we will multiply by a scale factor later. // forgefmt: disable-next-item let p := sub( // This heavily nested expression is to avoid stack-too-deep for via-ir. sar(96, mul(add(43456485725739037958740375743393, sar(96, mul(add(24828157081833163892658089445524, sar(96, mul(add(3273285459638523848632254066296, x), x))), x))), x)), 11111509109440967052023855526967) p := sub(sar(96, mul(p, x)), 45023709667254063763336534515857) p := sub(sar(96, mul(p, x)), 14706773417378608786704636184526) p := sub(mul(p, x), shl(96, 795164235651350426258249787498)) // We leave `p` in `2**192` basis so we don't need to scale it back up for the division. // `q` is monic by convention. let q := add(5573035233440673466300451813936, x) q := add(71694874799317883764090561454958, sar(96, mul(x, q))) q := add(283447036172924575727196451306956, sar(96, mul(x, q))) q := add(401686690394027663651624208769553, sar(96, mul(x, q))) q := add(204048457590392012362485061816622, sar(96, mul(x, q))) q := add(31853899698501571402653359427138, sar(96, mul(x, q))) q := add(909429971244387300277376558375, sar(96, mul(x, q))) // `p / q` is in the range `(0, 0.125) * 2**96`. // Finalization, we need to: // - Multiply by the scale factor `s = 5.549…`. // - Add `ln(2**96 / 10**18)`. // - Add `k * ln(2)`. // - Multiply by `10**18 / 2**96 = 5**18 >> 78`. // The q polynomial is known not to have zeros in the domain. // No scaling required because p is already `2**96` too large. p := sdiv(p, q) // Multiply by the scaling factor: `s * 5**18 * 2**96`, base is now `5**18 * 2**192`. p := mul(1677202110996718588342820967067443963516166, p) // Add `ln(2) * k * 5**18 * 2**192`. // forgefmt: disable-next-item p := add(mul(16597577552685614221487285958193947469193820559219878177908093499208371, sub(159, r)), p) // Add `ln(2**96 / 10**18) * 5**18 * 2**192`. p := add(600920179829731861736702779321621459595472258049074101567377883020018308, p) // Base conversion: mul `2**18 / 2**192`. r := sar(174, p) } } /// @dev Returns `W_0(x)`, denominated in `WAD`. /// See: https://en.wikipedia.org/wiki/Lambert_W_function /// a.k.a. Product log function. This is an approximation of the principal branch. /// Note: This function is an approximation. Monotonically increasing. function lambertW0Wad(int256 x) internal pure returns (int256 w) { // forgefmt: disable-next-item unchecked { if ((w = x) <= -367879441171442322) revert OutOfDomain(); // `x` less than `-1/e`. (int256 wad, int256 p) = (int256(WAD), x); uint256 c; // Whether we need to avoid catastrophic cancellation. uint256 i = 4; // Number of iterations. if (w <= 0x1ffffffffffff) { if (-0x4000000000000 <= w) { i = 1; // Inputs near zero only take one step to converge. } else if (w <= -0x3ffffffffffffff) { i = 32; // Inputs near `-1/e` take very long to converge. } } else if (uint256(w >> 63) == uint256(0)) { /// @solidity memory-safe-assembly assembly { // Inline log2 for more performance, since the range is small. let v := shr(49, w) let l := shl(3, lt(0xff, v)) l := add(or(l, byte(and(0x1f, shr(shr(l, v), 0x8421084210842108cc6318c6db6d54be)), 0x0706060506020504060203020504030106050205030304010505030400000000)), 49) w := sdiv(shl(l, 7), byte(sub(l, 31), 0x0303030303030303040506080c13)) c := gt(l, 60) i := add(2, add(gt(l, 53), c)) } } else { int256 ll = lnWad(w = lnWad(w)); /// @solidity memory-safe-assembly assembly { // `w = ln(x) - ln(ln(x)) + b * ln(ln(x)) / ln(x)`. w := add(sdiv(mul(ll, 1023715080943847266), w), sub(w, ll)) i := add(3, iszero(shr(68, x))) c := iszero(shr(143, x)) } if (c == uint256(0)) { do { // If `x` is big, use Newton's so that intermediate values won't overflow. int256 e = expWad(w); /// @solidity memory-safe-assembly assembly { let t := mul(w, div(e, wad)) w := sub(w, sdiv(sub(t, x), div(add(e, t), wad))) } if (p <= w) break; p = w; } while (--i != uint256(0)); /// @solidity memory-safe-assembly assembly { w := sub(w, sgt(w, 2)) } return w; } } do { // Otherwise, use Halley's for faster convergence. int256 e = expWad(w); /// @solidity memory-safe-assembly assembly { let t := add(w, wad) let s := sub(mul(w, e), mul(x, wad)) w := sub(w, sdiv(mul(s, wad), sub(mul(e, t), sdiv(mul(add(t, wad), s), add(t, t))))) } if (p <= w) break; p = w; } while (--i != c); /// @solidity memory-safe-assembly assembly { w := sub(w, sgt(w, 2)) } // For certain ranges of `x`, we'll use the quadratic-rate recursive formula of // R. Iacono and J.P. Boyd for the last iteration, to avoid catastrophic cancellation. if (c == uint256(0)) return w; int256 t = w | 1; /// @solidity memory-safe-assembly assembly { x := sdiv(mul(x, wad), t) } x = (t * (wad + lnWad(x))); /// @solidity memory-safe-assembly assembly { w := sdiv(x, add(wad, t)) } } } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* GENERAL NUMBER UTILITIES */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Returns `a * b == x * y`, with full precision. function fullMulEq(uint256 a, uint256 b, uint256 x, uint256 y) internal pure returns (bool result) { /// @solidity memory-safe-assembly assembly { result := and(eq(mul(a, b), mul(x, y)), eq(mulmod(x, y, not(0)), mulmod(a, b, not(0)))) } } /// @dev Calculates `floor(x * y / d)` with full precision. /// Throws if result overflows a uint256 or when `d` is zero. /// Credit to Remco Bloemen under MIT license: https://2π.com/21/muldiv function fullMulDiv(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { // 512-bit multiply `[p1 p0] = x * y`. // Compute the product mod `2**256` and mod `2**256 - 1` // then use the Chinese Remainder Theorem to reconstruct // the 512 bit result. The result is stored in two 256 // variables such that `product = p1 * 2**256 + p0`. // Temporarily use `z` as `p0` to save gas. z := mul(x, y) // Lower 256 bits of `x * y`. for {} 1 {} { // If overflows. if iszero(mul(or(iszero(x), eq(div(z, x), y)), d)) { let mm := mulmod(x, y, not(0)) let p1 := sub(mm, add(z, lt(mm, z))) // Upper 256 bits of `x * y`. /*------------------- 512 by 256 division --------------------*/ // Make division exact by subtracting the remainder from `[p1 p0]`. let r := mulmod(x, y, d) // Compute remainder using mulmod. let t := and(d, sub(0, d)) // The least significant bit of `d`. `t >= 1`. // Make sure `z` is less than `2**256`. Also prevents `d == 0`. // Placing the check here seems to give more optimal stack operations. if iszero(gt(d, p1)) { mstore(0x00, 0xae47f702) // `FullMulDivFailed()`. revert(0x1c, 0x04) } d := div(d, t) // Divide `d` by `t`, which is a power of two. // Invert `d mod 2**256` // Now that `d` is an odd number, it has an inverse // modulo `2**256` such that `d * inv = 1 mod 2**256`. // Compute the inverse by starting with a seed that is correct // correct for four bits. That is, `d * inv = 1 mod 2**4`. let inv := xor(2, mul(3, d)) // Now use 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. inv := mul(inv, sub(2, mul(d, inv))) // inverse mod 2**8 inv := mul(inv, sub(2, mul(d, inv))) // inverse mod 2**16 inv := mul(inv, sub(2, mul(d, inv))) // inverse mod 2**32 inv := mul(inv, sub(2, mul(d, inv))) // inverse mod 2**64 inv := mul(inv, sub(2, mul(d, inv))) // inverse mod 2**128 z := mul( // Divide [p1 p0] by the factors of two. // Shift in bits from `p1` into `p0`. For this we need // to flip `t` such that it is `2**256 / t`. or(mul(sub(p1, gt(r, z)), add(div(sub(0, t), t), 1)), div(sub(z, r), t)), mul(sub(2, mul(d, inv)), inv) // inverse mod 2**256 ) break } z := div(z, d) break } } } /// @dev Calculates `floor(x * y / d)` with full precision. /// Behavior is undefined if `d` is zero or the final result cannot fit in 256 bits. /// Performs the full 512 bit calculation regardless. function fullMulDivUnchecked(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { z := mul(x, y) let mm := mulmod(x, y, not(0)) let p1 := sub(mm, add(z, lt(mm, z))) let t := and(d, sub(0, d)) let r := mulmod(x, y, d) d := div(d, t) let inv := xor(2, mul(3, d)) inv := mul(inv, sub(2, mul(d, inv))) inv := mul(inv, sub(2, mul(d, inv))) inv := mul(inv, sub(2, mul(d, inv))) inv := mul(inv, sub(2, mul(d, inv))) inv := mul(inv, sub(2, mul(d, inv))) z := mul( or(mul(sub(p1, gt(r, z)), add(div(sub(0, t), t), 1)), div(sub(z, r), t)), mul(sub(2, mul(d, inv)), inv) ) } } /// @dev Calculates `floor(x * y / d)` with full precision, rounded up. /// Throws if result overflows a uint256 or when `d` is zero. /// Credit to Uniswap-v3-core under MIT license: /// https://github.com/Uniswap/v3-core/blob/main/contracts/libraries/FullMath.sol function fullMulDivUp(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) { z = fullMulDiv(x, y, d); /// @solidity memory-safe-assembly assembly { if mulmod(x, y, d) { z := add(z, 1) if iszero(z) { mstore(0x00, 0xae47f702) // `FullMulDivFailed()`. revert(0x1c, 0x04) } } } } /// @dev Calculates `floor(x * y / 2 ** n)` with full precision. /// Throws if result overflows a uint256. /// Credit to Philogy under MIT license: /// https://github.com/SorellaLabs/angstrom/blob/main/contracts/src/libraries/X128MathLib.sol function fullMulDivN(uint256 x, uint256 y, uint8 n) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { // Temporarily use `z` as `p0` to save gas. z := mul(x, y) // Lower 256 bits of `x * y`. We'll call this `z`. for {} 1 {} { if iszero(or(iszero(x), eq(div(z, x), y))) { let k := and(n, 0xff) // `n`, cleaned. let mm := mulmod(x, y, not(0)) let p1 := sub(mm, add(z, lt(mm, z))) // Upper 256 bits of `x * y`. // | p1 | z | // Before: | p1_0 ¦ p1_1 | z_0 ¦ z_1 | // Final: | 0 ¦ p1_0 | p1_1 ¦ z_0 | // Check that final `z` doesn't overflow by checking that p1_0 = 0. if iszero(shr(k, p1)) { z := add(shl(sub(256, k), p1), shr(k, z)) break } mstore(0x00, 0xae47f702) // `FullMulDivFailed()`. revert(0x1c, 0x04) } z := shr(and(n, 0xff), z) break } } } /// @dev Returns `floor(x * y / d)`. /// Reverts if `x * y` overflows, or `d` is zero. function mulDiv(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { z := mul(x, y) // Equivalent to `require(d != 0 && (y == 0 || x <= type(uint256).max / y))`. if iszero(mul(or(iszero(x), eq(div(z, x), y)), d)) { mstore(0x00, 0xad251c27) // `MulDivFailed()`. revert(0x1c, 0x04) } z := div(z, d) } } /// @dev Returns `ceil(x * y / d)`. /// Reverts if `x * y` overflows, or `d` is zero. function mulDivUp(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { z := mul(x, y) // Equivalent to `require(d != 0 && (y == 0 || x <= type(uint256).max / y))`. if iszero(mul(or(iszero(x), eq(div(z, x), y)), d)) { mstore(0x00, 0xad251c27) // `MulDivFailed()`. revert(0x1c, 0x04) } z := add(iszero(iszero(mod(z, d))), div(z, d)) } } /// @dev Returns `x`, the modular multiplicative inverse of `a`, such that `(a * x) % n == 1`. function invMod(uint256 a, uint256 n) internal pure returns (uint256 x) { /// @solidity memory-safe-assembly assembly { let g := n let r := mod(a, n) for { let y := 1 } 1 {} { let q := div(g, r) let t := g g := r r := sub(t, mul(r, q)) let u := x x := y y := sub(u, mul(y, q)) if iszero(r) { break } } x := mul(eq(g, 1), add(x, mul(slt(x, 0), n))) } } /// @dev Returns `ceil(x / d)`. /// Reverts if `d` is zero. function divUp(uint256 x, uint256 d) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { if iszero(d) { mstore(0x00, 0x65244e4e) // `DivFailed()`. revert(0x1c, 0x04) } z := add(iszero(iszero(mod(x, d))), div(x, d)) } } /// @dev Returns `max(0, x - y)`. Alias for `saturatingSub`. function zeroFloorSub(uint256 x, uint256 y) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { z := mul(gt(x, y), sub(x, y)) } } /// @dev Returns `max(0, x - y)`. function saturatingSub(uint256 x, uint256 y) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { z := mul(gt(x, y), sub(x, y)) } } /// @dev Returns `min(2 ** 256 - 1, x + y)`. function saturatingAdd(uint256 x, uint256 y) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { z := or(sub(0, lt(add(x, y), x)), add(x, y)) } } /// @dev Returns `min(2 ** 256 - 1, x * y)`. function saturatingMul(uint256 x, uint256 y) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { z := or(sub(or(iszero(x), eq(div(mul(x, y), x), y)), 1), mul(x, y)) } } /// @dev Returns `condition ? x : y`, without branching. function ternary(bool condition, uint256 x, uint256 y) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { z := xor(x, mul(xor(x, y), iszero(condition))) } } /// @dev Returns `condition ? x : y`, without branching. function ternary(bool condition, bytes32 x, bytes32 y) internal pure returns (bytes32 z) { /// @solidity memory-safe-assembly assembly { z := xor(x, mul(xor(x, y), iszero(condition))) } } /// @dev Returns `condition ? x : y`, without branching. function ternary(bool condition, address x, address y) internal pure returns (address z) { /// @solidity memory-safe-assembly assembly { z := xor(x, mul(xor(x, y), iszero(condition))) } } /// @dev Returns `x != 0 ? x : y`, without branching. function coalesce(uint256 x, uint256 y) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { z := or(x, mul(y, iszero(x))) } } /// @dev Returns `x != bytes32(0) ? x : y`, without branching. function coalesce(bytes32 x, bytes32 y) internal pure returns (bytes32 z) { /// @solidity memory-safe-assembly assembly { z := or(x, mul(y, iszero(x))) } } /// @dev Returns `x != address(0) ? x : y`, without branching. function coalesce(address x, address y) internal pure returns (address z) { /// @solidity memory-safe-assembly assembly { z := or(x, mul(y, iszero(shl(96, x)))) } } /// @dev Exponentiate `x` to `y` by squaring, denominated in base `b`. /// Reverts if the computation overflows. function rpow(uint256 x, uint256 y, uint256 b) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { z := mul(b, iszero(y)) // `0 ** 0 = 1`. Otherwise, `0 ** n = 0`. if x { z := xor(b, mul(xor(b, x), and(y, 1))) // `z = isEven(y) ? scale : x` let half := shr(1, b) // Divide `b` by 2. // Divide `y` by 2 every iteration. for { y := shr(1, y) } y { y := shr(1, y) } { let xx := mul(x, x) // Store x squared. let xxRound := add(xx, half) // Round to the nearest number. // Revert if `xx + half` overflowed, or if `x ** 2` overflows. if or(lt(xxRound, xx), shr(128, x)) { mstore(0x00, 0x49f7642b) // `RPowOverflow()`. revert(0x1c, 0x04) } x := div(xxRound, b) // Set `x` to scaled `xxRound`. // If `y` is odd: if and(y, 1) { let zx := mul(z, x) // Compute `z * x`. let zxRound := add(zx, half) // Round to the nearest number. // If `z * x` overflowed or `zx + half` overflowed: if or(xor(div(zx, x), z), lt(zxRound, zx)) { // Revert if `x` is non-zero. if x { mstore(0x00, 0x49f7642b) // `RPowOverflow()`. revert(0x1c, 0x04) } } z := div(zxRound, b) // Return properly scaled `zxRound`. } } } } } /// @dev Returns the square root of `x`, rounded down. function sqrt(uint256 x) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { // `floor(sqrt(2**15)) = 181`. `sqrt(2**15) - 181 = 2.84`. z := 181 // The "correct" value is 1, but this saves a multiplication later. // This segment is to get a reasonable initial estimate for the Babylonian method. With a bad // start, the correct # of bits increases ~linearly each iteration instead of ~quadratically. // Let `y = x / 2**r`. We check `y >= 2**(k + 8)` // but shift right by `k` bits to ensure that if `x >= 256`, then `y >= 256`. let r := shl(7, lt(0xffffffffffffffffffffffffffffffffff, x)) r := or(r, shl(6, lt(0xffffffffffffffffff, shr(r, x)))) r := or(r, shl(5, lt(0xffffffffff, shr(r, x)))) r := or(r, shl(4, lt(0xffffff, shr(r, x)))) z := shl(shr(1, r), z) // Goal was to get `z*z*y` within a small factor of `x`. More iterations could // get y in a tighter range. Currently, we will have y in `[256, 256*(2**16))`. // We ensured `y >= 256` so that the relative difference between `y` and `y+1` is small. // That's not possible if `x < 256` but we can just verify those cases exhaustively. // Now, `z*z*y <= x < z*z*(y+1)`, and `y <= 2**(16+8)`, and either `y >= 256`, or `x < 256`. // Correctness can be checked exhaustively for `x < 256`, so we assume `y >= 256`. // Then `z*sqrt(y)` is within `sqrt(257)/sqrt(256)` of `sqrt(x)`, or about 20bps. // For `s` in the range `[1/256, 256]`, the estimate `f(s) = (181/1024) * (s+1)` // is in the range `(1/2.84 * sqrt(s), 2.84 * sqrt(s))`, // with largest error when `s = 1` and when `s = 256` or `1/256`. // Since `y` is in `[256, 256*(2**16))`, let `a = y/65536`, so that `a` is in `[1/256, 256)`. // Then we can estimate `sqrt(y)` using // `sqrt(65536) * 181/1024 * (a + 1) = 181/4 * (y + 65536)/65536 = 181 * (y + 65536)/2**18`. // There is no overflow risk here since `y < 2**136` after the first branch above. z := shr(18, mul(z, add(shr(r, x), 65536))) // A `mul()` is saved from starting `z` at 181. // Given the worst case multiplicative error of 2.84 above, 7 iterations should be enough. z := shr(1, add(z, div(x, z))) z := shr(1, add(z, div(x, z))) z := shr(1, add(z, div(x, z))) z := shr(1, add(z, div(x, z))) z := shr(1, add(z, div(x, z))) z := shr(1, add(z, div(x, z))) z := shr(1, add(z, div(x, z))) // If `x+1` is a perfect square, the Babylonian method cycles between // `floor(sqrt(x))` and `ceil(sqrt(x))`. This statement ensures we return floor. // See: https://en.wikipedia.org/wiki/Integer_square_root#Using_only_integer_division z := sub(z, lt(div(x, z), z)) } } /// @dev Returns the cube root of `x`, rounded down. /// Credit to bout3fiddy and pcaversaccio under AGPLv3 license: /// https://github.com/pcaversaccio/snekmate/blob/main/src/snekmate/utils/math.vy /// Formally verified by xuwinnie: /// https://github.com/vectorized/solady/blob/main/audits/xuwinnie-solady-cbrt-proof.pdf function cbrt(uint256 x) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { let r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x)) r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x)))) r := or(r, shl(5, lt(0xffffffff, shr(r, x)))) r := or(r, shl(4, lt(0xffff, shr(r, x)))) r := or(r, shl(3, lt(0xff, shr(r, x)))) // Makeshift lookup table to nudge the approximate log2 result. z := div(shl(div(r, 3), shl(lt(0xf, shr(r, x)), 0xf)), xor(7, mod(r, 3))) // Newton-Raphson's. z := div(add(add(div(x, mul(z, z)), z), z), 3) z := div(add(add(div(x, mul(z, z)), z), z), 3) z := div(add(add(div(x, mul(z, z)), z), z), 3) z := div(add(add(div(x, mul(z, z)), z), z), 3) z := div(add(add(div(x, mul(z, z)), z), z), 3) z := div(add(add(div(x, mul(z, z)), z), z), 3) z := div(add(add(div(x, mul(z, z)), z), z), 3) // Round down. z := sub(z, lt(div(x, mul(z, z)), z)) } } /// @dev Returns the square root of `x`, denominated in `WAD`, rounded down. function sqrtWad(uint256 x) internal pure returns (uint256 z) { unchecked { if (x <= type(uint256).max / 10 ** 18) return sqrt(x * 10 ** 18); z = (1 + sqrt(x)) * 10 ** 9; z = (fullMulDivUnchecked(x, 10 ** 18, z) + z) >> 1; } /// @solidity memory-safe-assembly assembly { z := sub(z, gt(999999999999999999, sub(mulmod(z, z, x), 1))) // Round down. } } /// @dev Returns the cube root of `x`, denominated in `WAD`, rounded down. /// Formally verified by xuwinnie: /// https://github.com/vectorized/solady/blob/main/audits/xuwinnie-solady-cbrt-proof.pdf function cbrtWad(uint256 x) internal pure returns (uint256 z) { unchecked { if (x <= type(uint256).max / 10 ** 36) return cbrt(x * 10 ** 36); z = (1 + cbrt(x)) * 10 ** 12; z = (fullMulDivUnchecked(x, 10 ** 36, z * z) + z + z) / 3; } /// @solidity memory-safe-assembly assembly { let p := x for {} 1 {} { if iszero(shr(229, p)) { if iszero(shr(199, p)) { p := mul(p, 100000000000000000) // 10 ** 17. break } p := mul(p, 100000000) // 10 ** 8. break } if iszero(shr(249, p)) { p := mul(p, 100) } break } let t := mulmod(mul(z, z), z, p) z := sub(z, gt(lt(t, shr(1, p)), iszero(t))) // Round down. } } /// @dev Returns `sqrt(x * y)`. Also called the geometric mean. function mulSqrt(uint256 x, uint256 y) internal pure returns (uint256 z) { if (x == y) return x; uint256 p = rawMul(x, y); if (y == rawDiv(p, x)) return sqrt(p); for (z = saturatingMul(rawAdd(sqrt(x), 1), rawAdd(sqrt(y), 1));; z = avg(z, p)) { if ((p = fullMulDivUnchecked(x, y, z)) >= z) break; } } /// @dev Returns the factorial of `x`. function factorial(uint256 x) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { z := 1 if iszero(lt(x, 58)) { mstore(0x00, 0xaba0f2a2) // `FactorialOverflow()`. revert(0x1c, 0x04) } for {} x { x := sub(x, 1) } { z := mul(z, x) } } } /// @dev Returns the log2 of `x`. /// Equivalent to computing the index of the most significant bit (MSB) of `x`. /// Returns 0 if `x` is zero. function log2(uint256 x) internal pure returns (uint256 r) { /// @solidity memory-safe-assembly assembly { r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x)) r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x)))) r := or(r, shl(5, lt(0xffffffff, shr(r, x)))) r := or(r, shl(4, lt(0xffff, shr(r, x)))) r := or(r, shl(3, lt(0xff, shr(r, x)))) // forgefmt: disable-next-item r := or(r, byte(and(0x1f, shr(shr(r, x), 0x8421084210842108cc6318c6db6d54be)), 0x0706060506020504060203020504030106050205030304010505030400000000)) } } /// @dev Returns the log2 of `x`, rounded up. /// Returns 0 if `x` is zero. function log2Up(uint256 x) internal pure returns (uint256 r) { r = log2(x); /// @solidity memory-safe-assembly assembly { r := add(r, lt(shl(r, 1), x)) } } /// @dev Returns the log10 of `x`. /// Returns 0 if `x` is zero. function log10(uint256 x) internal pure returns (uint256 r) { /// @solidity memory-safe-assembly assembly { if iszero(lt(x, 100000000000000000000000000000000000000)) { x := div(x, 100000000000000000000000000000000000000) r := 38 } if iszero(lt(x, 100000000000000000000)) { x := div(x, 100000000000000000000) r := add(r, 20) } if iszero(lt(x, 10000000000)) { x := div(x, 10000000000) r := add(r, 10) } if iszero(lt(x, 100000)) { x := div(x, 100000) r := add(r, 5) } r := add(r, add(gt(x, 9), add(gt(x, 99), add(gt(x, 999), gt(x, 9999))))) } } /// @dev Returns the log10 of `x`, rounded up. /// Returns 0 if `x` is zero. function log10Up(uint256 x) internal pure returns (uint256 r) { r = log10(x); /// @solidity memory-safe-assembly assembly { r := add(r, lt(exp(10, r), x)) } } /// @dev Returns the log256 of `x`. /// Returns 0 if `x` is zero. function log256(uint256 x) internal pure returns (uint256 r) { /// @solidity memory-safe-assembly assembly { r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x)) r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x)))) r := or(r, shl(5, lt(0xffffffff, shr(r, x)))) r := or(r, shl(4, lt(0xffff, shr(r, x)))) r := or(shr(3, r), lt(0xff, shr(r, x))) } } /// @dev Returns the log256 of `x`, rounded up. /// Returns 0 if `x` is zero. function log256Up(uint256 x) internal pure returns (uint256 r) { r = log256(x); /// @solidity memory-safe-assembly assembly { r := add(r, lt(shl(shl(3, r), 1), x)) } } /// @dev Returns the scientific notation format `mantissa * 10 ** exponent` of `x`. /// Useful for compressing prices (e.g. using 25 bit mantissa and 7 bit exponent). function sci(uint256 x) internal pure returns (uint256 mantissa, uint256 exponent) { /// @solidity memory-safe-assembly assembly { mantissa := x if mantissa { if iszero(mod(mantissa, 1000000000000000000000000000000000)) { mantissa := div(mantissa, 1000000000000000000000000000000000) exponent := 33 } if iszero(mod(mantissa, 10000000000000000000)) { mantissa := div(mantissa, 10000000000000000000) exponent := add(exponent, 19) } if iszero(mod(mantissa, 1000000000000)) { mantissa := div(mantissa, 1000000000000) exponent := add(exponent, 12) } if iszero(mod(mantissa, 1000000)) { mantissa := div(mantissa, 1000000) exponent := add(exponent, 6) } if iszero(mod(mantissa, 10000)) { mantissa := div(mantissa, 10000) exponent := add(exponent, 4) } if iszero(mod(mantissa, 100)) { mantissa := div(mantissa, 100) exponent := add(exponent, 2) } if iszero(mod(mantissa, 10)) { mantissa := div(mantissa, 10) exponent := add(exponent, 1) } } } } /// @dev Convenience function for packing `x` into a smaller number using `sci`. /// The `mantissa` will be in bits [7..255] (the upper 249 bits). /// The `exponent` will be in bits [0..6] (the lower 7 bits). /// Use `SafeCastLib` to safely ensure that the `packed` number is small /// enough to fit in the desired unsigned integer type: /// ``` /// uint32 packed = SafeCastLib.toUint32(FixedPointMathLib.packSci(777 ether)); /// ``` function packSci(uint256 x) internal pure returns (uint256 packed) { (x, packed) = sci(x); // Reuse for `mantissa` and `exponent`. /// @solidity memory-safe-assembly assembly { if shr(249, x) { mstore(0x00, 0xce30380c) // `MantissaOverflow()`. revert(0x1c, 0x04) } packed := or(shl(7, x), packed) } } /// @dev Convenience function for unpacking a packed number from `packSci`. function unpackSci(uint256 packed) internal pure returns (uint256 unpacked) { unchecked { unpacked = (packed >> 7) * 10 ** (packed & 0x7f); } } /// @dev Returns the average of `x` and `y`. Rounds towards zero. function avg(uint256 x, uint256 y) internal pure returns (uint256 z) { unchecked { z = (x & y) + ((x ^ y) >> 1); } } /// @dev Returns the average of `x` and `y`. Rounds towards negative infinity. function avg(int256 x, int256 y) internal pure returns (int256 z) { unchecked { z = (x >> 1) + (y >> 1) + (x & y & 1); } } /// @dev Returns the absolute value of `x`. function abs(int256 x) internal pure returns (uint256 z) { unchecked { z = (uint256(x) + uint256(x >> 255)) ^ uint256(x >> 255); } } /// @dev Returns the absolute distance between `x` and `y`. function dist(uint256 x, uint256 y) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { z := add(xor(sub(0, gt(x, y)), sub(y, x)), gt(x, y)) } } /// @dev Returns the absolute distance between `x` and `y`. function dist(int256 x, int256 y) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { z := add(xor(sub(0, sgt(x, y)), sub(y, x)), sgt(x, y)) } } /// @dev Returns the minimum of `x` and `y`. function min(uint256 x, uint256 y) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { z := xor(x, mul(xor(x, y), lt(y, x))) } } /// @dev Returns the minimum of `x` and `y`. function min(int256 x, int256 y) internal pure returns (int256 z) { /// @solidity memory-safe-assembly assembly { z := xor(x, mul(xor(x, y), slt(y, x))) } } /// @dev Returns the maximum of `x` and `y`. function max(uint256 x, uint256 y) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { z := xor(x, mul(xor(x, y), gt(y, x))) } } /// @dev Returns the maximum of `x` and `y`. function max(int256 x, int256 y) internal pure returns (int256 z) { /// @solidity memory-safe-assembly assembly { z := xor(x, mul(xor(x, y), sgt(y, x))) } } /// @dev Returns `x`, bounded to `minValue` and `maxValue`. function clamp(uint256 x, uint256 minValue, uint256 maxValue) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { z := xor(x, mul(xor(x, minValue), gt(minValue, x))) z := xor(z, mul(xor(z, maxValue), lt(maxValue, z))) } } /// @dev Returns `x`, bounded to `minValue` and `maxValue`. function clamp(int256 x, int256 minValue, int256 maxValue) internal pure returns (int256 z) { /// @solidity memory-safe-assembly assembly { z := xor(x, mul(xor(x, minValue), sgt(minValue, x))) z := xor(z, mul(xor(z, maxValue), slt(maxValue, z))) } } /// @dev Returns greatest common divisor of `x` and `y`. function gcd(uint256 x, uint256 y) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { for { z := x } y {} { let t := y y := mod(z, y) z := t } } } /// @dev Returns `a + (b - a) * (t - begin) / (end - begin)`, /// with `t` clamped between `begin` and `end` (inclusive). /// Agnostic to the order of (`a`, `b`) and (`end`, `begin`). /// If `begins == end`, returns `t <= begin ? a : b`. function lerp(uint256 a, uint256 b, uint256 t, uint256 begin, uint256 end) internal pure returns (uint256) { if (begin > end) (t, begin, end) = (~t, ~begin, ~end); if (t <= begin) return a; if (t >= end) return b; unchecked { if (b >= a) return a + fullMulDiv(b - a, t - begin, end - begin); return a - fullMulDiv(a - b, t - begin, end - begin); } } /// @dev Returns `a + (b - a) * (t - begin) / (end - begin)`. /// with `t` clamped between `begin` and `end` (inclusive). /// Agnostic to the order of (`a`, `b`) and (`end`, `begin`). /// If `begins == end`, returns `t <= begin ? a : b`. function lerp(int256 a, int256 b, int256 t, int256 begin, int256 end) internal pure returns (int256) { if (begin > end) (t, begin, end) = (~t, ~begin, ~end); if (t <= begin) return a; if (t >= end) return b; // forgefmt: disable-next-item unchecked { if (b >= a) return int256(uint256(a) + fullMulDiv(uint256(b - a), uint256(t - begin), uint256(end - begin))); return int256(uint256(a) - fullMulDiv(uint256(a - b), uint256(t - begin), uint256(end - begin))); } } /// @dev Returns if `x` is an even number. Some people may need this. function isEven(uint256 x) internal pure returns (bool) { return x & uint256(1) == uint256(0); } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* RAW NUMBER OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Returns `x + y`, without checking for overflow. function rawAdd(uint256 x, uint256 y) internal pure returns (uint256 z) { unchecked { z = x + y; } } /// @dev Returns `x + y`, without checking for overflow. function rawAdd(int256 x, int256 y) internal pure returns (int256 z) { unchecked { z = x + y; } } /// @dev Returns `x - y`, without checking for underflow. function rawSub(uint256 x, uint256 y) internal pure returns (uint256 z) { unchecked { z = x - y; } } /// @dev Returns `x - y`, without checking for underflow. function rawSub(int256 x, int256 y) internal pure returns (int256 z) { unchecked { z = x - y; } } /// @dev Returns `x * y`, without checking for overflow. function rawMul(uint256 x, uint256 y) internal pure returns (uint256 z) { unchecked { z = x * y; } } /// @dev Returns `x * y`, without checking for overflow. function rawMul(int256 x, int256 y) internal pure returns (int256 z) { unchecked { z = x * y; } } /// @dev Returns `x / y`, returning 0 if `y` is zero. function rawDiv(uint256 x, uint256 y) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { z := div(x, y) } } /// @dev Returns `x / y`, returning 0 if `y` is zero. function rawSDiv(int256 x, int256 y) internal pure returns (int256 z) { /// @solidity memory-safe-assembly assembly { z := sdiv(x, y) } } /// @dev Returns `x % y`, returning 0 if `y` is zero. function rawMod(uint256 x, uint256 y) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { z := mod(x, y) } } /// @dev Returns `x % y`, returning 0 if `y` is zero. function rawSMod(int256 x, int256 y) internal pure returns (int256 z) { /// @solidity memory-safe-assembly assembly { z := smod(x, y) } } /// @dev Returns `(x + y) % d`, return 0 if `d` if zero. function rawAddMod(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { z := addmod(x, y, d) } } /// @dev Returns `(x * y) % d`, return 0 if `d` if zero. function rawMulMod(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { z := mulmod(x, y, d) } } }lib/mangrove-strats/src/strategies/routers/abstract/RoutingOrderLib.sol
// SPDX-License-Identifier: BSD-2-Clause pragma solidity ^0.8.10; import {IERC20} from "@mgv/lib/IERC20.sol"; /// @title Library to obtain Routing orders of various kind library RoutingOrderLib { ///@notice pointer to first storage slot used for randomized access bytes32 private constant OFFSET = keccak256("MangroveStrats.RoutingOrderLib.Layout"); ///@notice Argument to pull/push ///@param token the asset to be routed ///@param olKeyHash the id of the market that triggered the calling offer logic. Is bytes32(0) when routing is done outside offer logic. ///@param offerId the id of the offer that triggered the calling offer logic. Is uint(0) when routing is done outsider offer logic. ///@param fundOwner the owner of the routed funds. If calling router is a proxy, it address is determined by `fundOwner`. struct RoutingOrder { IERC20 token; bytes32 olKeyHash; uint offerId; address fundOwner; } ///@notice helper to create a RoutingOrder struct without zero'ed fields for market coordinates. ///@param token the asset to be routed ///@param fundOwner the owner of the routed funds ///@return ro the routing order struct function createOrder(IERC20 token, address fundOwner) internal pure returns (RoutingOrder memory ro) { ro.token = token; ro.fundOwner = fundOwner; } ///@notice the bound maker contracts which are allowed to call this router. struct Layout { mapping(address => bool) boundMakerContracts; } ///@notice allowed pullers/pushers for this router ///@return bound contracts to this router, in the form of a storage mapping function boundMakerContracts() internal view returns (mapping(address => bool) storage) { bytes32 offset = OFFSET; Layout storage st; assembly ("memory-safe") { st.slot := offset } return st.boundMakerContracts; } }lib/mangrove-strats/src/strategies/offer_maker/market_making/kandel/abstract/GeometricKandel.sol
// SPDX-License-Identifier: MIT pragma solidity ^0.8.10; import {IMangrove} from "@mgv/src/IMangrove.sol"; import {CoreKandel} from "./CoreKandel.sol"; import {MAX_TICK, MIN_TICK} from "@mgv/lib/core/Constants.sol"; import {OLKey} from "@mgv/src/core/MgvLib.sol"; import {KandelLib} from "./KandelLib.sol"; import {Tick} from "@mgv/lib/core/TickLib.sol"; ///@title Adds a geometric price progression to a `CoreKandel` strat without storing prices for individual price points. abstract contract GeometricKandel is CoreKandel { ///@notice The tick offset for absolute price used for the on-chain geometric progression deployment in `createDistribution`. It is recommended that this is a multiple of tickSpacing for the offer lists to avoid rounding. ///@param value the tick offset. event SetBaseQuoteTickOffset(uint value); ///@notice By emitting this data, an indexer will be able to keep track of what the stepSize and tickOffset is for the Kandel instance. ///@notice The tick offset for absolute price used for the on-chain geometric progression deployment in `createDistribution`. It is recommended that this is a multiple of tickSpacing for the offer lists to avoid rounding. uint public baseQuoteTickOffset; ///@notice Constructor ///@param mgv The Mangrove deployment. ///@param olKeyBaseQuote The OLKey for the outbound_tkn base and inbound_tkn quote offer list Kandel will act on, the flipped OLKey is used for the opposite offer list. ///@param routerParams parameters if this contract is using a router constructor(IMangrove mgv, OLKey memory olKeyBaseQuote, RouterParams memory routerParams) CoreKandel(mgv, olKeyBaseQuote, routerParams) {} ///@notice sets the tick offset if different from existing. ///@param _baseQuoteTickOffset the new tick offset. function setBaseQuoteTickOffset(uint _baseQuoteTickOffset) public onlyAdmin { require(uint24(_baseQuoteTickOffset) == _baseQuoteTickOffset, "Kandel/tickOffsetTooHigh"); if (baseQuoteTickOffset != _baseQuoteTickOffset) { baseQuoteTickOffset = _baseQuoteTickOffset; emit SetBaseQuoteTickOffset(_baseQuoteTickOffset); } } ///@notice Creates a distribution of bids and asks given by the parameters. Dual offers are included with gives=0. ///@param from populate offers starting from this index (inclusive). Must be at most `pricePoints`. ///@param to populate offers until this index (exclusive). Must be at most `pricePoints`. ///@param baseQuoteTickIndex0 the tick for the price point at index 0 given as a tick on the `base, quote` offer list, i.e. corresponding to an ask with a quote/base ratio. It is recommended that this is a multiple of tickSpacing for the offer lists to avoid rounding. ///@param _baseQuoteTickOffset the tick offset used for the geometric progression deployment. Must be at least 1. It is recommended that this is a multiple of tickSpacing for the offer lists to avoid rounding. ///@param firstAskIndex the (inclusive) index after which offer should be an ask. Must be at most `pricePoints`. ///@param bidGives The initial amount of quote to give for all bids. If 0, only book the offer, if type(uint).max then askGives is used as base for bids, and the quote the bid gives is set to according to the price. ///@param askGives The initial amount of base to give for all asks. If 0, only book the offer, if type(uint).max then bidGives is used as quote for asks, and the base the ask gives is set to according to the price. ///@param stepSize in amount of price points to jump for posting dual offer. Must be less than `pricePoints`. ///@param pricePoints the number of price points for the Kandel instance. Must be at least 2. ///@return distribution the distribution of bids and asks to populate ///@dev the absolute price of an offer is the ratio of quote/base volumes of tokens it trades ///@dev the tick of offers on Mangrove are in relative taker price of maker's inbound/outbound volumes of tokens it trades ///@dev for Bids, outbound_tkn=quote, inbound_tkn=base so relative taker price of a a bid is the inverse of the absolute price. ///@dev for Asks, outbound_tkn=base, inbound_tkn=quote so relative taker price of an ask coincides with absolute price. ///@dev Index0 will contain the ask with the lowest relative price and the bid with the highest relative price. Absolute price is geometrically increasing over indexes. ///@dev tickOffset moves an offer relative price s.t. `AskTick_{i+1} = AskTick_i + tickOffset` and `BidTick_{i+1} = BidTick_i - tickOffset` ///@dev A hole is left in the middle at the size of stepSize - either an offer or its dual is posted, not both. ///@dev The caller should make sure the minimum and maximum tick does not exceed the MIN_TICK and MAX_TICK from respectively; otherwise, populate will fail for those offers. ///@dev If type(uint).max is used for `bidGives` or `askGives` then very high or low prices can yield gives=0 (which results in both offer an dual being dead) or gives>=type(uin96).max which is not supported by Mangrove. function createDistribution( uint from, uint to, Tick baseQuoteTickIndex0, uint _baseQuoteTickOffset, uint firstAskIndex, uint bidGives, uint askGives, uint pricePoints, uint stepSize ) public pure returns (Distribution memory distribution) { return KandelLib.createGeometricDistribution( from, to, baseQuoteTickIndex0, _baseQuoteTickOffset, firstAskIndex, bidGives, askGives, pricePoints, stepSize ); } ///@notice publishes bids/asks according to a geometric distribution, and sets all parameters according to inputs. ///@param from populate offers starting from this index (inclusive). ///@param to populate offers until this index (exclusive). ///@param baseQuoteTickIndex0 the tick for the price point at index 0 given as a tick on the `base, quote` offer list, i.e. corresponding to an ask with a quote/base ratio. It is recommended that this is a multiple of tickSpacing for the offer lists to avoid rounding. ///@param _baseQuoteTickOffset the tick offset used for the geometric progression deployment. It is recommended that this is a multiple of tickSpacing for the offer lists to avoid rounding. ///@param firstAskIndex the (inclusive) index after which offer should be an ask. ///@param bidGives The initial amount of quote to give for all bids. If 0, only book the offer, if type(uint).max then askGives is used as base for bids, and the quote the bid gives is set to according to the price. ///@param askGives The initial amount of base to give for all asks. If 0, only book the offer, if type(uint).max then bidGives is used as quote for asks, and the base the ask gives is set to according to the price. ///@param parameters the parameters for Kandel. Only changed parameters will cause updates. Set `gasreq` and `gasprice` to 0 to keep existing values. ///@param baseAmount base amount to deposit ///@param quoteAmount quote amount to deposit ///@dev See `createDistribution` for further details. function populateFromOffset( uint from, uint to, Tick baseQuoteTickIndex0, uint _baseQuoteTickOffset, uint firstAskIndex, uint bidGives, uint askGives, Params calldata parameters, uint baseAmount, uint quoteAmount ) public payable onlyAdmin { if (msg.value > 0) { MGV.fund{value: msg.value}(); } setParams(parameters); setBaseQuoteTickOffset(_baseQuoteTickOffset); depositFunds(baseAmount, quoteAmount); populateChunkFromOffset(from, to, baseQuoteTickIndex0, firstAskIndex, bidGives, askGives); } ///@notice publishes bids/asks according to a geometric distribution, and reads parameters from the Kandel instance. ///@param from populate offers starting from this index (inclusive). ///@param to populate offers until this index (exclusive). ///@param baseQuoteTickIndex0 the tick for the price point at index 0 given as a tick on the `base, quote` offer list, i.e. corresponding to an ask with a quote/base ratio. It is recommended that this is a multiple of tickSpacing for the offer lists to avoid rounding. ///@param firstAskIndex the (inclusive) index after which offer should be an ask. ///@param bidGives The initial amount of quote to give for all bids. If 0, only book the offer, if type(uint).max then askGives is used as base for bids, and the quote the bid gives is set to according to the price. ///@param askGives The initial amount of base to give for all asks. If 0, only book the offer, if type(uint).max then bidGives is used as quote for asks, and the base the ask gives is set to according to the price. ///@dev This is typically used after a call to `populateFromOffset` to populate the rest of the offers with the same parameters. See that function for further details. function populateChunkFromOffset( uint from, uint to, Tick baseQuoteTickIndex0, uint firstAskIndex, uint bidGives, uint askGives ) public payable onlyAdmin { Params memory parameters = params; Distribution memory distribution = createDistribution( from, to, baseQuoteTickIndex0, baseQuoteTickOffset, firstAskIndex, bidGives, askGives, parameters.pricePoints, parameters.stepSize ); populateChunkInternal(distribution, parameters.gasreq, parameters.gasprice); } }lib/mangrove-core/lib/core/TickLib.sol
// SPDX-License-Identifier: MIT pragma solidity ^0.8.17; import {Bin} from "@mgv/lib/core/TickTreeLib.sol"; import "@mgv/lib/core/BitLib.sol"; import "@mgv/lib/core/Constants.sol"; /* This file is inspired by Uniswap's approach to ticks, with the following notable changes: - directly compute ticks base 1.0001 (not base `sqrt(1.0001)`) - directly compute ratios (not `sqrt(ratio)`) (simpler code elsewhere when dealing with actual ratios and logs of ratios) - ratios are floating-point numbers, not fixed-point numbers (increases precision when computing amounts) */ /* # TickLib The `TickLib` file contains tick math-related code and utilities for manipulating ticks. It also holds functions related to ratios, which are represented as (mantissa,exponent) pairs. */ /* Globally enable `tick.method(...)` */ type Tick is int; using TickLib for Tick global; library TickLib { function inRange(Tick tick) internal pure returns (bool) { return Tick.unwrap(tick) >= MIN_TICK && Tick.unwrap(tick) <= MAX_TICK; } function eq(Tick tick1, Tick tick2) internal pure returns (bool) { unchecked { return Tick.unwrap(tick1) == Tick.unwrap(tick2); } } /* Returns the nearest, higher bin to the given `tick` at the given `tickSpacing` We do not force ticks to fit the tickSpacing (aka `tick%tickSpacing==0`). Ratios are rounded up that the maker is always paid at least what they asked for */ function nearestBin(Tick tick, uint tickSpacing) internal pure returns (Bin bin) { unchecked { // By default division rounds towards 0. Since `smod` is signed we get the sign of `tick` and `tick%tickSpacing` in a single instruction. assembly("memory-safe") { bin := sdiv(tick,tickSpacing) bin := add(bin,sgt(smod(tick,tickSpacing),0)) } } } /* ## Conversion functions */ /* ### (inbound,tick) → outbound `inboundFromOutbound[Up]` converts an outbound amount (i.e. an `offer.gives` or a `takerWants`), to an inbound amount, following the price induced by `tick`. There's a rounding-up and a rounding-down variant. `outboundAmt` should not exceed 127 bits. */ function inboundFromOutbound(Tick tick, uint outboundAmt) internal pure returns (uint) { (uint sig, uint exp) = ratioFromTick(tick); return (sig * outboundAmt) >> exp; } function inboundFromOutboundUp(Tick tick, uint outboundAmt) internal pure returns (uint) { unchecked { (uint sig, uint exp) = ratioFromTick(tick); return divExpUp(sig*outboundAmt,exp); } } /* ### (outbound,tick) → inbound */ /* `outboundFromInbound[Up]` converts an inbound amount (i.e. an `offer.wants` or a `takerGives`), to an outbound amount, following the price induced by `tick`. There's a rounding-up and a rounding-down variant. `inboundAmt` should not exceed 127 bits. */ function outboundFromInbound(Tick tick, uint inboundAmt) internal pure returns (uint) { (uint sig, uint exp) = ratioFromTick(Tick.wrap(-Tick.unwrap(tick))); return (sig * inboundAmt) >> exp; } function outboundFromInboundUp(Tick tick, uint inboundAmt) internal pure returns (uint) { unchecked { (uint sig, uint exp) = ratioFromTick(Tick.wrap(-Tick.unwrap(tick))); return divExpUp(sig*inboundAmt,exp); } } /* ## Ratio representation Ratios are represented as a (mantissa,exponent) pair which represents the number `mantissa * 2**-exponent`. The exponent is negated so that, for ratios in the accepted range, the exponent is `>= 0`. This simplifies the code. Floats are normalized so that the mantissa uses exactly 128 bits. It enables easy comparison between floats and ensures they can be multiplied by amounts without overflow. The accepted ratio range is between `ratioFromTick(MIN_TICK)` and `ratioFromTick(MAX_TICK)` (inclusive). */ /* ### (inbound,outbound) → ratio */ /* `ratioFromVolumes` converts a pair of (inbound,outbound) volumes to a floating-point, normalized ratio. It rounds down. * `outboundAmt = 0` has a special meaning and the highest possible price will be returned. * `inboundAmt = 0` has a special meaning if `outboundAmt != 0` and the lowest possible price will be returned. */ function ratioFromVolumes(uint inboundAmt, uint outboundAmt) internal pure returns (uint mantissa, uint exp) { unchecked { require(inboundAmt <= MAX_SAFE_VOLUME, "mgv/ratioFromVol/inbound/tooBig"); require(outboundAmt <= MAX_SAFE_VOLUME, "mgv/ratioFromVol/outbound/tooBig"); if (outboundAmt == 0) { return (MAX_RATIO_MANTISSA,uint(MAX_RATIO_EXP)); } else if (inboundAmt == 0) { return (MIN_RATIO_MANTISSA,uint(MIN_RATIO_EXP)); } uint ratio = (inboundAmt << MANTISSA_BITS) / outboundAmt; uint log2 = BitLib.fls(ratio); require(ratio != 0,"mgv/ratioFromVolumes/zeroRatio"); if (log2 > MANTISSA_BITS_MINUS_ONE) { uint diff = log2 - MANTISSA_BITS_MINUS_ONE; return (ratio >> diff, MANTISSA_BITS - diff); } else { uint diff = MANTISSA_BITS_MINUS_ONE - log2; return (ratio << diff, MANTISSA_BITS + diff); } } } /* ### (inbound,outbound) → tick */ function tickFromVolumes(uint inboundAmt, uint outboundAmt) internal pure returns (Tick tick) { (uint man, uint exp) = ratioFromVolumes(inboundAmt, outboundAmt); return tickFromNormalizedRatio(man,exp); } /* ### ratio → tick */ /* Does not require a normalized ratio. */ function tickFromRatio(uint mantissa, int exp) internal pure returns (Tick) { uint normalized_exp; (mantissa, normalized_exp) = normalizeRatio(mantissa, exp); return tickFromNormalizedRatio(mantissa,normalized_exp); } /* ### low-level ratio → tick */ /* Given `ratio`, return greatest tick `t` such that `ratioFromTick(t) <= ratio`. * Input ratio must be within the maximum and minimum ratios returned by the available ticks. * Does _not_ expected a normalized float. The function works as follows: * Approximate log2(ratio) to the 13th fractional digit. * Following <a href="https://hackmd.io/@mangrovedao/HJvl21zla">https://hackmd.io/@mangrovedao/HJvl21zla</a>, obtain `tickLow` and `tickHigh` such that $\log_{1.0001}(ratio)$ is between them * Return the highest one that yields a ratio below the input ratio. */ function tickFromNormalizedRatio(uint mantissa, uint exp) internal pure returns (Tick tick) { if (floatLt(mantissa, exp, MIN_RATIO_MANTISSA, uint(MIN_RATIO_EXP))) { revert("mgv/tickFromRatio/tooLow"); } if (floatLt(MAX_RATIO_MANTISSA, uint(MAX_RATIO_EXP), mantissa, exp)) { revert("mgv/tickFromRatio/tooHigh"); } int log2ratio = int(MANTISSA_BITS_MINUS_ONE) - int(exp) << 64; uint mpow = mantissa >> MANTISSA_BITS_MINUS_ONE - 127; // give 129 bits of room left /* How the fractional digits of the log are computed: * for a given `n` compute $n^2$. * If $\lfloor\log_2(n^2)\rfloor = 2\lfloor\log_2(n)\rfloor$ then the fractional part of $\log_2(n^2)$ was $< 0.5$ (first digit is 0). * If $\lfloor\log_2(n^2)\rfloor = 1 + 2\lfloor\log_2(n)\rfloor$ then the fractional part of $\log_2(n^2)$ was $\geq 0.5$ (first digit is 1). * Apply starting with `n=mpow` repeatedly by keeping `n` on 127 bits through right-shifts (has no impact on high fractional bits). */ assembly ("memory-safe") { // 13 bits of precision mpow := shr(127, mul(mpow, mpow)) let highbit := shr(128, mpow) log2ratio := or(log2ratio, shl(63, highbit)) mpow := shr(highbit, mpow) mpow := shr(127, mul(mpow, mpow)) highbit := shr(128, mpow) log2ratio := or(log2ratio, shl(62, highbit)) mpow := shr(highbit, mpow) mpow := shr(127, mul(mpow, mpow)) highbit := shr(128, mpow) log2ratio := or(log2ratio, shl(61, highbit)) mpow := shr(highbit, mpow) mpow := shr(127, mul(mpow, mpow)) highbit := shr(128, mpow) log2ratio := or(log2ratio, shl(60, highbit)) mpow := shr(highbit, mpow) mpow := shr(127, mul(mpow, mpow)) highbit := shr(128, mpow) log2ratio := or(log2ratio, shl(59, highbit)) mpow := shr(highbit, mpow) mpow := shr(127, mul(mpow, mpow)) highbit := shr(128, mpow) log2ratio := or(log2ratio, shl(58, highbit)) mpow := shr(highbit, mpow) mpow := shr(127, mul(mpow, mpow)) highbit := shr(128, mpow) log2ratio := or(log2ratio, shl(57, highbit)) mpow := shr(highbit, mpow) mpow := shr(127, mul(mpow, mpow)) highbit := shr(128, mpow) log2ratio := or(log2ratio, shl(56, highbit)) mpow := shr(highbit, mpow) mpow := shr(127, mul(mpow, mpow)) highbit := shr(128, mpow) log2ratio := or(log2ratio, shl(55, highbit)) mpow := shr(highbit, mpow) mpow := shr(127, mul(mpow, mpow)) highbit := shr(128, mpow) log2ratio := or(log2ratio, shl(54, highbit)) mpow := shr(highbit, mpow) mpow := shr(127, mul(mpow, mpow)) highbit := shr(128, mpow) log2ratio := or(log2ratio, shl(53, highbit)) mpow := shr(highbit, mpow) mpow := shr(127, mul(mpow, mpow)) highbit := shr(128, mpow) log2ratio := or(log2ratio, shl(52, highbit)) mpow := shr(highbit, mpow) mpow := shr(127, mul(mpow, mpow)) highbit := shr(128, mpow) log2ratio := or(log2ratio, shl(51, highbit)) } // Convert log base 2 to log base 1.0001 (multiply by `log2(1.0001)^-1 << 64`), since log2ratio is x64 this yields a x128 number. int log_bp_ratio = log2ratio * 127869479499801913173571; // tickLow is approx - maximum error int tickLow = int((log_bp_ratio - 1701496478404567508395759362389778998) >> 128); // tickHigh is approx + minimum error int tickHigh = int((log_bp_ratio + 289637967442836606107396900709005211253) >> 128); (uint mantissaHigh, uint expHigh) = ratioFromTick(Tick.wrap(tickHigh)); bool ratioHighGt = floatLt(mantissa, exp, mantissaHigh, expHigh); if (tickLow == tickHigh || ratioHighGt) { tick = Tick.wrap(tickLow); } else { tick = Tick.wrap(tickHigh); } } /* ### tick → ratio conversion function */ /* Returns a normalized (man,exp) ratio floating-point number. The mantissa is on 128 bits to avoid overflow when mulitplying with token amounts. The exponent has no bias. for easy comparison. */ function ratioFromTick(Tick tick) internal pure returns (uint man, uint exp) { unchecked { (man, exp) = nonNormalizedRatioFromTick(tick); int shiftedTick = Tick.unwrap(tick) << LOG_BP_SHIFT; int log2ratio; // floor log2 of ratio towards negative infinity assembly ("memory-safe") { log2ratio := sdiv(shiftedTick,LOG_BP_2X235) log2ratio := sub(log2ratio,slt(smod(shiftedTick,LOG_BP_2X235),0)) } int diff = log2ratio+int(exp)-int(MANTISSA_BITS_MINUS_ONE); if (diff > 0) { // For |tick| <= 887272, this drops at most 5 bits of precision man = man >> uint(diff); } else { man = man << uint(-diff); } // For |tick| << 887272, log2ratio <= 127 exp = uint(int(MANTISSA_BITS_MINUS_ONE)-log2ratio); } } /* ### low-level tick → ratio conversion */ /* Compute 1.0001^tick and returns it as a (mantissa,exponent) pair. Works by checking each set bit of `|tick|` multiplying by `1.0001^(-2**i)<<128` if the ith bit of tick is set. Since we inspect the absolute value of `tick`, `-1048576` is not a valid tick. If the tick is positive this computes `1.0001^-tick`, and we take the inverse at the end. For maximum precision some powers of 1.0001 are shifted until they occupy 128 bits. The `extra_shift` is recorded and added to the exponent. Since the resulting mantissa is left-shifted by 128 bits, if tick was positive, we divide `2**256` by the mantissa to get the 128-bit left-shifted inverse of the mantissa. The error (relative to 1.0001^tick) may be negative or positive. */ function nonNormalizedRatioFromTick(Tick tick) internal pure returns (uint man, uint exp) { uint absTick = Tick.unwrap(tick) < 0 ? uint(-Tick.unwrap(tick)) : uint(Tick.unwrap(tick)); require(absTick <= uint(MAX_TICK), "mgv/absTick/outOfBounds"); int extra_shift; if (absTick & 0x1 != 0) { man = 0xfff97272373d413259a46990580e2139; } else { man = 0x100000000000000000000000000000000; } if (absTick & 0x2 != 0) { man = (man * 0xfff2e50f5f656932ef12357cf3c7fdcb) >> 128; } if (absTick & 0x4 != 0) { man = (man * 0xffe5caca7e10e4e61c3624eaa0941ccf) >> 128; } if (absTick & 0x8 != 0) { man = (man * 0xffcb9843d60f6159c9db58835c926643) >> 128; } if (absTick & 0x10 != 0) { man = (man * 0xff973b41fa98c081472e6896dfb254bf) >> 128; } if (absTick & 0x20 != 0) { man = (man * 0xff2ea16466c96a3843ec78b326b52860) >> 128; } if (absTick & 0x40 != 0) { man = (man * 0xfe5dee046a99a2a811c461f1969c3052) >> 128; } if (absTick & 0x80 != 0) { man = (man * 0xfcbe86c7900a88aedcffc83b479aa3a3) >> 128; } if (absTick & 0x100 != 0) { man = (man * 0xf987a7253ac413176f2b074cf7815e53) >> 128; } if (absTick & 0x200 != 0) { man = (man * 0xf3392b0822b70005940c7a398e4b70f2) >> 128; } if (absTick & 0x400 != 0) { man = (man * 0xe7159475a2c29b7443b29c7fa6e889d8) >> 128; } if (absTick & 0x800 != 0) { man = (man * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128; } if (absTick & 0x1000 != 0) { man = (man * 0xa9f746462d870fdf8a65dc1f90e061e4) >> 128; } if (absTick & 0x2000 != 0) { man = (man * 0xe1b0d342ada5437121767bec575e65ed) >> 128; extra_shift += 1; } if (absTick & 0x4000 != 0) { man = (man * 0xc6f84d7e5f423f66048c541550bf3e96) >> 128; extra_shift += 2; } if (absTick & 0x8000 != 0) { man = (man * 0x9aa508b5b7a84e1c677de54f3e99bc8f) >> 128; extra_shift += 4; } if (absTick & 0x10000 != 0) { man = (man * 0xbad5f1bdb70232cd33865244bdcc089c) >> 128; extra_shift += 9; } if (absTick & 0x20000 != 0) { man = (man * 0x885b9613d7e87aa498106fb7fa5edd37) >> 128; extra_shift += 18; } if (absTick & 0x40000 != 0) { man = (man * 0x9142e0723efb884889d1f447715afacd) >> 128; extra_shift += 37; } if (absTick & 0x80000 != 0) { man = (man * 0xa4d9a773d61316918f140bd96e8e6814) >> 128; extra_shift += 75; } if (Tick.unwrap(tick) > 0) { /* We use [Remco Bloemen's trick](https://xn--2-umb.com/17/512-bit-division/#divide-2-256-by-a-given-number) to divide `2**256` by `man`: */ assembly("memory-safe") { man := add(div(sub(0, man), man), 1) } extra_shift = -extra_shift; } exp = uint(128 + extra_shift); } /* Shift mantissa so it occupies exactly `MANTISSA_BITS` and adjust `exp` in consequence. A float is normalized when its mantissa occupies exactly 128 bits. All in-range normalized floats have `exp >= 0`, so we can use a `uint` for exponents everywhere we expect a normalized float. When a non-normalized float is expected/used, `exp` can be negative since there is no constraint on the size of the mantissa. */ function normalizeRatio(uint mantissa, int exp) internal pure returns (uint, uint) { require(mantissa != 0,"mgv/normalizeRatio/mantissaIs0"); uint log2ratio = BitLib.fls(mantissa); int shift = int(MANTISSA_BITS_MINUS_ONE) - int(log2ratio); if (shift < 0) { mantissa = mantissa >> uint(-shift); } else { mantissa = mantissa << uint(shift); } exp = exp + shift; if (exp < 0) { revert("mgv/normalizeRatio/lowExp"); } return (mantissa,uint(exp)); } /* Return `a/(2**e)` rounded up */ function divExpUp(uint a, uint e) internal pure returns (uint) { unchecked { uint rem; /* Let mask be `(1<<e)-1`, `rem` is 1 if `a & mask > 0`, and 0 otherwise. Explanation: * if a is 0 then `rem` must be 0. `0 & mask` is 0. * else if `e > 255` then `0 < a < 2^e`, so `rem` must be 1. `(1<<e)-1` is `type(uint).max`, so `a & mask is a > 0`. * else `a & mask` is `a % 2**e` */ assembly("memory-safe") { rem := gt(and(a,sub(shl(e,1),1)),0) } return (a>>e) + rem; } } /* Floats are normalized to 128 bits to ensure no overflow when multiplying with amounts, and for easier comparisons. Normalized in-range floats have `exp>=0`. */ function floatLt(uint mantissa_a, uint exp_a, uint mantissa_b, uint exp_b) internal pure returns (bool) { /* Exponents are negated (so that exponents of ratios within the accepted range as >= 0, which simplifies the code), which explains the direction of the `exp_a > exp_b` comparison. */ return (exp_a > exp_b || (exp_a == exp_b && mantissa_a < mantissa_b)); } }lib/solady/src/auth/Ownable.sol
// SPDX-License-Identifier: MIT pragma solidity ^0.8.4; /// @notice Simple single owner authorization mixin. /// @author Solady (https://github.com/vectorized/solady/blob/main/src/auth/Ownable.sol) /// /// @dev Note: /// This implementation does NOT auto-initialize the owner to `msg.sender`. /// You MUST call the `_initializeOwner` in the constructor / initializer. /// /// While the ownable portion follows /// [EIP-173](https://eips.ethereum.org/EIPS/eip-173) for compatibility, /// the nomenclature for the 2-step ownership handover may be unique to this codebase. abstract contract Ownable { /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* CUSTOM ERRORS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev The caller is not authorized to call the function. error Unauthorized(); /// @dev The `newOwner` cannot be the zero address. error NewOwnerIsZeroAddress(); /// @dev The `pendingOwner` does not have a valid handover request. error NoHandoverRequest(); /// @dev Cannot double-initialize. error AlreadyInitialized(); /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* EVENTS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev The ownership is transferred from `oldOwner` to `newOwner`. /// This event is intentionally kept the same as OpenZeppelin's Ownable to be /// compatible with indexers and [EIP-173](https://eips.ethereum.org/EIPS/eip-173), /// despite it not being as lightweight as a single argument event. event OwnershipTransferred(address indexed oldOwner, address indexed newOwner); /// @dev An ownership handover to `pendingOwner` has been requested. event OwnershipHandoverRequested(address indexed pendingOwner); /// @dev The ownership handover to `pendingOwner` has been canceled. event OwnershipHandoverCanceled(address indexed pendingOwner); /// @dev `keccak256(bytes("OwnershipTransferred(address,address)"))`. uint256 private constant _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE = 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0; /// @dev `keccak256(bytes("OwnershipHandoverRequested(address)"))`. uint256 private constant _OWNERSHIP_HANDOVER_REQUESTED_EVENT_SIGNATURE = 0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d; /// @dev `keccak256(bytes("OwnershipHandoverCanceled(address)"))`. uint256 private constant _OWNERSHIP_HANDOVER_CANCELED_EVENT_SIGNATURE = 0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92; /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* STORAGE */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev The owner slot is given by: /// `bytes32(~uint256(uint32(bytes4(keccak256("_OWNER_SLOT_NOT")))))`. /// It is intentionally chosen to be a high value /// to avoid collision with lower slots. /// The choice of manual storage layout is to enable compatibility /// with both regular and upgradeable contracts. bytes32 internal constant _OWNER_SLOT = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff74873927; /// The ownership handover slot of `newOwner` is given by: /// ``` /// mstore(0x00, or(shl(96, user), _HANDOVER_SLOT_SEED)) /// let handoverSlot := keccak256(0x00, 0x20) /// ``` /// It stores the expiry timestamp of the two-step ownership handover. uint256 private constant _HANDOVER_SLOT_SEED = 0x389a75e1; /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* INTERNAL FUNCTIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Override to return true to make `_initializeOwner` prevent double-initialization. function _guardInitializeOwner() internal pure virtual returns (bool guard) {} /// @dev Initializes the owner directly without authorization guard. /// This function must be called upon initialization, /// regardless of whether the contract is upgradeable or not. /// This is to enable generalization to both regular and upgradeable contracts, /// and to save gas in case the initial owner is not the caller. /// For performance reasons, this function will not check if there /// is an existing owner. function _initializeOwner(address newOwner) internal virtual { if (_guardInitializeOwner()) { /// @solidity memory-safe-assembly assembly { let ownerSlot := _OWNER_SLOT if sload(ownerSlot) { mstore(0x00, 0x0dc149f0) // `AlreadyInitialized()`. revert(0x1c, 0x04) } // Clean the upper 96 bits. newOwner := shr(96, shl(96, newOwner)) // Store the new value. sstore(ownerSlot, or(newOwner, shl(255, iszero(newOwner)))) // Emit the {OwnershipTransferred} event. log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, 0, newOwner) } } else { /// @solidity memory-safe-assembly assembly { // Clean the upper 96 bits. newOwner := shr(96, shl(96, newOwner)) // Store the new value. sstore(_OWNER_SLOT, newOwner) // Emit the {OwnershipTransferred} event. log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, 0, newOwner) } } } /// @dev Sets the owner directly without authorization guard. function _setOwner(address newOwner) internal virtual { if (_guardInitializeOwner()) { /// @solidity memory-safe-assembly assembly { let ownerSlot := _OWNER_SLOT // Clean the upper 96 bits. newOwner := shr(96, shl(96, newOwner)) // Emit the {OwnershipTransferred} event. log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, sload(ownerSlot), newOwner) // Store the new value. sstore(ownerSlot, or(newOwner, shl(255, iszero(newOwner)))) } } else { /// @solidity memory-safe-assembly assembly { let ownerSlot := _OWNER_SLOT // Clean the upper 96 bits. newOwner := shr(96, shl(96, newOwner)) // Emit the {OwnershipTransferred} event. log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, sload(ownerSlot), newOwner) // Store the new value. sstore(ownerSlot, newOwner) } } } /// @dev Throws if the sender is not the owner. function _checkOwner() internal view virtual { /// @solidity memory-safe-assembly assembly { // If the caller is not the stored owner, revert. if iszero(eq(caller(), sload(_OWNER_SLOT))) { mstore(0x00, 0x82b42900) // `Unauthorized()`. revert(0x1c, 0x04) } } } /// @dev Returns how long a two-step ownership handover is valid for in seconds. /// Override to return a different value if needed. /// Made internal to conserve bytecode. Wrap it in a public function if needed. function _ownershipHandoverValidFor() internal view virtual returns (uint64) { return 48 * 3600; } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* PUBLIC UPDATE FUNCTIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Allows the owner to transfer the ownership to `newOwner`. function transferOwnership(address newOwner) public payable virtual onlyOwner { /// @solidity memory-safe-assembly assembly { if iszero(shl(96, newOwner)) { mstore(0x00, 0x7448fbae) // `NewOwnerIsZeroAddress()`. revert(0x1c, 0x04) } } _setOwner(newOwner); } /// @dev Allows the owner to renounce their ownership. function renounceOwnership() public payable virtual onlyOwner { _setOwner(address(0)); } /// @dev Request a two-step ownership handover to the caller. /// The request will automatically expire in 48 hours (172800 seconds) by default. function requestOwnershipHandover() public payable virtual { unchecked { uint256 expires = block.timestamp + _ownershipHandoverValidFor(); /// @solidity memory-safe-assembly assembly { // Compute and set the handover slot to `expires`. mstore(0x0c, _HANDOVER_SLOT_SEED) mstore(0x00, caller()) sstore(keccak256(0x0c, 0x20), expires) // Emit the {OwnershipHandoverRequested} event. log2(0, 0, _OWNERSHIP_HANDOVER_REQUESTED_EVENT_SIGNATURE, caller()) } } } /// @dev Cancels the two-step ownership handover to the caller, if any. function cancelOwnershipHandover() public payable virtual { /// @solidity memory-safe-assembly assembly { // Compute and set the handover slot to 0. mstore(0x0c, _HANDOVER_SLOT_SEED) mstore(0x00, caller()) sstore(keccak256(0x0c, 0x20), 0) // Emit the {OwnershipHandoverCanceled} event. log2(0, 0, _OWNERSHIP_HANDOVER_CANCELED_EVENT_SIGNATURE, caller()) } } /// @dev Allows the owner to complete the two-step ownership handover to `pendingOwner`. /// Reverts if there is no existing ownership handover requested by `pendingOwner`. function completeOwnershipHandover(address pendingOwner) public payable virtual onlyOwner { /// @solidity memory-safe-assembly assembly { // Compute and set the handover slot to 0. mstore(0x0c, _HANDOVER_SLOT_SEED) mstore(0x00, pendingOwner) let handoverSlot := keccak256(0x0c, 0x20) // If the handover does not exist, or has expired. if gt(timestamp(), sload(handoverSlot)) { mstore(0x00, 0x6f5e8818) // `NoHandoverRequest()`. revert(0x1c, 0x04) } // Set the handover slot to 0. sstore(handoverSlot, 0) } _setOwner(pendingOwner); } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* PUBLIC READ FUNCTIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Returns the owner of the contract. function owner() public view virtual returns (address result) { /// @solidity memory-safe-assembly assembly { result := sload(_OWNER_SLOT) } } /// @dev Returns the expiry timestamp for the two-step ownership handover to `pendingOwner`. function ownershipHandoverExpiresAt(address pendingOwner) public view virtual returns (uint256 result) { /// @solidity memory-safe-assembly assembly { // Compute the handover slot. mstore(0x0c, _HANDOVER_SLOT_SEED) mstore(0x00, pendingOwner) // Load the handover slot. result := sload(keccak256(0x0c, 0x20)) } } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* MODIFIERS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Marks a function as only callable by the owner. modifier onlyOwner() virtual { _checkOwner(); _; } }src/libraries/OracleLib.sol
// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import {IOracle} from "../interfaces/IOracle.sol"; import {Tick, TickLib} from "@mgv/lib/core/TickLib.sol"; import {FixedPointMathLib} from "lib/solady/src/utils/FixedPointMathLib.sol"; /** * @title OracleData * @notice Packed struct containing oracle configuration and validation parameters * @dev This struct is optimized for gas efficiency using bit packing to fit in exactly 256 bits (1 slot). * The struct supports both static price values and dynamic oracle feeds with timelock mechanisms. * @param isStatic Whether to use a static tick value or query an external oracle * @param oracle The external oracle contract to query (used when isStatic is false) * @param staticValue The static tick value to use (used when isStatic is true) * @param maxDeviation Maximum allowed deviation in ticks from the oracle price * @param proposedAt Timestamp when the oracle configuration was proposed (used for timelock) * @param timelockMinutes Duration in minutes that must pass before accepting oracle changes */ struct OracleData { bool isStatic; // 8 bits IOracle oracle; // 160 bits + 8 bits = 168 bits int24 staticValue; // 24 bits + 168 bits = 192 bits uint16 maxDeviation; // 16 bits + 192 bits = 208 bits uint40 proposedAt; // 40 bits + 208 bits = 248 bits uint8 timelockMinutes; // 8 bits + 248 bits = 256 bits } /** * @title OracleLib * @notice Library for managing oracle data and price validation * @dev Provides functionality to: * - Retrieve current tick values from static or dynamic sources * - Validate trading positions against oracle-defined price ranges * - Enforce timelock periods for oracle configuration changes * @author Mangrove */ library OracleLib { using OracleLib for OracleData; /*////////////////////////////////////////////////////////////// CORE FUNCTIONS //////////////////////////////////////////////////////////////*/ /** * @notice Retrieves the current tick value from the oracle configuration * @param self The oracle data configuration * @return Tick The current tick value * @dev If isStatic is true, returns the staticValue. Otherwise, queries the external oracle. * This function may revert if the external oracle is unavailable or returns invalid data. */ function tick(OracleData memory self) internal view returns (Tick) { if (self.isStatic) { return Tick.wrap(self.staticValue); } return self.oracle.tick(); } /** * @notice Validates whether a given tick is within acceptable absolute deviation from the oracle's current tick * @param self The oracle data struct containing deviation parameters and oracle configuration * @param _tick The tick value to validate against the oracle price * @return bool True if the absolute distance between oracle tick and given tick is within maxDeviation, false otherwise * @dev Uses absolute distance calculation via FixedPointMathLib.dist() to check deviation in both directions * @dev This function provides symmetric validation - accepts ticks both above and below the oracle tick * @dev Unlike accepts(), this function checks |oracleTick - tick| <= maxDeviation (bidirectional) * @dev May revert if the oracle call fails when querying external oracles */ function withinDeviation(OracleData memory self, Tick _tick) internal view returns (bool) { unchecked { int256 tick_ = Tick.unwrap(_tick); int256 oracleTick = Tick.unwrap(self.tick()); uint256 distance = FixedPointMathLib.dist(oracleTick, tick_); return distance <= uint256(self.maxDeviation); } } /** * @notice Validates whether a given tick is within acceptable deviation from the oracle's current tick * @param self The oracle data struct containing deviation parameters * @param _tick The tick to validate against the oracle (ask tick) * @return bool True if the tick is within acceptable deviation range, false otherwise * @dev Checks if the absolute difference between oracle tick and given tick is within maxDeviation * @dev Current implementation only checks one direction (oracleTick - tick_ <= maxDeviation) * @dev This means it accepts ticks that are at most maxDeviation below the oracle tick */ function accepts(OracleData memory self, Tick _tick) internal view returns (bool) { unchecked { int256 tick_ = Tick.unwrap(_tick); int256 oracleTick = Tick.unwrap(self.tick()); return oracleTick - tick_ <= int256(uint256(self.maxDeviation)); } } /** * @notice Validates whether ask and bid ticks are within acceptable deviation from oracle price * @param self The oracle data configuration * @param askTick The tick value for the ask side (higher price, lower tick) * @param bidTick The tick value for the bid side (lower price, higher tick) * @return bool True if both ask and bid ticks are within maxDeviation, false otherwise * @dev Validates that: * - Oracle tick - ask tick <= maxDeviation (ask not too far below oracle) * - Oracle tick - bid tick <= maxDeviation (bid not too far below oracle) * This ensures trading positions respect oracle-defined price bounds. */ function accepts(OracleData memory self, Tick askTick, Tick bidTick) internal view returns (bool) { // can be unchecked because oracle tick and ticks are 24 bits integers and we will do 256 bit math unchecked { int256 oracleTick = Tick.unwrap(self.tick()); int256 askTick_ = Tick.unwrap(askTick); int256 bidTick_ = Tick.unwrap(bidTick); // Check if both ask and bid are within acceptable deviation from oracle price return oracleTick - askTick_ <= int256(uint256(self.maxDeviation)) && -oracleTick - bidTick_ <= int256(uint256(self.maxDeviation)); } } /** * @notice Checks whether an oracle configuration change is still under timelock * @param self The oracle data configuration * @param start The timestamp when the oracle change was proposed * @return bool True if the change is still timelocked (cannot be accepted), false if timelock has expired * @dev Returns true if: * - Current timestamp is before the start timestamp (invalid state) * - Time elapsed since start is less than timelockMinutes * 60 seconds * This prevents immediate oracle changes and provides time for guardians to review proposals. */ function timelocked(OracleData memory self, uint256 start) internal view returns (bool) { unchecked { uint256 date = block.timestamp; // Handle edge case where current time is before start time if (date < start) return true; // Check if timelock period has elapsed return date - start < uint256(self.timelockMinutes) * 60; } } /** * @notice Validates whether the oracle configuration can provide a valid tick value * @param self The oracle data configuration * @return bool True if oracle can provide a valid tick within acceptable range, false otherwise * @dev For static oracles, validates the staticValue is within valid tick range. * For dynamic oracles, attempts to query the external oracle with try-catch: * - If oracle call succeeds, validates the returned tick is within valid range * - If oracle call fails (reverts), returns false * This function is useful for validating oracle configurations before deployment. */ function isValid(OracleData memory self) internal view returns (bool) { unchecked { if (self.isStatic) { // For static oracles, just check if the static value is in valid range return Tick.wrap(self.staticValue).inRange(); } // Check if oracle code size is greater than 0 if (address(self.oracle).code.length == 0) { return false; } // For dynamic oracles, try to query the oracle and validate the result try self.oracle.tick() returns (Tick oracleTick) { return oracleTick.inRange(); } catch { // Oracle call failed, return false return false; } } } /*////////////////////////////////////////////////////////////// PUBLIC FUNCTIONS //////////////////////////////////////////////////////////////*/ /** * @notice Validates if a trade is acceptable based on oracle price constraints * @param self The oracle data configuration * @param isSell True if selling base token for quote token, false if buying base with quote * @param received The amount of tokens received from the trade * @param sent The amount of tokens sent in the trade * @return True if the trade tick is within acceptable oracle range, false otherwise * @dev This function is public (not internal) because it uses TickLib which is a large library. * Making it internal to contracts would largely increase their bytecode size. * The function calculates the trade tick from volumes and validates it against oracle constraints. */ function acceptsTrade(OracleData memory self, bool isSell, uint256 received, uint256 sent) public view returns (bool) { int256 tradeTick = Tick.unwrap(TickLib.tickFromVolumes(received, sent)); int256 oracleTick = Tick.unwrap(self.tick()); if (!isSell) oracleTick = -oracleTick; return oracleTick - tradeTick <= int256(uint256(self.maxDeviation)); } /** * @notice Validates if an initial mint operation is acceptable based on oracle price constraints * @param self The oracle data configuration * @param baseAmount The amount of base tokens in the initial mint * @param quoteAmount The amount of quote tokens in the initial mint * @return True if the mint ratio is within acceptable oracle deviation, false otherwise * @dev This function is public (not internal) because it uses TickLib which is a large library. * Making it internal to contracts would largely increase their bytecode size. * The function calculates the tick from the base/quote ratio and validates it's within deviation limits. */ function acceptsInitialMint(OracleData memory self, uint256 baseAmount, uint256 quoteAmount) public view returns (bool) { Tick tradeTick = TickLib.tickFromVolumes(quoteAmount, baseAmount); return self.withinDeviation(tradeTick); } }lib/mangrove-core/lib/core/BitLib.sol
// SPDX-License-Identifier: MIT pragma solidity ^0.8.17; library BitLib { // - if x is a nonzero uint64: // return number of zeroes in x that do not have a 1 to their right // - otherwise: // return 64 function ctz64(uint x) internal pure returns (uint256 c) { unchecked { assembly ("memory-safe") { // clean x:= and(x,0xffffffffffffffff) // 7th bit c:= shl(6,iszero(x)) // isolate lsb x := and(x, add(not(x), 1)) // 6th bit c := or(c,shl(5, lt(0xffffffff, x))) // debruijn lookup c := or(c, byte(shr(251, mul(shr(c, x), shl(224, 0x077cb531))), 0x00011c021d0e18031e16140f191104081f1b0d17151310071a0c12060b050a09)) } } } // Function fls is MIT License. Copyright (c) 2022 Solady. /// @dev find last set. /// Returns the index of the most significant bit of `x`, /// counting from the least significant bit position. /// If `x` is zero, returns 256. /// Equivalent to `log2(x)`, but without reverting for the zero case. function fls(uint256 x) internal pure returns (uint256 r) { assembly ("memory-safe") { r := shl(8, iszero(x)) r := or(r, shl(7, lt(0xffffffffffffffffffffffffffffffff, x))) r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x)))) r := or(r, shl(5, lt(0xffffffff, shr(r, x)))) // For the remaining 32 bits, use a De Bruijn lookup. x := shr(r, x) x := or(x, shr(1, x)) x := or(x, shr(2, x)) x := or(x, shr(4, x)) x := or(x, shr(8, x)) x := or(x, shr(16, x)) // forgefmt: disable-next-item r := or(r, byte(shr(251, mul(x, shl(224, 0x07c4acdd))), 0x0009010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f)) } } }lib/mangrove-core/src/preprocessed/OfferDetail.post.sol
// SPDX-License-Identifier: MIT pragma solidity ^0.8.13; /* ************************************************** * GENERATED FILE. DO NOT EDIT. * ************************************************** */ /* since you can't convert bool to uint in an expression without conditionals, * we add a file-level function and rely on compiler optimization */ function uint_of_bool(bool b) pure returns (uint u) { assembly ("memory-safe") { u := b } } import "@mgv/lib/core/Constants.sol"; struct OfferDetailUnpacked { address maker; uint gasreq; uint kilo_offer_gasbase; uint gasprice; } //some type safety for each struct type OfferDetail is uint; using OfferDetailLib for OfferDetail global; ////////////// ADDITIONAL DEFINITIONS, IF ANY //////////////// import {OfferDetailExtra,OfferDetailUnpackedExtra} from "@mgv/lib/core/OfferDetailExtra.sol"; using OfferDetailExtra for OfferDetail global; using OfferDetailUnpackedExtra for OfferDetailUnpacked global; ////////////// END OF ADDITIONAL DEFINITIONS ///////////////// library OfferDetailLib { // number of bits in each field uint constant maker_bits = 160; uint constant gasreq_bits = 24; uint constant kilo_offer_gasbase_bits = 9; uint constant gasprice_bits = 26; // number of bits before each field uint constant maker_before = 0 + 0; uint constant gasreq_before = maker_before + maker_bits; uint constant kilo_offer_gasbase_before = gasreq_before + gasreq_bits; uint constant gasprice_before = kilo_offer_gasbase_before + kilo_offer_gasbase_bits; // focus-mask: 1s at field location, 0s elsewhere uint constant maker_mask_inv = (ONES << 256 - maker_bits) >> maker_before; uint constant gasreq_mask_inv = (ONES << 256 - gasreq_bits) >> gasreq_before; uint constant kilo_offer_gasbase_mask_inv = (ONES << 256 - kilo_offer_gasbase_bits) >> kilo_offer_gasbase_before; uint constant gasprice_mask_inv = (ONES << 256 - gasprice_bits) >> gasprice_before; // cleanup-mask: 0s at field location, 1s elsewhere uint constant maker_mask = ~maker_mask_inv; uint constant gasreq_mask = ~gasreq_mask_inv; uint constant kilo_offer_gasbase_mask = ~kilo_offer_gasbase_mask_inv; uint constant gasprice_mask = ~gasprice_mask_inv; // cast-mask: 0s followed by |field| trailing 1s uint constant maker_cast_mask = ~(ONES << maker_bits); uint constant gasreq_cast_mask = ~(ONES << gasreq_bits); uint constant kilo_offer_gasbase_cast_mask = ~(ONES << kilo_offer_gasbase_bits); uint constant gasprice_cast_mask = ~(ONES << gasprice_bits); // size-related error message string constant maker_size_error = "mgv/config/maker/160bits"; string constant gasreq_size_error = "mgv/config/gasreq/24bits"; string constant kilo_offer_gasbase_size_error = "mgv/config/kilo_offer_gasbase/9bits"; string constant gasprice_size_error = "mgv/config/gasprice/26bits"; // from packed to in-memory struct function to_struct(OfferDetail __packed) internal pure returns (OfferDetailUnpacked memory __s) { unchecked { __s.maker = address(uint160(uint(OfferDetail.unwrap(__packed) << maker_before) >> (256 - maker_bits))); __s.gasreq = uint(OfferDetail.unwrap(__packed) << gasreq_before) >> (256 - gasreq_bits); __s.kilo_offer_gasbase = uint(OfferDetail.unwrap(__packed) << kilo_offer_gasbase_before) >> (256 - kilo_offer_gasbase_bits); __s.gasprice = uint(OfferDetail.unwrap(__packed) << gasprice_before) >> (256 - gasprice_bits); }} // equality checking function eq(OfferDetail __packed1, OfferDetail __packed2) internal pure returns (bool) { unchecked { return OfferDetail.unwrap(__packed1) == OfferDetail.unwrap(__packed2); }} // from packed to a tuple function unpack(OfferDetail __packed) internal pure returns (address __maker, uint __gasreq, uint __kilo_offer_gasbase, uint __gasprice) { unchecked { __maker = address(uint160(uint(OfferDetail.unwrap(__packed) << maker_before) >> (256 - maker_bits))); __gasreq = uint(OfferDetail.unwrap(__packed) << gasreq_before) >> (256 - gasreq_bits); __kilo_offer_gasbase = uint(OfferDetail.unwrap(__packed) << kilo_offer_gasbase_before) >> (256 - kilo_offer_gasbase_bits); __gasprice = uint(OfferDetail.unwrap(__packed) << gasprice_before) >> (256 - gasprice_bits); }} // getters function maker(OfferDetail __packed) internal pure returns(address) { unchecked { return address(uint160(uint(OfferDetail.unwrap(__packed) << maker_before) >> (256 - maker_bits))); }} // setters function maker(OfferDetail __packed,address val) internal pure returns(OfferDetail) { unchecked { return OfferDetail.wrap((OfferDetail.unwrap(__packed) & maker_mask) | (uint(uint160(val)) << (256 - maker_bits)) >> maker_before); }} function gasreq(OfferDetail __packed) internal pure returns(uint) { unchecked { return uint(OfferDetail.unwrap(__packed) << gasreq_before) >> (256 - gasreq_bits); }} // setters function gasreq(OfferDetail __packed,uint val) internal pure returns(OfferDetail) { unchecked { return OfferDetail.wrap((OfferDetail.unwrap(__packed) & gasreq_mask) | (val << (256 - gasreq_bits)) >> gasreq_before); }} function kilo_offer_gasbase(OfferDetail __packed) internal pure returns(uint) { unchecked { return uint(OfferDetail.unwrap(__packed) << kilo_offer_gasbase_before) >> (256 - kilo_offer_gasbase_bits); }} // setters function kilo_offer_gasbase(OfferDetail __packed,uint val) internal pure returns(OfferDetail) { unchecked { return OfferDetail.wrap((OfferDetail.unwrap(__packed) & kilo_offer_gasbase_mask) | (val << (256 - kilo_offer_gasbase_bits)) >> kilo_offer_gasbase_before); }} function gasprice(OfferDetail __packed) internal pure returns(uint) { unchecked { return uint(OfferDetail.unwrap(__packed) << gasprice_before) >> (256 - gasprice_bits); }} // setters function gasprice(OfferDetail __packed,uint val) internal pure returns(OfferDetail) { unchecked { return OfferDetail.wrap((OfferDetail.unwrap(__packed) & gasprice_mask) | (val << (256 - gasprice_bits)) >> gasprice_before); }} // from in-memory struct to packed function t_of_struct(OfferDetailUnpacked memory __s) internal pure returns (OfferDetail) { unchecked { return pack(__s.maker, __s.gasreq, __s.kilo_offer_gasbase, __s.gasprice); }} // from arguments to packed function pack(address __maker, uint __gasreq, uint __kilo_offer_gasbase, uint __gasprice) internal pure returns (OfferDetail) { unchecked { uint __packed; __packed |= (uint(uint160(__maker)) << (256 - maker_bits)) >> maker_before; __packed |= (__gasreq << (256 - gasreq_bits)) >> gasreq_before; __packed |= (__kilo_offer_gasbase << (256 - kilo_offer_gasbase_bits)) >> kilo_offer_gasbase_before; __packed |= (__gasprice << (256 - gasprice_bits)) >> gasprice_before; return OfferDetail.wrap(__packed); }} // input checking function maker_check(address __maker) internal pure returns (bool) { unchecked { return (uint(uint160(__maker)) & maker_cast_mask) == uint(uint160(__maker)); }} function gasreq_check(uint __gasreq) internal pure returns (bool) { unchecked { return (__gasreq & gasreq_cast_mask) == __gasreq; }} function kilo_offer_gasbase_check(uint __kilo_offer_gasbase) internal pure returns (bool) { unchecked { return (__kilo_offer_gasbase & kilo_offer_gasbase_cast_mask) == __kilo_offer_gasbase; }} function gasprice_check(uint __gasprice) internal pure returns (bool) { unchecked { return (__gasprice & gasprice_cast_mask) == __gasprice; }} }lib/mangrove-strats/src/strategies/interfaces/IOfferLogic.sol
// SPDX-License-Identifier: MIT pragma solidity >=0.8.10; import {IMangrove} from "@mgv/src/IMangrove.sol"; import {IERC20, IMaker, OLKey} from "@mgv/src/core/MgvLib.sol"; import {AbstractRouter, RL} from "@mgv-strats/src/strategies/routers/abstract/AbstractRouter.sol"; import {Tick} from "@mgv/lib/core/TickLib.sol"; ///@title IOfferLogic interface for offer management ///@notice It is an IMaker for Mangrove. interface IOfferLogic is IMaker { ///@notice Log incident (during post trade execution) ///@param olKeyHash the hash of the offer list key. This is indexed so that RPC calls can filter on it. ///@param offerId the Mangrove offer id. This is indexed so that RPC calls can filter on it. ///@param makerData from the maker. ///@param mgvData from Mangrove. ///@notice By emitting this data, an indexer can keep track of what incidents has happened. event LogIncident(bytes32 indexed olKeyHash, uint indexed offerId, bytes32 makerData, bytes32 mgvData); ///@notice Approves a spender to transfer a certain amount of tokens on behalf of `this`. ///@param token the ERC20 token contract ///@param spender the approved spender ///@param amount the spending amount ///@return result of token approval. ///@dev admin may use this function to revoke specific approvals of `this` that are set after a call to `activate`. function approve(IERC20 token, address spender, uint amount) external returns (bool); ///@notice Performs approval that are necessary to trade a given asset ///@param token the traded asset ///@dev anyone can call since only max approval can be set function activate(IERC20 token) external; ///@notice computes the amount of native tokens that can be redeemed when deprovisioning a given offer. ///@param olKey the offer list key. ///@param offerId the identifier of the offer in the offer list ///@return provision the amount of native tokens that can be redeemed when deprovisioning the offer function provisionOf(OLKey memory olKey, uint offerId) external view returns (uint provision); ///@notice withdraws native tokens from `this` balance on Mangrove. ///@param amount the amount of WEI one wishes to withdraw. ///@param receiver the address of the receiver of the funds. ///@dev Since a call is made to the `receiver`, this function is subject to reentrancy. function withdrawFromMangrove(uint amount, address payable receiver) external; ///@notice Memory allocation for `_new/updateOffer`'s arguments. ///@param olKey the offer list key. ///@param tick the tick. ///@param gives the amount of outbound tokens the maker gives for a complete fill. ///@param gasreq the amount of gas units that are required to execute the trade ///@param gasprice the gasprice used to compute offer's provision (use 0 to use Mangrove's gasprice) ///@param fund WEIs in `this` contract's balance that are used to provision the offer. ///@param noRevert is set to true if calling function does not wish `_newOffer` to revert on error. struct OfferArgs { OLKey olKey; Tick tick; uint gives; uint gasreq; uint gasprice; uint fund; bool noRevert; } /// @notice Contract's Mangrove getter /// @return the Mangrove contract. function MGV() external view returns (IMangrove); ///@notice returns the contract that routes owner's funds ///@param owner the fund owner ///@return router the router contract. ///@dev returns `AbstractRouter(address(0))` if the strat is not using a router. function router(address owner) external view returns (AbstractRouter router); }lib/mangrove-core/lib/core/DensityLib.sol
// SPDX-License-Identifier: MIT pragma solidity ^0.8.17; import {Field} from "@mgv/lib/core/TickTreeLib.sol"; import {ONES} from "@mgv/lib/core/Constants.sol"; import {BitLib} from "@mgv/lib/core/BitLib.sol"; /* The density of a semibook is the number of outbound tokens per gas required. An offer must a respect a semibook's density. Density can be < 1. The density of a semibook is stored as a 9 bits float. For convenience, governance functions read density as a 96.32 fixed point number. The functions below give conversion utilities between the two formats As a guideline, fixed-point densities should be uints and should use hungarian notation (for instance `uint density96X32`). Floating-point densities should use the Density user-defined type. The float <-> fixed conversion is format agnostic but the expectation is that fixed points are 96x32, and floats are 2-bit mantissa, 7bit exponent with bias 32. The encoding is nonstandard so the code can be simpler. There are no subnormal floats in this encoding, `[exp][mantissa]` means: ``` if exp is 0 or 1: 0bmantissa * 2^-32 otherwise: 0b1.mantissa * 2^(exp-32) ``` so the small values have some holes: ``` coeff exponent available | coeff exponent available -------------------------------------------------------------- 0b0.00 | 0b1.10 -31 0b1.00 -32 | 0b1.11 -31 no 0b1.01 -32 no | 0b1.00 -30 0b1.10 -32 no | 0b1.01 -30 0b1.11 -32 no | 0b1.10 -30 0b1.00 -31 | 0b1.11 -30 0b1.01 -31 no | 0b1.00 -29 ``` */ type Density is uint; using DensityLib for Density global; library DensityLib { /* Numbers in this file assume that density is 9 bits in structs.ts */ uint constant BITS = 9; // must match structs.ts uint constant MANTISSA_BITS = 2; uint constant SUBNORMAL_LIMIT = ~(ONES << (MANTISSA_BITS+1)); uint constant MANTISSA_MASK = ~(ONES << MANTISSA_BITS); uint constant MASK = ~(ONES << BITS); uint constant MANTISSA_INTEGER = 1 << MANTISSA_BITS; uint constant EXPONENT_BITS = BITS - MANTISSA_BITS; function eq(Density a, Density b) internal pure returns (bool) { unchecked { return Density.unwrap(a) == Density.unwrap(b); }} /* Check the size of a fixed-point formatted density */ function checkDensity96X32(uint density96X32) internal pure returns (bool) { unchecked { return density96X32 < (1<<(96+32)); }} /* fixed-point -> float conversion */ /* Warning: no bit cleaning (expected to be done by Local's code), no checking that the input is on 128 bits. */ /* floats with `[exp=1]` are not in the image of fromFixed. They are considered noncanonical. */ function from96X32(uint density96X32) internal pure returns (Density) { unchecked { if (density96X32 <= MANTISSA_MASK) { return Density.wrap(density96X32); } // invariant: `exp >= 2` (so not 0) uint exp = BitLib.fls(density96X32); return make(density96X32 >> (exp-MANTISSA_BITS),exp); }} /* float -> fixed-point conversion */ function to96X32(Density density) internal pure returns (uint) { unchecked { /* also accepts floats not generated by fixedToFloat, i.e. with exp=1 */ if (Density.unwrap(density) <= SUBNORMAL_LIMIT) { return Density.unwrap(density) & MANTISSA_MASK; } /* assumes exp is on the right number of bits */ // invariant: `exp >= 2` uint shift = (Density.unwrap(density) >> MANTISSA_BITS) - MANTISSA_BITS; return ((Density.unwrap(density) & MANTISSA_MASK) | MANTISSA_INTEGER) << shift; }} function mantissa(Density density) internal pure returns (uint) { unchecked { return Density.unwrap(density) & MANTISSA_MASK; }} function exponent(Density density) internal pure returns (uint) { unchecked { return Density.unwrap(density) >> MANTISSA_BITS; }} /* Make a float from a mantissa and an exponent. May make a noncanonical float. */ /* Warning: no checks */ function make(uint _mantissa, uint _exponent) internal pure returns (Density) { unchecked { return Density.wrap((_exponent << MANTISSA_BITS) | (_mantissa & MANTISSA_MASK)); }} /* None of the functions below will overflow if m is 96bit wide. Density being a 96.32 number is useful because: - Most of its range is representable with the 9-bits float format chosen - It does not overflow when multiplied with a 96bit number, which is the size chosen to represent token amounts in Mangrove. - Densities below `2^-32` need `> 4e9` gasreq to force gives > 0, which is not realistic */ /* Multiply the density with m, rounded towards zero. */ /* May overflow if `|m|>9` */ function multiply(Density density, uint m) internal pure returns (uint) { unchecked { return (m * density.to96X32())>>32; }} /* Multiply the density with m, rounded towards +infinity. */ /* May overflow if `|m|>96` */ function multiplyUp(Density density, uint m) internal pure returns (uint) { unchecked { uint part = m * density.to96X32(); return (part >> 32) + (part%(2<<32) == 0 ? 0 : 1); }} /* Convenience function: get a fixed-point density from the given parameters. Computes the price of gas in outbound tokens (base units), then multiplies by cover_factor. */ /* Warning: you must multiply input usd prices by 100 */ /* not supposed to be gas optimized */ function paramsTo96X32( uint outbound_decimals, uint gasprice_in_Mwei, uint eth_in_centiusd, uint outbound_display_in_centiusd, uint cover_factor ) internal pure returns (uint) { // Do not use unchecked here require(uint8(outbound_decimals) == outbound_decimals,"DensityLib/fixedFromParams1/decimals/wrong"); uint num = cover_factor * gasprice_in_Mwei * (10**outbound_decimals) * eth_in_centiusd; // use * instead of << to trigger overflow check return (num * (1 << 32)) / (outbound_display_in_centiusd * 1e12); } /* Version with token in Mwei instead of usd */ function paramsTo96X32( uint outbound_decimals, uint gasprice_in_Mwei, uint outbound_display_in_Mwei, uint cover_factor ) internal pure returns (uint) { /* **Do not** use unchecked here. */ require(uint8(outbound_decimals) == outbound_decimals,"DensityLib/fixedFromParams2/decimals/wrong"); uint num = cover_factor * gasprice_in_Mwei * (10**outbound_decimals); /* use `*` instead of `<<` to trigger overflow check */ return (num * (1 << 32)) / outbound_display_in_Mwei; } }lib/mangrove-strats/src/strategies/offer_maker/market_making/kandel/abstract/DirectWithBidsAndAsksDistribution.sol
// SPDX-License-Identifier: MIT pragma solidity ^0.8.10; import {OLKey} from "@mgv/src/core/MgvLib.sol"; import {Direct} from "@mgv-strats/src/strategies/offer_maker/abstract/Direct.sol"; import {OfferType} from "./TradesBaseQuotePair.sol"; import {HasIndexedBidsAndAsks} from "./HasIndexedBidsAndAsks.sol"; import {IMangrove} from "@mgv/src/IMangrove.sol"; import {Local, Offer} from "@mgv/src/core/MgvLib.sol"; import {Tick} from "@mgv/lib/core/TickLib.sol"; ///@title `Direct` strat with an indexed collection of bids and asks which can be populated according to a desired base and quote distribution for gives and wants. abstract contract DirectWithBidsAndAsksDistribution is Direct, HasIndexedBidsAndAsks { ///@notice logs the start of a call to populate ///@notice By emitting this, an indexer will be able to know that the following events are in the context of populate. event PopulateStart(); ///@notice logs the end of a call to populate ///@notice By emitting this, an indexer will know that the previous PopulateStart event is over. event PopulateEnd(); ///@notice logs the start of a call to retractOffers ///@notice By emitting this, an indexer will be able to know that the following events are in the context of retract. event RetractStart(); ///@notice logs the end of a call to retractOffers ///@notice By emitting this, an indexer will know that the previous RetractStart event is over. event RetractEnd(); ///@notice Constructor ///@param mgv The Mangrove deployment. ///@param routerParams parameters if this stract is using a router constructor(IMangrove mgv, RouterParams memory routerParams) Direct(mgv, routerParams) {} ///@param index the index of the offer ///@param tick the tick for the index (the tick price of base per quote for bids and quote per base for asks) ///@param gives the gives for the index (the `quote` for bids and the `base` for asks) struct DistributionOffer { uint index; Tick tick; uint gives; } ///@param asks the asks to populate ///@param bids the bids to populate struct Distribution { DistributionOffer[] asks; DistributionOffer[] bids; } ///@notice Publishes bids/asks for the distribution in the `indices`. Care must be taken to publish offers in meaningful chunks. For instance, for Kandel an offer and its dual should be published in the same chunk (one being optionally initially dead). ///@param distribution the distribution of bids and asks to populate ///@param gasreq the amount of gas units that are required to execute the trade. ///@param gasprice the gasprice used to compute offer's provision. ///@dev Gives of 0 means create/update and then retract offer (but update price, gasreq, gasprice of the offer) function populateChunkInternal(Distribution memory distribution, uint gasreq, uint gasprice) internal { emit PopulateStart(); // Initialize static values of args OfferArgs memory args; // args.fund = 0; offers are already funded // args.noRevert = false; we want revert in case of failure args.gasreq = gasreq; args.gasprice = gasprice; // Populate bids args.olKey = offerListOfOfferType(OfferType.Bid); populateOfferListChunkInternal(distribution.bids, OfferType.Bid, args); // Populate asks args.olKey = args.olKey.flipped(); populateOfferListChunkInternal(distribution.asks, OfferType.Ask, args); emit PopulateEnd(); } ///@notice populates one of the offer lists with the given offers ///@param offers the offers to populate ///@param ba whether to populate bids or asks ///@param args a reused offer creation args structure with defaults passed from caller. function populateOfferListChunkInternal(DistributionOffer[] memory offers, OfferType ba, OfferArgs memory args) internal { Local local = MGV.local(args.olKey); // Minimum gives for offers (to post and retract) uint minGives = local.density().multiplyUp(args.gasreq + local.offer_gasbase()); for (uint i; i < offers.length; ++i) { DistributionOffer memory offer = offers[i]; uint index = offer.index; args.tick = offer.tick; args.gives = offer.gives; populateIndex(ba, offerIdOfIndex(ba, index), index, args, minGives); } } ///@notice publishes (by either creating or updating) a bid/ask at a given price index. ///@param ba whether the offer is a bid or an ask. ///@param offerId the Mangrove offer id (0 for a new offer). ///@param index the price index. ///@param args the argument of the offer. `args.gives=0` means offer will be created/updated and then retracted. ///@param minGives the minimum gives to satisfy density requirement - used for creating/updating offers when args.gives=0. function populateIndex(OfferType ba, uint offerId, uint index, OfferArgs memory args, uint minGives) internal { // if offer does not exist on mangrove yet if (offerId == 0) { // and offer should be live if (args.gives > 0) { // create it - we revert in case of failure (see populateChunk), so offerId is always > 0 (offerId,) = _newOffer(args); setIndexMapping(ba, index, offerId); } else { // else offerId && gives are 0 and the offer is posted and retracted to reserve the offerId and set the price // set args.gives to minGives to be above density requirement, we do it here since we use the args.gives=0 to signal a dead offer. args.gives = minGives; // create it - we revert in case of failure (see populateChunk), so offerId is always > 0 (offerId,) = _newOffer(args); // reset args.gives since args is reused args.gives = 0; // retract, keeping provision, thus the offer is reserved and ready for use in posthook. _retractOffer(args.olKey, offerId, false); setIndexMapping(ba, index, offerId); } } // else offer exists else { // but the offer should be dead since gives is 0 if (args.gives == 0) { // * `gives == 0` may happen from populate in case of re-population where some offers are then retracted by setting gives to 0. // set args.gives to minGives to be above density requirement, we do it here since we use the args.gives=0 to signal a dead offer. args.gives = minGives; // Update offer to set correct price, gasreq, gasprice, then retract _updateOffer(args, offerId); // reset args.gives since args is reused args.gives = 0; // retract, keeping provision, thus the offer is reserved and ready for use in posthook. _retractOffer(args.olKey, offerId, false); } else { // so the offer exists and it should, we simply update it with potentially new volume and price _updateOffer(args, offerId); } } } ///@notice retracts and deprovisions offers of the distribution interval `[from, to[`. ///@param from the start index. ///@param to the end index. ///@dev use in conjunction of `withdrawFromMangrove` if the user wishes to redeem the available WEIs. function retractOffers(uint from, uint to) public onlyAdmin { emit RetractStart(); retractOffersOnOfferList(from, to, OfferType.Ask); retractOffersOnOfferList(from, to, OfferType.Bid); emit RetractEnd(); } ///@notice retracts and deprovisions offers of the distribution interval `[from, to[` for the given offer type. ///@param from the start index. ///@param to the end index. ///@param ba the offer type. function retractOffersOnOfferList(uint from, uint to, OfferType ba) internal { OLKey memory olKey = offerListOfOfferType(ba); for (uint index = from; index < to; ++index) { // These offerIds could be recycled in a new populate uint offerId = offerIdOfIndex(ba, index); if (offerId != 0) { _retractOffer(olKey, offerId, true); } } } ///@notice gets the Mangrove offer at the given index for the offer type. ///@param ba the offer type. ///@param index the index. ///@return offer the Mangrove offer. function getOffer(OfferType ba, uint index) public view returns (Offer offer) { uint offerId = offerIdOfIndex(ba, index); OLKey memory olKey = offerListOfOfferType(ba); offer = MGV.offers(olKey, offerId); } /// @notice gets the total gives of all offers of the offer type. /// @param ba offer type. /// @return volume the total gives of all offers of the offer type. /// @dev function is very gas costly, for external calls only. function offeredVolume(OfferType ba) public view returns (uint volume) { for (uint index = 0; index < length; ++index) { Offer offer = getOffer(ba, index); volume += offer.gives(); } } }lib/mangrove-strats/src/strategies/utils/AccessControlled.sol
// SPDX-License-Identifier: MIT pragma solidity ^0.8.10; /// @title This contract is used to restrict access to privileged functions of inheriting contracts through modifiers. /// @notice The contract stores an admin address which is checked against `msg.sender` in the `onlyAdmin` modifier. /// @notice Additionally, a specific `msg.sender` can be verified with the `onlyCaller` modifier. contract AccessControlled { /** * @notice logs new `admin` of `this` * @param admin The new admin. * @notice By emitting this data, an indexer will be able to keep track of what address is the admin of this contract. */ event SetAdmin(address admin); /** * @notice The admin address. */ address internal _admin; /** * @notice `AccessControlled`'s constructor * @param admin_ The address of the admin that can access privileged functions and also allowed to change the admin. Cannot be `address(0)`. */ constructor(address admin_) { _setAdmin(admin_); } /** * @notice This modifier verifies that `msg.sender` is the admin. */ modifier onlyAdmin() { require(msg.sender == _admin, "AccessControlled/Invalid"); _; } /** * @notice This modifier verifies that `msg.sender` is the caller. * @param caller The address of the caller that can access the modified function. */ modifier onlyCaller(address caller) { require(msg.sender == caller, "AccessControlled/Invalid"); _; } /** * @notice This modifier verifies that `msg.sender` is either caller or the admin * @param caller The address of a caller that can access the modified function. */ modifier adminOrCaller(address caller) { // test _admin second to save a storage read when possible require(msg.sender == caller || msg.sender == _admin, "AccessControlled/Invalid"); _; } /** * @notice Retrieves the current admin. * @return current admin. */ function admin() public view returns (address current) { return _admin; } /** * @notice This sets and log a new admin * @param admin_ The new admin. Cannot be `address(0)`. */ function _setAdmin(address admin_) internal { require(admin_ != address(0), "AccessControlled/0xAdmin"); _admin = admin_; emit SetAdmin(admin_); } /** * @notice This sets the admin. Only the current admin can change the admin. * @param admin_ The new admin. Cannot be `address(0)`. */ function setAdmin(address admin_) external onlyAdmin { _setAdmin(admin_); } }lib/mangrove-core/lib/core/TickTreeLib.sol
// SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.17; import "@mgv/lib/core/Constants.sol"; import {Tick} from "@mgv/lib/core/TickLib.sol"; import {BitLib} from "@mgv/lib/core/BitLib.sol"; import {console2 as csf} from "@mgv/forge-std/console2.sol"; import {Local} from "@mgv/src/preprocessed/Local.post.sol"; /* # Libraries for tick tree manipulation Offers in Mangrove are structured in a tree so that offer insertion, removal, and update can happen in constant time. The tree has the following structure: nodes at height 0, 1, 2 and 3 are bit fields (type `Field`) and nodes at height 4 (leaves) are arrays of pairs of offers (type `Leaf`). - The root node is a 2-bit `Field` and has 2 child nodes. - The nodes below are called `level1` nodes and are 64-bit `Field`s, with 64 child nodes each. - The nodes below are called `level2` nodes and are 64-bit `Field`s with 64 child nodes each. - The nodes below are called `level3` nodes and are 64-bit `Field`s with 64 child nodes each. - The nodes below are called `leaf` nodes and are arrays of pairs of offers. Each pair of offers represents the first and last offer of a bin. - Bins are linked lists of offers. For each field, the i-th bit (starting from least significant) is set if there is at least one bin holding offers below the i-th child of the field. */ /* Globally enable `leaf.method(...)` */ type Leaf is uint; using LeafLib for Leaf global; /* Each `Leaf` holds information about 4 bins as follows: `[firstId,lastId] [firstId,lastId] [firstId,lastId] [firstId,lastId]`. For each bin `firstId` is used by `marketOrder` to start consuming offers in the bin (each offer contains a pointer to the next offer in the bin, until `lastId` is reacher). `lastId` is used when inserting offers in the bin: the newly inserted offer replaces the last offer. Each `leaf` has an `index`: it is the number of leaves before it. */ library LeafLib { Leaf constant EMPTY = Leaf.wrap(uint(0)); /* Checks if a leaf is dirty or not (see below for more on dirty leaves). */ function dirty(Leaf leaf) internal pure returns (DirtyLeaf) { unchecked { assembly ("memory-safe") { leaf := or(iszero(leaf),leaf) } return DirtyLeaf.wrap(Leaf.unwrap(leaf)); } } function eq(Leaf leaf1, Leaf leaf2) internal pure returns (bool) { unchecked { return Leaf.unwrap(leaf1) == Leaf.unwrap(leaf2); } } function isEmpty(Leaf leaf) internal pure returns (bool) { unchecked { return Leaf.unwrap(leaf) == Leaf.unwrap(EMPTY); } } /* `bool -> int` cast */ function uint_of_bool(bool b) internal pure returns (uint u) { unchecked { assembly ("memory-safe") { u := b } } } /* Set either tick's first (at pos*size) or last (at pos*size + 1) */ function setPosFirstOrLast(Leaf leaf, uint pos, uint id, bool last) internal pure returns (Leaf) { unchecked { uint before = OFFER_BITS * ((pos * 2) + uint_of_bool(last)); uint cleanupMask = ~(OFFER_MASK << (256 - OFFER_BITS - before)); uint shiftedId = id << (256 - OFFER_BITS - before); uint newLeaf = Leaf.unwrap(leaf) & cleanupMask | shiftedId; return Leaf.wrap(newLeaf); } } /* Assume `leaf` is the leaf of `bin`. Set the first offer of `bin` in leaf to `id`. */ function setBinFirst(Leaf leaf, Bin bin, uint id) internal pure returns (Leaf) { unchecked { uint posInLeaf = bin.posInLeaf(); return setPosFirstOrLast(leaf, posInLeaf, id, false); } } /* Assume `leaf` is the leaf of `bin`. Set the last offer of `bin` in leaf to `id`. */ function setBinLast(Leaf leaf, Bin bin, uint id) internal pure returns (Leaf) { unchecked { uint posInLeaf = bin.posInLeaf(); return setPosFirstOrLast(leaf, posInLeaf, id, true); } } // _This function is not used by Mangrove but is useful for MgvReader._ /* Assumes `leaf` is the leaf of `bin`. Erase contents of bins in `leaf` to (not including) `bin`. */ function eraseBelow(Leaf leaf, Bin bin) internal pure returns (Leaf) { unchecked { uint mask = ONES >> ((bin.posInLeaf() + 1) * OFFER_BITS * 2); return Leaf.wrap(Leaf.unwrap(leaf) & mask); } } // _This function is not used by Mangrove but is useful for MgvReader._ /* Assumes `leaf` is the leaf of `bin`. Erase contents of bins in `leaf` from (not including) `bin`. */ function eraseAbove(Leaf leaf, Bin bin) internal pure returns (Leaf) { unchecked { uint mask = ~(ONES >> (bin.posInLeaf() * OFFER_BITS * 2)); return Leaf.wrap(Leaf.unwrap(leaf) & mask); } } // _This function is not used by Mangrove but is part of the library._ /* Return the id of the first offer in `bin`, assuming `bin`'s leaf is `leaf`. */ function firstOfBin(Leaf leaf, Bin bin) internal pure returns (uint) { unchecked { return firstOfPos(leaf, bin.posInLeaf()); } } /* Return the id of the last offer in `bin`, assuming `bin`'s leaf is `leaf`. */ function lastOfBin(Leaf leaf, Bin bin) internal pure returns (uint) { unchecked { return lastOfPos(leaf, bin.posInLeaf()); } } /* Return first offer of pair in position `pos` */ function firstOfPos(Leaf leaf, uint pos) internal pure returns (uint) { unchecked { uint raw = Leaf.unwrap(leaf); return uint(raw << (pos * OFFER_BITS * 2) >> (256 - OFFER_BITS)); } } /* Return second offer of pair in position `pos` */ function lastOfPos(Leaf leaf, uint pos) internal pure returns (uint) { unchecked { uint raw = Leaf.unwrap(leaf); return uint(raw << (OFFER_BITS * ((pos * 2) + 1)) >> (256 - OFFER_BITS)); } } /* Return the position of the first pair of `leaf` (0, 1, 2 or 3) that has a nonzero offer. Explanation: Note that unlike in fields, have their low bin on the most significant bits. `pos` is initially 1 if `leaf` has some nonzero bit in its MSB half, 0 otherwise. Then `pos` is `A | B`, where `A` is `iszero(pos)<<1`, so `A` is 0 if leaf has some nonzero bit in its MSB half, 2 otherwise. And `B` is 0 if `leaf >> (pos << 7)` has some nonzero bit in its most significant 192 bits, 1 otherwise. */ function bestNonEmptyBinPos(Leaf leaf) internal pure returns (uint pos) { assembly("memory-safe") { pos := gt(leaf,0xffffffffffffffffffffffffffffffff) pos := or(shl(1,iszero(pos)),iszero(gt(shr(shl(7,pos),leaf),0xffffffffffffffff))) } } /* Return the offer id of the first offer of the first non-empty pair in `leaf`. */ function bestOfferId(Leaf leaf) internal pure returns (uint offerId) { unchecked { return firstOfPos(leaf,bestNonEmptyBinPos(leaf)); } } } /* Bins are numbered from MIN_BIN to MAX_BIN (inclusive). Each bin contains the offers at a given price. For a given `tickSpacing`, bins represent the following prices (centered on the central bin): ``` ... 1.0001^-(tickSpacing*2) 1.0001^-(tickSpacing*1) 1.0001 1.0001^(tickSpacing*1) 1.0001^(tickSpacing*2) ... ``` There are 4 bins per leaf, `4 * 64` bins per level3, etc. The leaf of a bin is the leaf that holds its first/last offer id. The level3 of a bin is the level3 field above its leaf; the level2 of a bin is the level2 field above its level3, etc. */ /* Globally enable `bin.method(...)` */ type Bin is int; using TickTreeLib for Bin global; library TickTreeLib { function eq(Bin bin1, Bin bin2) internal pure returns (bool) { unchecked { return Bin.unwrap(bin1) == Bin.unwrap(bin2); } } function inRange(Bin bin) internal pure returns (bool) { unchecked { return Bin.unwrap(bin) >= MIN_BIN && Bin.unwrap(bin) <= MAX_BIN; } } // _This function is not used by Mangrove but is part of the library for convenience._ /* Not optimized for gas. Returns the bin induced by the branch formed by the arguments. Returned value will make no sense if any of the `Field` arguments are empty. */ function bestBinFromBranch(uint binPosInLeaf,Field level3, Field level2, Field level1, Field root) internal pure returns (Bin) { unchecked { Local local; local = local.binPosInLeaf(binPosInLeaf).level3(level3).level2(level2).level1(level1).root(root); return bestBinFromLocal(local); } } /* Returns tick held by the `bin`, given a `tickSpacing`. */ function tick(Bin bin, uint tickSpacing) internal pure returns (Tick) { return Tick.wrap(Bin.unwrap(bin) * int(tickSpacing)); } /* Returns the bin induced by the branch held in `local`. Returned value will make no sense if any of the fields of `local` are empty. */ function bestBinFromLocal(Local local) internal pure returns (Bin) { unchecked { uint ubin = local.binPosInLeaf() | ((BitLib.ctz64(Field.unwrap(local.level3())) | (BitLib.ctz64(Field.unwrap(local.level2())) | (BitLib.ctz64(Field.unwrap(local.level1())) | uint( (int(BitLib.ctz64(Field.unwrap(local.root())))-ROOT_SIZE/2) << LEVEL_SIZE_BITS)) << LEVEL_SIZE_BITS) << LEVEL_SIZE_BITS) << LEAF_SIZE_BITS); return Bin.wrap(int(ubin)); } } /* Returns the index of the leaf that holds `bin` */ function leafIndex(Bin bin) internal pure returns (int) { unchecked { return Bin.unwrap(bin) >> LEAF_SIZE_BITS; } } /* Returns the position of `bin` in its leaf. */ function posInLeaf(Bin bin) internal pure returns (uint) { unchecked { return uint(Bin.unwrap(bin)) & LEAF_SIZE_MASK; } } /* Returns the index of `bin`'s level3 */ function level3Index(Bin bin) internal pure returns (int) { unchecked { return Bin.unwrap(bin) >> (LEAF_SIZE_BITS + LEVEL_SIZE_BITS); } } /* Returns the position of `bin`'s leaf in `bin`'s level3 */ function posInLevel3(Bin bin) internal pure returns (uint) { unchecked { return uint(bin.leafIndex()) & LEVEL_SIZE_MASK; } } /* Returns the index of `bin`'s level2 */ function level2Index(Bin bin) internal pure returns (int) { unchecked { return Bin.unwrap(bin) >> (LEAF_SIZE_BITS + 2* LEVEL_SIZE_BITS); } } /* Returns the position of `bin`'s level3 in `bin`'s level2 */ function posInLevel2(Bin bin) internal pure returns (uint) { unchecked { return uint(bin.level3Index()) & LEVEL_SIZE_MASK; } } /* Returns the index of `bin`'s level1 */ function level1Index(Bin bin) internal pure returns (int) { unchecked { return Bin.unwrap(bin) >> (LEAF_SIZE_BITS + 3 * LEVEL_SIZE_BITS); } } /* Returns the position of `bin`'s level2 in `bin`'s level1 */ function posInLevel1(Bin bin) internal pure returns (uint) { unchecked { return uint(bin.level2Index()) & LEVEL_SIZE_MASK; } } /* Returns the position of `bin`'s level1 in root */ function posInRoot(Bin bin) internal pure returns (uint) { unchecked { return uint(bin.level1Index() + ROOT_SIZE / 2); } } /* `<`, typed for bin */ function strictlyBetter(Bin bin1, Bin bin2) internal pure returns (bool) { unchecked { return Bin.unwrap(bin1) < Bin.unwrap(bin2); } } /* `<=`, typed for bin */ function better(Bin bin1, Bin bin2) internal pure returns (bool) { unchecked { return Bin.unwrap(bin1) <= Bin.unwrap(bin2); } } } /* Globally enable `field.method(...)` */ type Field is uint; using FieldLib for Field global; /* Fields are bit fields. Each bit position of a field corresponds to a child of the node in the tick tree. The i-th bit of a field is set iff its child is the parent of at least one non-emtpy field. Using bit fields market orders can locate the next bin containing an offer in constant time: once a leaf has been emptied, one must at most walk up along the branch of the current bin, up to the first 1 in a field, then go down to the of the tree, then go down the corresponding child to the nonempty bin found. */ /* In fields, positions are counted from the least significant bits */ library FieldLib { Field constant EMPTY = Field.wrap(uint(0)); /* Checks if a field is dirty or not (see below for more on dirty fields). */ function dirty(Field field) internal pure returns (DirtyField) { unchecked { assembly ("memory-safe") { field := or(TOPBIT,field) } return DirtyField.wrap(Field.unwrap(field)); } } function eq(Field field1, Field field2) internal pure returns (bool) { unchecked { return Field.unwrap(field1) == Field.unwrap(field2); } } function isEmpty(Field field) internal pure returns (bool) { unchecked { return Field.unwrap(field) == Field.unwrap(EMPTY); } } /* Flip the bit at the position of `bin`'s leaf */ function flipBitAtLevel3(Field level3, Bin bin) internal pure returns (Field) { unchecked { uint pos = bin.posInLevel3(); level3 = Field.wrap(Field.unwrap(level3) ^ (1 << pos)); return level3; } } /* Flip the bit at the position of `bin`'s level3 */ function flipBitAtLevel2(Field level2, Bin bin) internal pure returns (Field) { unchecked { uint pos = bin.posInLevel2(); level2 = Field.wrap(Field.unwrap(level2) ^ (1 << pos)); return level2; } } /* Flip the bit at the position of `bin`'s level2 */ function flipBitAtLevel1(Field level1, Bin bin) internal pure returns (Field) { unchecked { uint pos = bin.posInLevel1(); level1 = Field.wrap(Field.unwrap(level1) ^ (1 << pos)); return level1; } } /* Flip the bit at the position of `bin`'s level1 */ function flipBitAtRoot(Field root, Bin bin) internal pure returns (Field) { unchecked { uint pos = bin.posInRoot(); root = Field.wrap(Field.unwrap(root) ^ (1 << pos)); return root; } } // _This function is not used by Mangrove but is useful for MgvReader._ /* Assumes `field` is the level3 of `bin`. Erase contents of field up to (not including) bin. */ function eraseBelowInLevel3(Field field, Bin bin) internal pure returns (Field) { unchecked { uint mask = ONES << (bin.posInLevel3() + 1); return Field.wrap(Field.unwrap(field) & mask); } } // _This function is not used by Mangrove but is useful for MgvReader._ /* Assumes `field` is the level3 of `bin`. Erase contents of field from (not including) bin. */ function eraseAboveInLevel3(Field field, Bin bin) internal pure returns (Field) { unchecked { uint mask = ~(ONES << bin.posInLevel3()); return Field.wrap(Field.unwrap(field) & mask); } } // _This function is not used by Mangrove but is useful for MgvReader._ /* Assumes `field` is the level2 of `bin`. Erase contents of field up to (not including) bin. */ function eraseBelowInLevel2(Field field, Bin bin) internal pure returns (Field) { unchecked { uint mask = ONES << (bin.posInLevel2() + 1); return Field.wrap(Field.unwrap(field) & mask); } } // _This function is not used by Mangrove but is useful for MgvReader._ /* Assumes `field` is the level2 of `bin`. Erase contents of field from (not including) bin. */ function eraseAboveInLevel2(Field field, Bin bin) internal pure returns (Field) { unchecked { uint mask = ~(ONES << bin.posInLevel2()); return Field.wrap(Field.unwrap(field) & mask); } } // _This function is not used by Mangrove but is useful for MgvReader._ /* Assumes `field` is the level1 of `bin`. Erase contents of field up to (not including) bin. */ function eraseBelowInLevel1(Field field, Bin bin) internal pure returns (Field) { unchecked { uint mask = ONES << (bin.posInLevel1() + 1); return Field.wrap(Field.unwrap(field) & mask); } } // _This function is not used by Mangrove but is useful for MgvReader._ /* Assumes `field` is the level1 of `bin`. Erase contents of field from (not including) bin. */ function eraseAboveInLevel1(Field field, Bin bin) internal pure returns (Field) { unchecked { uint mask = ~(ONES << bin.posInLevel1()); return Field.wrap(Field.unwrap(field) & mask); } } // _This function is not used by Mangrove but is useful for MgvReader._ /* Assumes `field` is the root of `bin`. Erase contents of field up to (not including) bin. */ function eraseBelowInRoot(Field field, Bin bin) internal pure returns (Field) { unchecked { uint mask = ONES << (bin.posInRoot() + 1); return Field.wrap(Field.unwrap(field) & mask); } } // _This function is not used by Mangrove but is useful for MgvReader._ /* Assumes `field` is the root of `bin`. Erase contents of field from (not including) bin. */ function eraseAboveInRoot(Field field, Bin bin) internal pure returns (Field) { unchecked { uint mask = ~(ONES << bin.posInRoot()); return Field.wrap(Field.unwrap(field) & mask); } } /* Returns first nonzero position of `field`. Will return 64 if field is empty */ function firstOnePosition(Field field) internal pure returns (uint) { unchecked { return BitLib.ctz64(Field.unwrap(field)); } } // _This function is not used by Mangrove but is useful for MgvReader._ /* Returns last nonzero position of `field`. Will return 256 if field is empty */ function lastOnePosition(Field field) internal pure returns (uint) { unchecked { return BitLib.fls(Field.unwrap(field)); } } /* Return index of the first nonempty level1 below `root` (`root` should not be empty) */ function firstLevel1Index(Field root) internal pure returns (int) { unchecked { return int(root.firstOnePosition()) - ROOT_SIZE / 2; } } // _This function is not used by Mangrove but is useful for MgvReader._ /* Return index of the last nonempty level1 below `root` (root should not be empty) */ function lastLevel1Index(Field root) internal pure returns (int) { unchecked { return int(root.lastOnePosition()) - ROOT_SIZE / 2; } } /* Return index of the first nonempty level2 below `level1` assuming its index is `level1Index` (`level1` should not be empty). */ function firstLevel2Index(Field level1, int level1Index) internal pure returns (int) { unchecked { return level1Index * LEVEL_SIZE + int(level1.firstOnePosition()); } } // _This function is not used by Mangrove but is useful for MgvReader._ /* Return index of the last nonempty level2 below `level1` assuming its index is `level1Index` (`level1` should not be empty). */ function lastLevel2Index(Field level1, int level1Index) internal pure returns (int) { unchecked { return level1Index * LEVEL_SIZE + int(level1.lastOnePosition()); } } /* Return index of the first nonempty level3 below `level2` assuming its index is `level2Index` (`level2` should not be empty). */ function firstLevel3Index(Field level2, int level2Index) internal pure returns (int) { unchecked { return level2Index * LEVEL_SIZE + int(level2.firstOnePosition()); } } // _This function is not used by Mangrove but is useful for MgvReader._ /* Return index of the last nonempty level3 below `level2` assuming its index is `level2Index` (`level2` should not be empty). */ function lastLevel3Index(Field level2, int level2Index) internal pure returns (int) { unchecked { return level2Index * LEVEL_SIZE + int(level2.lastOnePosition()); } } /* Return index of the first nonempty leaf below `level3` assuming its index is `level3Index` (`level3` should not be empty). */ function firstLeafIndex(Field level3, int level3Index) internal pure returns (int) { unchecked { return level3Index * LEVEL_SIZE + int(level3.firstOnePosition()); } } // _This function is not used by Mangrove but is useful for MgvReader._ /* Return index of the last nonempty leaf below `level3` assuming its index is `level3Index` (`level3` should not be empty). */ function lastLeafIndex(Field level3, int level3Index) internal pure returns (int) { unchecked { return level3Index * LEVEL_SIZE + int(level3.lastOnePosition()); } } } /* ## Clean/Dirty Fields and Leaves To save gas, leaves and fields at never zeroed out but written with a dirty bit. This is especially helpful when the price oscillates quickly between two nodes. Leaves don't have 'available bits' so an empty leaf is encoded as 1. This is not a valid leaf because for every offer pair in a leaf, either both are 0 (the bin is empty) or both are nonzero (they are the same if the bin has a single offer). */ /* Globally enable `dirtyLeaf.method(...)` */ type DirtyLeaf is uint; using DirtyLeafLib for DirtyLeaf global; library DirtyLeafLib { /* Return 0 if leaf is 1, leaf otherwise */ function clean(DirtyLeaf leaf) internal pure returns (Leaf) { unchecked { assembly ("memory-safe") { leaf := xor(eq(leaf,ONE),leaf) } return Leaf.wrap(DirtyLeaf.unwrap(leaf)); } } function isDirty(DirtyLeaf leaf) internal pure returns (bool) { unchecked { return DirtyLeaf.unwrap(leaf) == ONE; } } function eq(DirtyLeaf leaf1, DirtyLeaf leaf2) internal pure returns (bool) { unchecked { return DirtyLeaf.unwrap(leaf1) == DirtyLeaf.unwrap(leaf2); } } } /* We use `TOPBIT` as the optimized in-storage value since 1 is a valid Field value */ /* For fields, it's simpler, since they do not use 64 bits we store the word with top bit at 1 and everything else at 0 to mark emptiness. */ /* Globally enable `dirtyField.method(...)` */ type DirtyField is uint; using DirtyFieldLib for DirtyField global; library DirtyFieldLib { DirtyField constant DIRTY_EMPTY = DirtyField.wrap(TOPBIT); DirtyField constant CLEAN_EMPTY = DirtyField.wrap(0); /* Return clean field with topbit set to 0 */ function clean(DirtyField field) internal pure returns (Field) { unchecked { assembly ("memory-safe") { field := and(NOT_TOPBIT,field) } return Field.wrap(DirtyField.unwrap(field)); } } function isDirty(DirtyField field) internal pure returns (bool) { unchecked { return DirtyField.unwrap(field) & TOPBIT == TOPBIT; } } function eq(DirtyField leaf1, DirtyField leaf2) internal pure returns (bool) { unchecked { return DirtyField.unwrap(leaf1) == DirtyField.unwrap(leaf2); } } }lib/mangrove-core/src/preprocessed/Global.post.sol
// SPDX-License-Identifier: MIT pragma solidity ^0.8.13; /* ************************************************** * GENERATED FILE. DO NOT EDIT. * ************************************************** */ /* since you can't convert bool to uint in an expression without conditionals, * we add a file-level function and rely on compiler optimization */ function uint_of_bool(bool b) pure returns (uint u) { assembly ("memory-safe") { u := b } } import "@mgv/lib/core/Constants.sol"; struct GlobalUnpacked { address monitor; bool useOracle; bool notify; uint gasprice; uint gasmax; bool dead; uint maxRecursionDepth; uint maxGasreqForFailingOffers; } //some type safety for each struct type Global is uint; using GlobalLib for Global global; ////////////// ADDITIONAL DEFINITIONS, IF ANY //////////////// ////////////// END OF ADDITIONAL DEFINITIONS ///////////////// library GlobalLib { // number of bits in each field uint constant monitor_bits = 160; uint constant useOracle_bits = 1; uint constant notify_bits = 1; uint constant gasprice_bits = 26; uint constant gasmax_bits = 24; uint constant dead_bits = 1; uint constant maxRecursionDepth_bits = 8; uint constant maxGasreqForFailingOffers_bits = 32; // number of bits before each field uint constant monitor_before = 0 + 0; uint constant useOracle_before = monitor_before + monitor_bits; uint constant notify_before = useOracle_before + useOracle_bits; uint constant gasprice_before = notify_before + notify_bits; uint constant gasmax_before = gasprice_before + gasprice_bits; uint constant dead_before = gasmax_before + gasmax_bits; uint constant maxRecursionDepth_before = dead_before + dead_bits; uint constant maxGasreqForFailingOffers_before = maxRecursionDepth_before + maxRecursionDepth_bits; // focus-mask: 1s at field location, 0s elsewhere uint constant monitor_mask_inv = (ONES << 256 - monitor_bits) >> monitor_before; uint constant useOracle_mask_inv = (ONES << 256 - useOracle_bits) >> useOracle_before; uint constant notify_mask_inv = (ONES << 256 - notify_bits) >> notify_before; uint constant gasprice_mask_inv = (ONES << 256 - gasprice_bits) >> gasprice_before; uint constant gasmax_mask_inv = (ONES << 256 - gasmax_bits) >> gasmax_before; uint constant dead_mask_inv = (ONES << 256 - dead_bits) >> dead_before; uint constant maxRecursionDepth_mask_inv = (ONES << 256 - maxRecursionDepth_bits) >> maxRecursionDepth_before; uint constant maxGasreqForFailingOffers_mask_inv = (ONES << 256 - maxGasreqForFailingOffers_bits) >> maxGasreqForFailingOffers_before; // cleanup-mask: 0s at field location, 1s elsewhere uint constant monitor_mask = ~monitor_mask_inv; uint constant useOracle_mask = ~useOracle_mask_inv; uint constant notify_mask = ~notify_mask_inv; uint constant gasprice_mask = ~gasprice_mask_inv; uint constant gasmax_mask = ~gasmax_mask_inv; uint constant dead_mask = ~dead_mask_inv; uint constant maxRecursionDepth_mask = ~maxRecursionDepth_mask_inv; uint constant maxGasreqForFailingOffers_mask = ~maxGasreqForFailingOffers_mask_inv; // cast-mask: 0s followed by |field| trailing 1s uint constant monitor_cast_mask = ~(ONES << monitor_bits); uint constant useOracle_cast_mask = ~(ONES << useOracle_bits); uint constant notify_cast_mask = ~(ONES << notify_bits); uint constant gasprice_cast_mask = ~(ONES << gasprice_bits); uint constant gasmax_cast_mask = ~(ONES << gasmax_bits); uint constant dead_cast_mask = ~(ONES << dead_bits); uint constant maxRecursionDepth_cast_mask = ~(ONES << maxRecursionDepth_bits); uint constant maxGasreqForFailingOffers_cast_mask = ~(ONES << maxGasreqForFailingOffers_bits); // size-related error message string constant monitor_size_error = "mgv/config/monitor/160bits"; string constant useOracle_size_error = "mgv/config/useOracle/1bits"; string constant notify_size_error = "mgv/config/notify/1bits"; string constant gasprice_size_error = "mgv/config/gasprice/26bits"; string constant gasmax_size_error = "mgv/config/gasmax/24bits"; string constant dead_size_error = "mgv/config/dead/1bits"; string constant maxRecursionDepth_size_error = "mgv/config/maxRecursionDepth/8bits"; string constant maxGasreqForFailingOffers_size_error = "mgv/config/maxGasreqForFailingOffers/32bits"; // from packed to in-memory struct function to_struct(Global __packed) internal pure returns (GlobalUnpacked memory __s) { unchecked { __s.monitor = address(uint160(uint(Global.unwrap(__packed) << monitor_before) >> (256 - monitor_bits))); __s.useOracle = ((Global.unwrap(__packed) & useOracle_mask_inv) > 0); __s.notify = ((Global.unwrap(__packed) & notify_mask_inv) > 0); __s.gasprice = uint(Global.unwrap(__packed) << gasprice_before) >> (256 - gasprice_bits); __s.gasmax = uint(Global.unwrap(__packed) << gasmax_before) >> (256 - gasmax_bits); __s.dead = ((Global.unwrap(__packed) & dead_mask_inv) > 0); __s.maxRecursionDepth = uint(Global.unwrap(__packed) << maxRecursionDepth_before) >> (256 - maxRecursionDepth_bits); __s.maxGasreqForFailingOffers = uint(Global.unwrap(__packed) << maxGasreqForFailingOffers_before) >> (256 - maxGasreqForFailingOffers_bits); }} // equality checking function eq(Global __packed1, Global __packed2) internal pure returns (bool) { unchecked { return Global.unwrap(__packed1) == Global.unwrap(__packed2); }} // from packed to a tuple function unpack(Global __packed) internal pure returns (address __monitor, bool __useOracle, bool __notify, uint __gasprice, uint __gasmax, bool __dead, uint __maxRecursionDepth, uint __maxGasreqForFailingOffers) { unchecked { __monitor = address(uint160(uint(Global.unwrap(__packed) << monitor_before) >> (256 - monitor_bits))); __useOracle = ((Global.unwrap(__packed) & useOracle_mask_inv) > 0); __notify = ((Global.unwrap(__packed) & notify_mask_inv) > 0); __gasprice = uint(Global.unwrap(__packed) << gasprice_before) >> (256 - gasprice_bits); __gasmax = uint(Global.unwrap(__packed) << gasmax_before) >> (256 - gasmax_bits); __dead = ((Global.unwrap(__packed) & dead_mask_inv) > 0); __maxRecursionDepth = uint(Global.unwrap(__packed) << maxRecursionDepth_before) >> (256 - maxRecursionDepth_bits); __maxGasreqForFailingOffers = uint(Global.unwrap(__packed) << maxGasreqForFailingOffers_before) >> (256 - maxGasreqForFailingOffers_bits); }} // getters function monitor(Global __packed) internal pure returns(address) { unchecked { return address(uint160(uint(Global.unwrap(__packed) << monitor_before) >> (256 - monitor_bits))); }} // setters function monitor(Global __packed,address val) internal pure returns(Global) { unchecked { return Global.wrap((Global.unwrap(__packed) & monitor_mask) | (uint(uint160(val)) << (256 - monitor_bits)) >> monitor_before); }} function useOracle(Global __packed) internal pure returns(bool) { unchecked { return ((Global.unwrap(__packed) & useOracle_mask_inv) > 0); }} // setters function useOracle(Global __packed,bool val) internal pure returns(Global) { unchecked { return Global.wrap((Global.unwrap(__packed) & useOracle_mask) | (uint_of_bool(val) << (256 - useOracle_bits)) >> useOracle_before); }} function notify(Global __packed) internal pure returns(bool) { unchecked { return ((Global.unwrap(__packed) & notify_mask_inv) > 0); }} // setters function notify(Global __packed,bool val) internal pure returns(Global) { unchecked { return Global.wrap((Global.unwrap(__packed) & notify_mask) | (uint_of_bool(val) << (256 - notify_bits)) >> notify_before); }} function gasprice(Global __packed) internal pure returns(uint) { unchecked { return uint(Global.unwrap(__packed) << gasprice_before) >> (256 - gasprice_bits); }} // setters function gasprice(Global __packed,uint val) internal pure returns(Global) { unchecked { return Global.wrap((Global.unwrap(__packed) & gasprice_mask) | (val << (256 - gasprice_bits)) >> gasprice_before); }} function gasmax(Global __packed) internal pure returns(uint) { unchecked { return uint(Global.unwrap(__packed) << gasmax_before) >> (256 - gasmax_bits); }} // setters function gasmax(Global __packed,uint val) internal pure returns(Global) { unchecked { return Global.wrap((Global.unwrap(__packed) & gasmax_mask) | (val << (256 - gasmax_bits)) >> gasmax_before); }} function dead(Global __packed) internal pure returns(bool) { unchecked { return ((Global.unwrap(__packed) & dead_mask_inv) > 0); }} // setters function dead(Global __packed,bool val) internal pure returns(Global) { unchecked { return Global.wrap((Global.unwrap(__packed) & dead_mask) | (uint_of_bool(val) << (256 - dead_bits)) >> dead_before); }} function maxRecursionDepth(Global __packed) internal pure returns(uint) { unchecked { return uint(Global.unwrap(__packed) << maxRecursionDepth_before) >> (256 - maxRecursionDepth_bits); }} // setters function maxRecursionDepth(Global __packed,uint val) internal pure returns(Global) { unchecked { return Global.wrap((Global.unwrap(__packed) & maxRecursionDepth_mask) | (val << (256 - maxRecursionDepth_bits)) >> maxRecursionDepth_before); }} function maxGasreqForFailingOffers(Global __packed) internal pure returns(uint) { unchecked { return uint(Global.unwrap(__packed) << maxGasreqForFailingOffers_before) >> (256 - maxGasreqForFailingOffers_bits); }} // setters function maxGasreqForFailingOffers(Global __packed,uint val) internal pure returns(Global) { unchecked { return Global.wrap((Global.unwrap(__packed) & maxGasreqForFailingOffers_mask) | (val << (256 - maxGasreqForFailingOffers_bits)) >> maxGasreqForFailingOffers_before); }} // from in-memory struct to packed function t_of_struct(GlobalUnpacked memory __s) internal pure returns (Global) { unchecked { return pack(__s.monitor, __s.useOracle, __s.notify, __s.gasprice, __s.gasmax, __s.dead, __s.maxRecursionDepth, __s.maxGasreqForFailingOffers); }} // from arguments to packed function pack(address __monitor, bool __useOracle, bool __notify, uint __gasprice, uint __gasmax, bool __dead, uint __maxRecursionDepth, uint __maxGasreqForFailingOffers) internal pure returns (Global) { unchecked { uint __packed; __packed |= (uint(uint160(__monitor)) << (256 - monitor_bits)) >> monitor_before; __packed |= (uint_of_bool(__useOracle) << (256 - useOracle_bits)) >> useOracle_before; __packed |= (uint_of_bool(__notify) << (256 - notify_bits)) >> notify_before; __packed |= (__gasprice << (256 - gasprice_bits)) >> gasprice_before; __packed |= (__gasmax << (256 - gasmax_bits)) >> gasmax_before; __packed |= (uint_of_bool(__dead) << (256 - dead_bits)) >> dead_before; __packed |= (__maxRecursionDepth << (256 - maxRecursionDepth_bits)) >> maxRecursionDepth_before; __packed |= (__maxGasreqForFailingOffers << (256 - maxGasreqForFailingOffers_bits)) >> maxGasreqForFailingOffers_before; return Global.wrap(__packed); }} // input checking function monitor_check(address __monitor) internal pure returns (bool) { unchecked { return (uint(uint160(__monitor)) & monitor_cast_mask) == uint(uint160(__monitor)); }} function useOracle_check(bool __useOracle) internal pure returns (bool) { unchecked { return (uint_of_bool(__useOracle) & useOracle_cast_mask) == uint_of_bool(__useOracle); }} function notify_check(bool __notify) internal pure returns (bool) { unchecked { return (uint_of_bool(__notify) & notify_cast_mask) == uint_of_bool(__notify); }} function gasprice_check(uint __gasprice) internal pure returns (bool) { unchecked { return (__gasprice & gasprice_cast_mask) == __gasprice; }} function gasmax_check(uint __gasmax) internal pure returns (bool) { unchecked { return (__gasmax & gasmax_cast_mask) == __gasmax; }} function dead_check(bool __dead) internal pure returns (bool) { unchecked { return (uint_of_bool(__dead) & dead_cast_mask) == uint_of_bool(__dead); }} function maxRecursionDepth_check(uint __maxRecursionDepth) internal pure returns (bool) { unchecked { return (__maxRecursionDepth & maxRecursionDepth_cast_mask) == __maxRecursionDepth; }} function maxGasreqForFailingOffers_check(uint __maxGasreqForFailingOffers) internal pure returns (bool) { unchecked { return (__maxGasreqForFailingOffers & maxGasreqForFailingOffers_cast_mask) == __maxGasreqForFailingOffers; }} }lib/solady/src/utils/ReentrancyGuardTransient.sol
// SPDX-License-Identifier: MIT pragma solidity ^0.8.24; /// @notice Reentrancy guard mixin (transient storage variant). /// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/ReentrancyGuardTransient.sol) /// /// @dev Note: This implementation utilizes the `TSTORE` and `TLOAD` opcodes. /// Please ensure that the chain you are deploying on supports them. abstract contract ReentrancyGuardTransient { /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* CUSTOM ERRORS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Unauthorized reentrant call. error Reentrancy(); /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* STORAGE */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Equivalent to: `uint32(bytes4(keccak256("Reentrancy()"))) | 1 << 71`. /// 9 bytes is large enough to avoid collisions in practice, /// but not too large to result in excessive bytecode bloat. uint256 private constant _REENTRANCY_GUARD_SLOT = 0x8000000000ab143c06; /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* REENTRANCY GUARD */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Guards a function from reentrancy. modifier nonReentrant() virtual { if (_useTransientReentrancyGuardOnlyOnMainnet()) { uint256 s = _REENTRANCY_GUARD_SLOT; if (block.chainid == 1) { /// @solidity memory-safe-assembly assembly { if tload(s) { mstore(0x00, s) // `Reentrancy()`. revert(0x1c, 0x04) } tstore(s, address()) } } else { /// @solidity memory-safe-assembly assembly { if eq(sload(s), address()) { mstore(0x00, s) // `Reentrancy()`. revert(0x1c, 0x04) } sstore(s, address()) } } } else { /// @solidity memory-safe-assembly assembly { if tload(_REENTRANCY_GUARD_SLOT) { mstore(0x00, 0xab143c06) // `Reentrancy()`. revert(0x1c, 0x04) } tstore(_REENTRANCY_GUARD_SLOT, address()) } } _; if (_useTransientReentrancyGuardOnlyOnMainnet()) { uint256 s = _REENTRANCY_GUARD_SLOT; if (block.chainid == 1) { /// @solidity memory-safe-assembly assembly { tstore(s, 0) } } else { /// @solidity memory-safe-assembly assembly { sstore(s, s) } } } else { /// @solidity memory-safe-assembly assembly { tstore(_REENTRANCY_GUARD_SLOT, 0) } } } /// @dev Guards a view function from read-only reentrancy. modifier nonReadReentrant() virtual { if (_useTransientReentrancyGuardOnlyOnMainnet()) { uint256 s = _REENTRANCY_GUARD_SLOT; if (block.chainid == 1) { /// @solidity memory-safe-assembly assembly { if tload(s) { mstore(0x00, s) // `Reentrancy()`. revert(0x1c, 0x04) } } } else { /// @solidity memory-safe-assembly assembly { if eq(sload(s), address()) { mstore(0x00, s) // `Reentrancy()`. revert(0x1c, 0x04) } } } } else { /// @solidity memory-safe-assembly assembly { if tload(_REENTRANCY_GUARD_SLOT) { mstore(0x00, 0xab143c06) // `Reentrancy()`. revert(0x1c, 0x04) } } } _; } /// @dev For widespread compatibility with L2s. /// Only Ethereum mainnet is expensive anyways. function _useTransientReentrancyGuardOnlyOnMainnet() internal view virtual returns (bool) { return true; } }lib/mangrove-core/src/preprocessed/Offer.post.sol
// SPDX-License-Identifier: MIT pragma solidity ^0.8.13; /* ************************************************** * GENERATED FILE. DO NOT EDIT. * ************************************************** */ /* since you can't convert bool to uint in an expression without conditionals, * we add a file-level function and rely on compiler optimization */ function uint_of_bool(bool b) pure returns (uint u) { assembly ("memory-safe") { u := b } } import "@mgv/lib/core/Constants.sol"; struct OfferUnpacked { uint prev; uint next; Tick tick; uint gives; } //some type safety for each struct type Offer is uint; using OfferLib for Offer global; ////////////// ADDITIONAL DEFINITIONS, IF ANY //////////////// import {Bin} from "@mgv/lib/core/TickTreeLib.sol"; import {Tick} from "@mgv/lib/core/TickLib.sol"; import {OfferExtra,OfferUnpackedExtra} from "@mgv/lib/core/OfferExtra.sol"; using OfferExtra for Offer global; using OfferUnpackedExtra for OfferUnpacked global; ////////////// END OF ADDITIONAL DEFINITIONS ///////////////// library OfferLib { // number of bits in each field uint constant prev_bits = 32; uint constant next_bits = 32; uint constant tick_bits = 21; uint constant gives_bits = 127; // number of bits before each field uint constant prev_before = 0 + 0; uint constant next_before = prev_before + prev_bits; uint constant tick_before = next_before + next_bits; uint constant gives_before = tick_before + tick_bits; // focus-mask: 1s at field location, 0s elsewhere uint constant prev_mask_inv = (ONES << 256 - prev_bits) >> prev_before; uint constant next_mask_inv = (ONES << 256 - next_bits) >> next_before; uint constant tick_mask_inv = (ONES << 256 - tick_bits) >> tick_before; uint constant gives_mask_inv = (ONES << 256 - gives_bits) >> gives_before; // cleanup-mask: 0s at field location, 1s elsewhere uint constant prev_mask = ~prev_mask_inv; uint constant next_mask = ~next_mask_inv; uint constant tick_mask = ~tick_mask_inv; uint constant gives_mask = ~gives_mask_inv; // cast-mask: 0s followed by |field| trailing 1s uint constant prev_cast_mask = ~(ONES << prev_bits); uint constant next_cast_mask = ~(ONES << next_bits); uint constant tick_cast_mask = ~(ONES << tick_bits); uint constant gives_cast_mask = ~(ONES << gives_bits); // size-related error message string constant prev_size_error = "mgv/config/prev/32bits"; string constant next_size_error = "mgv/config/next/32bits"; string constant tick_size_error = "mgv/config/tick/21bits"; string constant gives_size_error = "mgv/config/gives/127bits"; // from packed to in-memory struct function to_struct(Offer __packed) internal pure returns (OfferUnpacked memory __s) { unchecked { __s.prev = uint(Offer.unwrap(__packed) << prev_before) >> (256 - prev_bits); __s.next = uint(Offer.unwrap(__packed) << next_before) >> (256 - next_bits); __s.tick = Tick.wrap(int(int(Offer.unwrap(__packed) << tick_before) >> (256 - tick_bits))); __s.gives = uint(Offer.unwrap(__packed) << gives_before) >> (256 - gives_bits); }} // equality checking function eq(Offer __packed1, Offer __packed2) internal pure returns (bool) { unchecked { return Offer.unwrap(__packed1) == Offer.unwrap(__packed2); }} // from packed to a tuple function unpack(Offer __packed) internal pure returns (uint __prev, uint __next, Tick __tick, uint __gives) { unchecked { __prev = uint(Offer.unwrap(__packed) << prev_before) >> (256 - prev_bits); __next = uint(Offer.unwrap(__packed) << next_before) >> (256 - next_bits); __tick = Tick.wrap(int(int(Offer.unwrap(__packed) << tick_before) >> (256 - tick_bits))); __gives = uint(Offer.unwrap(__packed) << gives_before) >> (256 - gives_bits); }} // getters function prev(Offer __packed) internal pure returns(uint) { unchecked { return uint(Offer.unwrap(__packed) << prev_before) >> (256 - prev_bits); }} // setters function prev(Offer __packed,uint val) internal pure returns(Offer) { unchecked { return Offer.wrap((Offer.unwrap(__packed) & prev_mask) | (val << (256 - prev_bits)) >> prev_before); }} function next(Offer __packed) internal pure returns(uint) { unchecked { return uint(Offer.unwrap(__packed) << next_before) >> (256 - next_bits); }} // setters function next(Offer __packed,uint val) internal pure returns(Offer) { unchecked { return Offer.wrap((Offer.unwrap(__packed) & next_mask) | (val << (256 - next_bits)) >> next_before); }} function tick(Offer __packed) internal pure returns(Tick) { unchecked { return Tick.wrap(int(int(Offer.unwrap(__packed) << tick_before) >> (256 - tick_bits))); }} // setters function tick(Offer __packed,Tick val) internal pure returns(Offer) { unchecked { return Offer.wrap((Offer.unwrap(__packed) & tick_mask) | (uint(Tick.unwrap(val)) << (256 - tick_bits)) >> tick_before); }} function gives(Offer __packed) internal pure returns(uint) { unchecked { return uint(Offer.unwrap(__packed) << gives_before) >> (256 - gives_bits); }} // setters function gives(Offer __packed,uint val) internal pure returns(Offer) { unchecked { return Offer.wrap((Offer.unwrap(__packed) & gives_mask) | (val << (256 - gives_bits)) >> gives_before); }} // from in-memory struct to packed function t_of_struct(OfferUnpacked memory __s) internal pure returns (Offer) { unchecked { return pack(__s.prev, __s.next, __s.tick, __s.gives); }} // from arguments to packed function pack(uint __prev, uint __next, Tick __tick, uint __gives) internal pure returns (Offer) { unchecked { uint __packed; __packed |= (__prev << (256 - prev_bits)) >> prev_before; __packed |= (__next << (256 - next_bits)) >> next_before; __packed |= (uint(Tick.unwrap(__tick)) << (256 - tick_bits)) >> tick_before; __packed |= (__gives << (256 - gives_bits)) >> gives_before; return Offer.wrap(__packed); }} // input checking function prev_check(uint __prev) internal pure returns (bool) { unchecked { return (__prev & prev_cast_mask) == __prev; }} function next_check(uint __next) internal pure returns (bool) { unchecked { return (__next & next_cast_mask) == __next; }} function tick_check(Tick __tick) internal pure returns (bool) { unchecked { return (uint(Tick.unwrap(__tick)) & tick_cast_mask) == uint(Tick.unwrap(__tick)); }} function gives_check(uint __gives) internal pure returns (bool) { unchecked { return (__gives & gives_cast_mask) == __gives; }} }lib/mangrove-core/lib/forge-std/src/console2.sol
// SPDX-License-Identifier: MIT pragma solidity >=0.4.22 <0.9.0; /// @dev The original console.sol uses `int` and `uint` for computing function selectors, but it should /// use `int256` and `uint256`. This modified version fixes that. This version is recommended /// over `console.sol` if you don't need compatibility with Hardhat as the logs will show up in /// forge stack traces. If you do need compatibility with Hardhat, you must use `console.sol`. /// Reference: https://github.com/NomicFoundation/hardhat/issues/2178 library console2 { address constant CONSOLE_ADDRESS = address(0x000000000000000000636F6e736F6c652e6c6f67); function _castLogPayloadViewToPure( function(bytes memory) internal view fnIn ) internal pure returns (function(bytes memory) internal pure fnOut) { assembly { fnOut := fnIn } } function _sendLogPayload(bytes memory payload) internal pure { _castLogPayloadViewToPure(_sendLogPayloadView)(payload); } function _sendLogPayloadView(bytes memory payload) private view { uint256 payloadLength = payload.length; address consoleAddress = CONSOLE_ADDRESS; /// @solidity memory-safe-assembly assembly { let payloadStart := add(payload, 32) let r := staticcall(gas(), consoleAddress, payloadStart, payloadLength, 0, 0) } } function log() internal pure { _sendLogPayload(abi.encodeWithSignature("log()")); } function logInt(int256 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(int256)", p0)); } function logUint(uint256 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256)", p0)); } function logString(string memory p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string)", p0)); } function logBool(bool p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool)", p0)); } function logAddress(address p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address)", p0)); } function logBytes(bytes memory p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes)", p0)); } function logBytes1(bytes1 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes1)", p0)); } function logBytes2(bytes2 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes2)", p0)); } function logBytes3(bytes3 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes3)", p0)); } function logBytes4(bytes4 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes4)", p0)); } function logBytes5(bytes5 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes5)", p0)); } function logBytes6(bytes6 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes6)", p0)); } function logBytes7(bytes7 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes7)", p0)); } function logBytes8(bytes8 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes8)", p0)); } function logBytes9(bytes9 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes9)", p0)); } function logBytes10(bytes10 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes10)", p0)); } function logBytes11(bytes11 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes11)", p0)); } function logBytes12(bytes12 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes12)", p0)); } function logBytes13(bytes13 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes13)", p0)); } function logBytes14(bytes14 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes14)", p0)); } function logBytes15(bytes15 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes15)", p0)); } function logBytes16(bytes16 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes16)", p0)); } function logBytes17(bytes17 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes17)", p0)); } function logBytes18(bytes18 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes18)", p0)); } function logBytes19(bytes19 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes19)", p0)); } function logBytes20(bytes20 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes20)", p0)); } function logBytes21(bytes21 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes21)", p0)); } function logBytes22(bytes22 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes22)", p0)); } function logBytes23(bytes23 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes23)", p0)); } function logBytes24(bytes24 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes24)", p0)); } function logBytes25(bytes25 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes25)", p0)); } function logBytes26(bytes26 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes26)", p0)); } function logBytes27(bytes27 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes27)", p0)); } function logBytes28(bytes28 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes28)", p0)); } function logBytes29(bytes29 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes29)", p0)); } function logBytes30(bytes30 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes30)", p0)); } function logBytes31(bytes31 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes31)", p0)); } function logBytes32(bytes32 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes32)", p0)); } function log(uint256 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256)", p0)); } function log(int256 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(int256)", p0)); } function log(string memory p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string)", p0)); } function log(bool p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool)", p0)); } function log(address p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address)", p0)); } function log(uint256 p0, uint256 p1) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256)", p0, p1)); } function log(uint256 p0, string memory p1) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,string)", p0, p1)); } function log(uint256 p0, bool p1) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,bool)", p0, p1)); } function log(uint256 p0, address p1) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,address)", p0, p1)); } function log(string memory p0, uint256 p1) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,uint256)", p0, p1)); } function log(string memory p0, int256 p1) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,int256)", p0, p1)); } function log(string memory p0, string memory p1) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,string)", p0, p1)); } function log(string memory p0, bool p1) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,bool)", p0, p1)); } function log(string memory p0, address p1) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,address)", p0, p1)); } function log(bool p0, uint256 p1) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,uint256)", p0, p1)); } function log(bool p0, string memory p1) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,string)", p0, p1)); } function log(bool p0, bool p1) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,bool)", p0, p1)); } function log(bool p0, address p1) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,address)", p0, p1)); } function log(address p0, uint256 p1) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,uint256)", p0, p1)); } function log(address p0, string memory p1) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,string)", p0, p1)); } function log(address p0, bool p1) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,bool)", p0, p1)); } function log(address p0, address p1) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,address)", p0, p1)); } function log(uint256 p0, uint256 p1, uint256 p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,uint256)", p0, p1, p2)); } function log(uint256 p0, uint256 p1, string memory p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,string)", p0, p1, p2)); } function log(uint256 p0, uint256 p1, bool p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,bool)", p0, p1, p2)); } function log(uint256 p0, uint256 p1, address p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,address)", p0, p1, p2)); } function log(uint256 p0, string memory p1, uint256 p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,string,uint256)", p0, p1, p2)); } function log(uint256 p0, string memory p1, string memory p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,string,string)", p0, p1, p2)); } function log(uint256 p0, string memory p1, bool p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,string,bool)", p0, p1, p2)); } function log(uint256 p0, string memory p1, address p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,string,address)", p0, p1, p2)); } function log(uint256 p0, bool p1, uint256 p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,uint256)", p0, p1, p2)); } function log(uint256 p0, bool p1, string memory p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,string)", p0, p1, p2)); } function log(uint256 p0, bool p1, bool p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,bool)", p0, p1, p2)); } function log(uint256 p0, bool p1, address p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,address)", p0, p1, p2)); } function log(uint256 p0, address p1, uint256 p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,address,uint256)", p0, p1, p2)); } function log(uint256 p0, address p1, string memory p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,address,string)", p0, p1, p2)); } function log(uint256 p0, address p1, bool p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,address,bool)", p0, p1, p2)); } function log(uint256 p0, address p1, address p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,address,address)", p0, p1, p2)); } function log(string memory p0, uint256 p1, uint256 p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,uint256,uint256)", p0, p1, p2)); } function log(string memory p0, uint256 p1, string memory p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,uint256,string)", p0, p1, p2)); } function log(string memory p0, uint256 p1, bool p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,uint256,bool)", p0, p1, p2)); } function log(string memory p0, uint256 p1, address p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,uint256,address)", p0, p1, p2)); } function log(string memory p0, string memory p1, uint256 p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,string,uint256)", p0, p1, p2)); } function log(string memory p0, string memory p1, string memory p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,string,string)", p0, p1, p2)); } function log(string memory p0, string memory p1, bool p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,string,bool)", p0, p1, p2)); } function log(string memory p0, string memory p1, address p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,string,address)", p0, p1, p2)); } function log(string memory p0, bool p1, uint256 p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,bool,uint256)", p0, p1, p2)); } function log(string memory p0, bool p1, string memory p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,bool,string)", p0, p1, p2)); } function log(string memory p0, bool p1, bool p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,bool,bool)", p0, p1, p2)); } function log(string memory p0, bool p1, address p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,bool,address)", p0, p1, p2)); } function log(string memory p0, address p1, uint256 p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,address,uint256)", p0, p1, p2)); } function log(string memory p0, address p1, string memory p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,address,string)", p0, p1, p2)); } function log(string memory p0, address p1, bool p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,address,bool)", p0, p1, p2)); } function log(string memory p0, address p1, address p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,address,address)", p0, p1, p2)); } function log(bool p0, uint256 p1, uint256 p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,uint256)", p0, p1, p2)); } function log(bool p0, uint256 p1, string memory p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,string)", p0, p1, p2)); } function log(bool p0, uint256 p1, bool p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,bool)", p0, p1, p2)); } function log(bool p0, uint256 p1, address p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,address)", p0, p1, p2)); } function log(bool p0, string memory p1, uint256 p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,string,uint256)", p0, p1, p2)); } function log(bool p0, string memory p1, string memory p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,string,string)", p0, p1, p2)); } function log(bool p0, string memory p1, bool p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,string,bool)", p0, p1, p2)); } function log(bool p0, string memory p1, address p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,string,address)", p0, p1, p2)); } function log(bool p0, bool p1, uint256 p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint256)", p0, p1, p2)); } function log(bool p0, bool p1, string memory p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,string)", p0, p1, p2)); } function log(bool p0, bool p1, bool p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,bool)", p0, p1, p2)); } function log(bool p0, bool p1, address p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,address)", p0, p1, p2)); } function log(bool p0, address p1, uint256 p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,address,uint256)", p0, p1, p2)); } function log(bool p0, address p1, string memory p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,address,string)", p0, p1, p2)); } function log(bool p0, address p1, bool p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,address,bool)", p0, p1, p2)); } function log(bool p0, address p1, address p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,address,address)", p0, p1, p2)); } function log(address p0, uint256 p1, uint256 p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,uint256,uint256)", p0, p1, p2)); } function log(address p0, uint256 p1, string memory p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,uint256,string)", p0, p1, p2)); } function log(address p0, uint256 p1, bool p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,uint256,bool)", p0, p1, p2)); } function log(address p0, uint256 p1, address p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,uint256,address)", p0, p1, p2)); } function log(address p0, string memory p1, uint256 p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,string,uint256)", p0, p1, p2)); } function log(address p0, string memory p1, string memory p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,string,string)", p0, p1, p2)); } function log(address p0, string memory p1, bool p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,string,bool)", p0, p1, p2)); } function log(address p0, string memory p1, address p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,string,address)", p0, p1, p2)); } function log(address p0, bool p1, uint256 p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,bool,uint256)", p0, p1, p2)); } function log(address p0, bool p1, string memory p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,bool,string)", p0, p1, p2)); } function log(address p0, bool p1, bool p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,bool,bool)", p0, p1, p2)); } function log(address p0, bool p1, address p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,bool,address)", p0, p1, p2)); } function log(address p0, address p1, uint256 p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,address,uint256)", p0, p1, p2)); } function log(address p0, address p1, string memory p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,address,string)", p0, p1, p2)); } function log(address p0, address p1, bool p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,address,bool)", p0, p1, p2)); } function log(address p0, address p1, address p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,address,address)", p0, p1, p2)); } function log(uint256 p0, uint256 p1, uint256 p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,uint256,uint256)", p0, p1, p2, p3)); } function log(uint256 p0, uint256 p1, uint256 p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,uint256,string)", p0, p1, p2, p3)); } function log(uint256 p0, uint256 p1, uint256 p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,uint256,bool)", p0, p1, p2, p3)); } function log(uint256 p0, uint256 p1, uint256 p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,uint256,address)", p0, p1, p2, p3)); } function log(uint256 p0, uint256 p1, string memory p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,string,uint256)", p0, p1, p2, p3)); } function log(uint256 p0, uint256 p1, string memory p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,string,string)", p0, p1, p2, p3)); } function log(uint256 p0, uint256 p1, string memory p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,string,bool)", p0, p1, p2, p3)); } function log(uint256 p0, uint256 p1, string memory p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,string,address)", p0, p1, p2, p3)); } function log(uint256 p0, uint256 p1, bool p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,bool,uint256)", p0, p1, p2, p3)); } function log(uint256 p0, uint256 p1, bool p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,bool,string)", p0, p1, p2, p3)); } function log(uint256 p0, uint256 p1, bool p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,bool,bool)", p0, p1, p2, p3)); } function log(uint256 p0, uint256 p1, bool p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,bool,address)", p0, p1, p2, p3)); } function log(uint256 p0, uint256 p1, address p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,address,uint256)", p0, p1, p2, p3)); } function log(uint256 p0, uint256 p1, address p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,address,string)", p0, p1, p2, p3)); } function log(uint256 p0, uint256 p1, address p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,address,bool)", p0, p1, p2, p3)); } function log(uint256 p0, uint256 p1, address p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,address,address)", p0, p1, p2, p3)); } function log(uint256 p0, string memory p1, uint256 p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,string,uint256,uint256)", p0, p1, p2, p3)); } function log(uint256 p0, string memory p1, uint256 p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,string,uint256,string)", p0, p1, p2, p3)); } function log(uint256 p0, string memory p1, uint256 p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,string,uint256,bool)", p0, p1, p2, p3)); } function log(uint256 p0, string memory p1, uint256 p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,string,uint256,address)", p0, p1, p2, p3)); } function log(uint256 p0, string memory p1, string memory p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,string,string,uint256)", p0, p1, p2, p3)); } function log(uint256 p0, string memory p1, string memory p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,string,string,string)", p0, p1, p2, p3)); } function log(uint256 p0, string memory p1, string memory p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,string,string,bool)", p0, p1, p2, p3)); } function log(uint256 p0, string memory p1, string memory p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,string,string,address)", p0, p1, p2, p3)); } function log(uint256 p0, string memory p1, bool p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,string,bool,uint256)", p0, p1, p2, p3)); } function log(uint256 p0, string memory p1, bool p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,string,bool,string)", p0, p1, p2, p3)); } function log(uint256 p0, string memory p1, bool p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,string,bool,bool)", p0, p1, p2, p3)); } function log(uint256 p0, string memory p1, bool p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,string,bool,address)", p0, p1, p2, p3)); } function log(uint256 p0, string memory p1, address p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,string,address,uint256)", p0, p1, p2, p3)); } function log(uint256 p0, string memory p1, address p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,string,address,string)", p0, p1, p2, p3)); } function log(uint256 p0, string memory p1, address p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,string,address,bool)", p0, p1, p2, p3)); } function log(uint256 p0, string memory p1, address p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,string,address,address)", p0, p1, p2, p3)); } function log(uint256 p0, bool p1, uint256 p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,uint256,uint256)", p0, p1, p2, p3)); } function log(uint256 p0, bool p1, uint256 p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,uint256,string)", p0, p1, p2, p3)); } function log(uint256 p0, bool p1, uint256 p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,uint256,bool)", p0, p1, p2, p3)); } function log(uint256 p0, bool p1, uint256 p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,uint256,address)", p0, p1, p2, p3)); } function log(uint256 p0, bool p1, string memory p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,string,uint256)", p0, p1, p2, p3)); } function log(uint256 p0, bool p1, string memory p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,string,string)", p0, p1, p2, p3)); } function log(uint256 p0, bool p1, string memory p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,string,bool)", p0, p1, p2, p3)); } function log(uint256 p0, bool p1, string memory p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,string,address)", p0, p1, p2, p3)); } function log(uint256 p0, bool p1, bool p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,bool,uint256)", p0, p1, p2, p3)); } function log(uint256 p0, bool p1, bool p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,bool,string)", p0, p1, p2, p3)); } function log(uint256 p0, bool p1, bool p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,bool,bool)", p0, p1, p2, p3)); } function log(uint256 p0, bool p1, bool p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,bool,address)", p0, p1, p2, p3)); } function log(uint256 p0, bool p1, address p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,address,uint256)", p0, p1, p2, p3)); } function log(uint256 p0, bool p1, address p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,address,string)", p0, p1, p2, p3)); } function log(uint256 p0, bool p1, address p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,address,bool)", p0, p1, p2, p3)); } function log(uint256 p0, bool p1, address p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,address,address)", p0, p1, p2, p3)); } function log(uint256 p0, address p1, uint256 p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,address,uint256,uint256)", p0, p1, p2, p3)); } function log(uint256 p0, address p1, uint256 p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,address,uint256,string)", p0, p1, p2, p3)); } function log(uint256 p0, address p1, uint256 p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,address,uint256,bool)", p0, p1, p2, p3)); } function log(uint256 p0, address p1, uint256 p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,address,uint256,address)", p0, p1, p2, p3)); } function log(uint256 p0, address p1, string memory p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,address,string,uint256)", p0, p1, p2, p3)); } function log(uint256 p0, address p1, string memory p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,address,string,string)", p0, p1, p2, p3)); } function log(uint256 p0, address p1, string memory p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,address,string,bool)", p0, p1, p2, p3)); } function log(uint256 p0, address p1, string memory p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,address,string,address)", p0, p1, p2, p3)); } function log(uint256 p0, address p1, bool p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,address,bool,uint256)", p0, p1, p2, p3)); } function log(uint256 p0, address p1, bool p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,address,bool,string)", p0, p1, p2, p3)); } function log(uint256 p0, address p1, bool p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,address,bool,bool)", p0, p1, p2, p3)); } function log(uint256 p0, address p1, bool p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,address,bool,address)", p0, p1, p2, p3)); } function log(uint256 p0, address p1, address p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,address,address,uint256)", p0, p1, p2, p3)); } function log(uint256 p0, address p1, address p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,address,address,string)", p0, p1, p2, p3)); } function log(uint256 p0, address p1, address p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,address,address,bool)", p0, p1, p2, p3)); } function log(uint256 p0, address p1, address p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(uint256,address,address,address)", p0, p1, p2, p3)); } function log(string memory p0, uint256 p1, uint256 p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,uint256,uint256,uint256)", p0, p1, p2, p3)); } function log(string memory p0, uint256 p1, uint256 p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,uint256,uint256,string)", p0, p1, p2, p3)); } function log(string memory p0, uint256 p1, uint256 p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,uint256,uint256,bool)", p0, p1, p2, p3)); } function log(string memory p0, uint256 p1, uint256 p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,uint256,uint256,address)", p0, p1, p2, p3)); } function log(string memory p0, uint256 p1, string memory p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,uint256,string,uint256)", p0, p1, p2, p3)); } function log(string memory p0, uint256 p1, string memory p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,uint256,string,string)", p0, p1, p2, p3)); } function log(string memory p0, uint256 p1, string memory p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,uint256,string,bool)", p0, p1, p2, p3)); } function log(string memory p0, uint256 p1, string memory p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,uint256,string,address)", p0, p1, p2, p3)); } function log(string memory p0, uint256 p1, bool p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,uint256,bool,uint256)", p0, p1, p2, p3)); } function log(string memory p0, uint256 p1, bool p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,uint256,bool,string)", p0, p1, p2, p3)); } function log(string memory p0, uint256 p1, bool p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,uint256,bool,bool)", p0, p1, p2, p3)); } function log(string memory p0, uint256 p1, bool p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,uint256,bool,address)", p0, p1, p2, p3)); } function log(string memory p0, uint256 p1, address p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,uint256,address,uint256)", p0, p1, p2, p3)); } function log(string memory p0, uint256 p1, address p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,uint256,address,string)", p0, p1, p2, p3)); } function log(string memory p0, uint256 p1, address p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,uint256,address,bool)", p0, p1, p2, p3)); } function log(string memory p0, uint256 p1, address p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,uint256,address,address)", p0, p1, p2, p3)); } function log(string memory p0, string memory p1, uint256 p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,string,uint256,uint256)", p0, p1, p2, p3)); } function log(string memory p0, string memory p1, uint256 p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,string,uint256,string)", p0, p1, p2, p3)); } function log(string memory p0, string memory p1, uint256 p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,string,uint256,bool)", p0, p1, p2, p3)); } function log(string memory p0, string memory p1, uint256 p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,string,uint256,address)", p0, p1, p2, p3)); } function log(string memory p0, string memory p1, string memory p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,string,string,uint256)", p0, p1, p2, p3)); } function log(string memory p0, string memory p1, string memory p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,string,string,string)", p0, p1, p2, p3)); } function log(string memory p0, string memory p1, string memory p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,string,string,bool)", p0, p1, p2, p3)); } function log(string memory p0, string memory p1, string memory p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,string,string,address)", p0, p1, p2, p3)); } function log(string memory p0, string memory p1, bool p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,string,bool,uint256)", p0, p1, p2, p3)); } function log(string memory p0, string memory p1, bool p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,string,bool,string)", p0, p1, p2, p3)); } function log(string memory p0, string memory p1, bool p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,string,bool,bool)", p0, p1, p2, p3)); } function log(string memory p0, string memory p1, bool p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,string,bool,address)", p0, p1, p2, p3)); } function log(string memory p0, string memory p1, address p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,string,address,uint256)", p0, p1, p2, p3)); } function log(string memory p0, string memory p1, address p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,string,address,string)", p0, p1, p2, p3)); } function log(string memory p0, string memory p1, address p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,string,address,bool)", p0, p1, p2, p3)); } function log(string memory p0, string memory p1, address p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,string,address,address)", p0, p1, p2, p3)); } function log(string memory p0, bool p1, uint256 p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,bool,uint256,uint256)", p0, p1, p2, p3)); } function log(string memory p0, bool p1, uint256 p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,bool,uint256,string)", p0, p1, p2, p3)); } function log(string memory p0, bool p1, uint256 p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,bool,uint256,bool)", p0, p1, p2, p3)); } function log(string memory p0, bool p1, uint256 p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,bool,uint256,address)", p0, p1, p2, p3)); } function log(string memory p0, bool p1, string memory p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,bool,string,uint256)", p0, p1, p2, p3)); } function log(string memory p0, bool p1, string memory p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,bool,string,string)", p0, p1, p2, p3)); } function log(string memory p0, bool p1, string memory p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,bool,string,bool)", p0, p1, p2, p3)); } function log(string memory p0, bool p1, string memory p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,bool,string,address)", p0, p1, p2, p3)); } function log(string memory p0, bool p1, bool p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,bool,bool,uint256)", p0, p1, p2, p3)); } function log(string memory p0, bool p1, bool p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,bool,bool,string)", p0, p1, p2, p3)); } function log(string memory p0, bool p1, bool p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,bool,bool,bool)", p0, p1, p2, p3)); } function log(string memory p0, bool p1, bool p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,bool,bool,address)", p0, p1, p2, p3)); } function log(string memory p0, bool p1, address p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,bool,address,uint256)", p0, p1, p2, p3)); } function log(string memory p0, bool p1, address p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,bool,address,string)", p0, p1, p2, p3)); } function log(string memory p0, bool p1, address p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,bool,address,bool)", p0, p1, p2, p3)); } function log(string memory p0, bool p1, address p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,bool,address,address)", p0, p1, p2, p3)); } function log(string memory p0, address p1, uint256 p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,address,uint256,uint256)", p0, p1, p2, p3)); } function log(string memory p0, address p1, uint256 p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,address,uint256,string)", p0, p1, p2, p3)); } function log(string memory p0, address p1, uint256 p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,address,uint256,bool)", p0, p1, p2, p3)); } function log(string memory p0, address p1, uint256 p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,address,uint256,address)", p0, p1, p2, p3)); } function log(string memory p0, address p1, string memory p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,address,string,uint256)", p0, p1, p2, p3)); } function log(string memory p0, address p1, string memory p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,address,string,string)", p0, p1, p2, p3)); } function log(string memory p0, address p1, string memory p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,address,string,bool)", p0, p1, p2, p3)); } function log(string memory p0, address p1, string memory p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,address,string,address)", p0, p1, p2, p3)); } function log(string memory p0, address p1, bool p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,address,bool,uint256)", p0, p1, p2, p3)); } function log(string memory p0, address p1, bool p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,address,bool,string)", p0, p1, p2, p3)); } function log(string memory p0, address p1, bool p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,address,bool,bool)", p0, p1, p2, p3)); } function log(string memory p0, address p1, bool p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,address,bool,address)", p0, p1, p2, p3)); } function log(string memory p0, address p1, address p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,address,address,uint256)", p0, p1, p2, p3)); } function log(string memory p0, address p1, address p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,address,address,string)", p0, p1, p2, p3)); } function log(string memory p0, address p1, address p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,address,address,bool)", p0, p1, p2, p3)); } function log(string memory p0, address p1, address p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,address,address,address)", p0, p1, p2, p3)); } function log(bool p0, uint256 p1, uint256 p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,uint256,uint256)", p0, p1, p2, p3)); } function log(bool p0, uint256 p1, uint256 p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,uint256,string)", p0, p1, p2, p3)); } function log(bool p0, uint256 p1, uint256 p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,uint256,bool)", p0, p1, p2, p3)); } function log(bool p0, uint256 p1, uint256 p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,uint256,address)", p0, p1, p2, p3)); } function log(bool p0, uint256 p1, string memory p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,string,uint256)", p0, p1, p2, p3)); } function log(bool p0, uint256 p1, string memory p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,string,string)", p0, p1, p2, p3)); } function log(bool p0, uint256 p1, string memory p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,string,bool)", p0, p1, p2, p3)); } function log(bool p0, uint256 p1, string memory p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,string,address)", p0, p1, p2, p3)); } function log(bool p0, uint256 p1, bool p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,bool,uint256)", p0, p1, p2, p3)); } function log(bool p0, uint256 p1, bool p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,bool,string)", p0, p1, p2, p3)); } function log(bool p0, uint256 p1, bool p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,bool,bool)", p0, p1, p2, p3)); } function log(bool p0, uint256 p1, bool p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,bool,address)", p0, p1, p2, p3)); } function log(bool p0, uint256 p1, address p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,address,uint256)", p0, p1, p2, p3)); } function log(bool p0, uint256 p1, address p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,address,string)", p0, p1, p2, p3)); } function log(bool p0, uint256 p1, address p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,address,bool)", p0, p1, p2, p3)); } function log(bool p0, uint256 p1, address p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,address,address)", p0, p1, p2, p3)); } function log(bool p0, string memory p1, uint256 p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,string,uint256,uint256)", p0, p1, p2, p3)); } function log(bool p0, string memory p1, uint256 p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,string,uint256,string)", p0, p1, p2, p3)); } function log(bool p0, string memory p1, uint256 p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,string,uint256,bool)", p0, p1, p2, p3)); } function log(bool p0, string memory p1, uint256 p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,string,uint256,address)", p0, p1, p2, p3)); } function log(bool p0, string memory p1, string memory p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,string,string,uint256)", p0, p1, p2, p3)); } function log(bool p0, string memory p1, string memory p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,string,string,string)", p0, p1, p2, p3)); } function log(bool p0, string memory p1, string memory p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,string,string,bool)", p0, p1, p2, p3)); } function log(bool p0, string memory p1, string memory p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,string,string,address)", p0, p1, p2, p3)); } function log(bool p0, string memory p1, bool p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,string,bool,uint256)", p0, p1, p2, p3)); } function log(bool p0, string memory p1, bool p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,string,bool,string)", p0, p1, p2, p3)); } function log(bool p0, string memory p1, bool p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,string,bool,bool)", p0, p1, p2, p3)); } function log(bool p0, string memory p1, bool p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,string,bool,address)", p0, p1, p2, p3)); } function log(bool p0, string memory p1, address p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,string,address,uint256)", p0, p1, p2, p3)); } function log(bool p0, string memory p1, address p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,string,address,string)", p0, p1, p2, p3)); } function log(bool p0, string memory p1, address p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,string,address,bool)", p0, p1, p2, p3)); } function log(bool p0, string memory p1, address p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,string,address,address)", p0, p1, p2, p3)); } function log(bool p0, bool p1, uint256 p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint256,uint256)", p0, p1, p2, p3)); } function log(bool p0, bool p1, uint256 p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint256,string)", p0, p1, p2, p3)); } function log(bool p0, bool p1, uint256 p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint256,bool)", p0, p1, p2, p3)); } function log(bool p0, bool p1, uint256 p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint256,address)", p0, p1, p2, p3)); } function log(bool p0, bool p1, string memory p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,string,uint256)", p0, p1, p2, p3)); } function log(bool p0, bool p1, string memory p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,string,string)", p0, p1, p2, p3)); } function log(bool p0, bool p1, string memory p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,string,bool)", p0, p1, p2, p3)); } function log(bool p0, bool p1, string memory p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,string,address)", p0, p1, p2, p3)); } function log(bool p0, bool p1, bool p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,bool,uint256)", p0, p1, p2, p3)); } function log(bool p0, bool p1, bool p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,bool,string)", p0, p1, p2, p3)); } function log(bool p0, bool p1, bool p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,bool,bool)", p0, p1, p2, p3)); } function log(bool p0, bool p1, bool p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,bool,address)", p0, p1, p2, p3)); } function log(bool p0, bool p1, address p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,address,uint256)", p0, p1, p2, p3)); } function log(bool p0, bool p1, address p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,address,string)", p0, p1, p2, p3)); } function log(bool p0, bool p1, address p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,address,bool)", p0, p1, p2, p3)); } function log(bool p0, bool p1, address p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,address,address)", p0, p1, p2, p3)); } function log(bool p0, address p1, uint256 p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,address,uint256,uint256)", p0, p1, p2, p3)); } function log(bool p0, address p1, uint256 p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,address,uint256,string)", p0, p1, p2, p3)); } function log(bool p0, address p1, uint256 p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,address,uint256,bool)", p0, p1, p2, p3)); } function log(bool p0, address p1, uint256 p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,address,uint256,address)", p0, p1, p2, p3)); } function log(bool p0, address p1, string memory p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,address,string,uint256)", p0, p1, p2, p3)); } function log(bool p0, address p1, string memory p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,address,string,string)", p0, p1, p2, p3)); } function log(bool p0, address p1, string memory p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,address,string,bool)", p0, p1, p2, p3)); } function log(bool p0, address p1, string memory p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,address,string,address)", p0, p1, p2, p3)); } function log(bool p0, address p1, bool p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,address,bool,uint256)", p0, p1, p2, p3)); } function log(bool p0, address p1, bool p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,address,bool,string)", p0, p1, p2, p3)); } function log(bool p0, address p1, bool p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,address,bool,bool)", p0, p1, p2, p3)); } function log(bool p0, address p1, bool p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,address,bool,address)", p0, p1, p2, p3)); } function log(bool p0, address p1, address p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,address,address,uint256)", p0, p1, p2, p3)); } function log(bool p0, address p1, address p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,address,address,string)", p0, p1, p2, p3)); } function log(bool p0, address p1, address p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,address,address,bool)", p0, p1, p2, p3)); } function log(bool p0, address p1, address p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,address,address,address)", p0, p1, p2, p3)); } function log(address p0, uint256 p1, uint256 p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,uint256,uint256,uint256)", p0, p1, p2, p3)); } function log(address p0, uint256 p1, uint256 p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,uint256,uint256,string)", p0, p1, p2, p3)); } function log(address p0, uint256 p1, uint256 p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,uint256,uint256,bool)", p0, p1, p2, p3)); } function log(address p0, uint256 p1, uint256 p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,uint256,uint256,address)", p0, p1, p2, p3)); } function log(address p0, uint256 p1, string memory p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,uint256,string,uint256)", p0, p1, p2, p3)); } function log(address p0, uint256 p1, string memory p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,uint256,string,string)", p0, p1, p2, p3)); } function log(address p0, uint256 p1, string memory p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,uint256,string,bool)", p0, p1, p2, p3)); } function log(address p0, uint256 p1, string memory p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,uint256,string,address)", p0, p1, p2, p3)); } function log(address p0, uint256 p1, bool p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,uint256,bool,uint256)", p0, p1, p2, p3)); } function log(address p0, uint256 p1, bool p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,uint256,bool,string)", p0, p1, p2, p3)); } function log(address p0, uint256 p1, bool p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,uint256,bool,bool)", p0, p1, p2, p3)); } function log(address p0, uint256 p1, bool p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,uint256,bool,address)", p0, p1, p2, p3)); } function log(address p0, uint256 p1, address p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,uint256,address,uint256)", p0, p1, p2, p3)); } function log(address p0, uint256 p1, address p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,uint256,address,string)", p0, p1, p2, p3)); } function log(address p0, uint256 p1, address p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,uint256,address,bool)", p0, p1, p2, p3)); } function log(address p0, uint256 p1, address p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,uint256,address,address)", p0, p1, p2, p3)); } function log(address p0, string memory p1, uint256 p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,string,uint256,uint256)", p0, p1, p2, p3)); } function log(address p0, string memory p1, uint256 p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,string,uint256,string)", p0, p1, p2, p3)); } function log(address p0, string memory p1, uint256 p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,string,uint256,bool)", p0, p1, p2, p3)); } function log(address p0, string memory p1, uint256 p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,string,uint256,address)", p0, p1, p2, p3)); } function log(address p0, string memory p1, string memory p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,string,string,uint256)", p0, p1, p2, p3)); } function log(address p0, string memory p1, string memory p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,string,string,string)", p0, p1, p2, p3)); } function log(address p0, string memory p1, string memory p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,string,string,bool)", p0, p1, p2, p3)); } function log(address p0, string memory p1, string memory p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,string,string,address)", p0, p1, p2, p3)); } function log(address p0, string memory p1, bool p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,string,bool,uint256)", p0, p1, p2, p3)); } function log(address p0, string memory p1, bool p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,string,bool,string)", p0, p1, p2, p3)); } function log(address p0, string memory p1, bool p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,string,bool,bool)", p0, p1, p2, p3)); } function log(address p0, string memory p1, bool p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,string,bool,address)", p0, p1, p2, p3)); } function log(address p0, string memory p1, address p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,string,address,uint256)", p0, p1, p2, p3)); } function log(address p0, string memory p1, address p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,string,address,string)", p0, p1, p2, p3)); } function log(address p0, string memory p1, address p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,string,address,bool)", p0, p1, p2, p3)); } function log(address p0, string memory p1, address p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,string,address,address)", p0, p1, p2, p3)); } function log(address p0, bool p1, uint256 p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,bool,uint256,uint256)", p0, p1, p2, p3)); } function log(address p0, bool p1, uint256 p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,bool,uint256,string)", p0, p1, p2, p3)); } function log(address p0, bool p1, uint256 p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,bool,uint256,bool)", p0, p1, p2, p3)); } function log(address p0, bool p1, uint256 p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,bool,uint256,address)", p0, p1, p2, p3)); } function log(address p0, bool p1, string memory p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,bool,string,uint256)", p0, p1, p2, p3)); } function log(address p0, bool p1, string memory p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,bool,string,string)", p0, p1, p2, p3)); } function log(address p0, bool p1, string memory p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,bool,string,bool)", p0, p1, p2, p3)); } function log(address p0, bool p1, string memory p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,bool,string,address)", p0, p1, p2, p3)); } function log(address p0, bool p1, bool p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,bool,bool,uint256)", p0, p1, p2, p3)); } function log(address p0, bool p1, bool p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,bool,bool,string)", p0, p1, p2, p3)); } function log(address p0, bool p1, bool p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,bool,bool,bool)", p0, p1, p2, p3)); } function log(address p0, bool p1, bool p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,bool,bool,address)", p0, p1, p2, p3)); } function log(address p0, bool p1, address p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,bool,address,uint256)", p0, p1, p2, p3)); } function log(address p0, bool p1, address p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,bool,address,string)", p0, p1, p2, p3)); } function log(address p0, bool p1, address p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,bool,address,bool)", p0, p1, p2, p3)); } function log(address p0, bool p1, address p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,bool,address,address)", p0, p1, p2, p3)); } function log(address p0, address p1, uint256 p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,address,uint256,uint256)", p0, p1, p2, p3)); } function log(address p0, address p1, uint256 p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,address,uint256,string)", p0, p1, p2, p3)); } function log(address p0, address p1, uint256 p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,address,uint256,bool)", p0, p1, p2, p3)); } function log(address p0, address p1, uint256 p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,address,uint256,address)", p0, p1, p2, p3)); } function log(address p0, address p1, string memory p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,address,string,uint256)", p0, p1, p2, p3)); } function log(address p0, address p1, string memory p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,address,string,string)", p0, p1, p2, p3)); } function log(address p0, address p1, string memory p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,address,string,bool)", p0, p1, p2, p3)); } function log(address p0, address p1, string memory p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,address,string,address)", p0, p1, p2, p3)); } function log(address p0, address p1, bool p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,address,bool,uint256)", p0, p1, p2, p3)); } function log(address p0, address p1, bool p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,address,bool,string)", p0, p1, p2, p3)); } function log(address p0, address p1, bool p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,address,bool,bool)", p0, p1, p2, p3)); } function log(address p0, address p1, bool p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,address,bool,address)", p0, p1, p2, p3)); } function log(address p0, address p1, address p2, uint256 p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,address,address,uint256)", p0, p1, p2, p3)); } function log(address p0, address p1, address p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,address,address,string)", p0, p1, p2, p3)); } function log(address p0, address p1, address p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,address,address,bool)", p0, p1, p2, p3)); } function log(address p0, address p1, address p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,address,address,address)", p0, p1, p2, p3)); } }lib/mangrove-core/src/IMangrove.sol
// SPDX-License-Identifier: MIT // This file must be kept up-to-date with the actual Mangrove interface. pragma solidity >=0.7.0 <0.9.0; import "@mgv/src/core/MgvLib.sol"; ///@title Interface for the Mangrove contract. interface IMangrove is HasMgvEvents { // # Permit functions ///@notice See {IERC20Permit-DOMAIN_SEPARATOR}. ///@return the domain separator. function DOMAIN_SEPARATOR() external view returns (bytes32); ///@notice See {IERC20Permit-PERMIT_TYPEHASH}. ///@return The permit type hash. function PERMIT_TYPEHASH() external pure returns (bytes32); ///@notice Approves the spender to spend the amount of tokens on behalf of the caller. ///@param outbound_tkn The address of the (maker) outbound token. ///@param inbound_tkn The address of the (maker) inbound token. ///@param spender The address of the spender. ///@param value The amount of tokens to approve. ///@return true If the approval succeeded; always true. function approve(address outbound_tkn, address inbound_tkn, address spender, uint value) external returns (bool); ///@notice Returns the allowance of the spender to spend tokens on behalf of the owner. ///@param outbound_tkn The address of the (maker) outbound token. ///@param inbound_tkn The address of the (maker) inbound token. ///@param owner The address of the owner. ///@param spender The address of the spender. ///@return amount The amount of tokens the spender is allowed to spend on behalf of the owner. function allowance(address outbound_tkn, address inbound_tkn, address owner, address spender) external view returns (uint amount); ///@notice Adapted from [Uniswap v2 contract](https://github.com/Uniswap/uniswap-v2-core/blob/55ae25109b7918565867e5c39f1e84b7edd19b2a/contracts/UniswapV2ERC20.sol#L81) ///@param outbound_tkn The address of the (maker) outbound token. ///@param inbound_tkn The address of the (maker) inbound token. ///@param owner The address of the owner. ///@param spender The address of the spender. ///@param value The amount of tokens to approve. ///@param deadline The deadline after which the permit is no longer valid. ///@param v The signature v parameter. ///@param r The signature r parameter. ///@param s The signature s parameter. function permit( address outbound_tkn, address inbound_tkn, address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s ) external; ///@notice See {IERC20Permit-nonces}. ///@param owner The address of the owner. ///@return nonce The current nonce of the owner. function nonces(address owner) external view returns (uint nonce); // # Taker functions ///@notice Performs a market order on a specified offer list taking offers up to a limit price. ///@param olKey The offer list key given by (maker) `outbound_tkn`, (maker) `inbound_tkn`, and `tickSpacing`. ///@param maxTick Must be `>= MIN_TICK` and `<= MAX_TICK`. The log of the limit price the taker is ready to pay (meaning: the log base 1.0001 of the ratio of inbound tokens over outbound tokens). ///@param fillVolume Must be `<= MAX_SAFE_VOLUME`. If `fillWants` is true, the amount of `olKey.outbound_tkn` the taker wants to buy; otherwise, the amount of `olKey.inbound_tkn` the taker wants to sell. ///@param fillWants If true, the matching engine tries to get the taker all they want; otherwise, the matching engine tries to sell all that the taker gives. In both cases subject to the price limit. ///@return takerGot The amount of `olKey.outbound_tkn` the taker got. ///@return takerGave The amount of `olKey.inbound_tkn` the taker gave. ///@return bounty The amount of native token the taker got as a bounty due to failing offers (in wei). ///@return feePaid The amount of `olKey.outbound_tkn` the taker paid as a fee to Mangrove. ///@dev The market order stops when there are no more offers at or below `maxTick`, when the end of the book has been reached, or: ///@dev - If `fillWants` is true, the market order stops when `fillVolume` units of `olKey.outbound_tkn` have been obtained. To buy a specific volume of `olKey.outbound_tkn` at any price, set `fillWants` to true, set `fillVolume` to the volume you want to buy, and set `maxTick` to the `MAX_TICK` constant. ///@dev - If `fillWants` is false, the market order stops when `fillVolume` units of `olKey.inbound_tkn` have been paid. To sell a specific volume of `olKey.inbound_tkn` at any price, set `fillWants` to false, set `fillVolume` to the volume you want to sell, and set `maxTick` to the `MAX_TICK` constant. function marketOrderByTick(OLKey memory olKey, Tick maxTick, uint fillVolume, bool fillWants) external returns (uint takerGot, uint takerGave, uint bounty, uint feePaid); ///@notice Performs a market order on a specified offer list taking offers up to a limit price, while allowing to specify a custom `maxGasreqForFailingOffers`. ///@param olKey The offer list key given by (maker) `outbound_tkn`, (maker) `inbound_tkn`, and `tickSpacing`. ///@param maxTick Must be `>= MIN_TICK` and `<= MAX_TICK`. The log of the limit price the taker is ready to pay (meaning: the log base 1.0001 of the ratio of inbound tokens over outbound tokens). ///@param fillVolume Must be `<= MAX_SAFE_VOLUME`. If `fillWants` is true, the amount of `olKey.outbound_tkn` the taker wants to buy; otherwise, the amount of `olKey.inbound_tkn` the taker wants to sell. ///@param fillWants If true, the matching engine tries to get the taker all they want; otherwise, the matching engine tries to sell all that the taker gives. In both cases subject to the price limit. ///@param maxGasreqForFailingOffers The maximum allowed gas required for failing offers (in wei). ///@return takerGot The amount of `olKey.outbound_tkn` the taker got. ///@return takerGave The amount of `olKey.inbound_tkn` the taker gave. ///@return bounty The amount of native token the taker got as a bounty due to failing offers (in wei). ///@return feePaid The amount of `olKey.outbound_tkn` the taker paid as a fee to Mangrove. ///@dev Mangrove stops a market order after it has gone through failing offers such that their cumulative `gasreq` is greater than the global `maxGasreqForFailingOffers` parameter. This function can be used by the taker to override the default `maxGasreqForFailingOffers` parameter. function marketOrderByTickCustom( OLKey memory olKey, Tick maxTick, uint fillVolume, bool fillWants, uint maxGasreqForFailingOffers ) external returns (uint takerGot, uint takerGave, uint bounty, uint feePaid); ///@notice Performs a market order on a specified offer list taking offers up to a limit price defined by a ratio `inbound_tkn/outbound_tkn` of volumes. ///@param olKey The offer list key given by (maker) `outbound_tkn`, (maker) `inbound_tkn`, and `tickSpacing`. ///@param takerWants Must be `<= MAX_SAFE_VOLUME`. The amount the taker wants. This is used along with `takerGives` to derive a max price (`maxTick`) which is the lowest allowed tick in the offer list such that `log_1.0001(takerGives/takerWants) <= maxTick`. ///@param takerGives Must be `<= MAX_SAFE_VOLUME`. The amount the taker gives. This is used along with `takerWants` to derive a max price (`maxTick`) which is the lowest allowed tick in the offer list such that `log_1.0001(takerGives/takerWants) <= maxTick`. ///@param fillWants If true, the matching engine tries to get the taker all they want; otherwise, the matching engine tries to sell all that the taker gives. In both cases subject to the price limit. ///@return takerGot The amount of `olKey.outbound_tkn` the taker got. ///@return takerGave The amount of `olKey.inbound_tkn` the taker gave. ///@return bounty The amount of native token the taker got as a bounty due to failing offers (in wei). ///@return feePaid The amount of `olKey.outbound_tkn` the taker paid as a fee to Mangrove. ///@dev This function is just a wrapper for `marketOrderByTick`, see that function for details. ///@dev When deriving the tick, then `takerWants = 0` has a special meaning and the tick for the highest possible ratio between wants and gives will be used, ///@dev and if `takerGives = 0` and `takerWants != 0`, then the tick for the lowest possible ratio will be used. function marketOrderByVolume(OLKey memory olKey, uint takerWants, uint takerGives, bool fillWants) external returns (uint takerGot, uint takerGave, uint bounty, uint feePaid); ///@notice Performs a market order on a specified offer list taking offers up to a limit price for a specified taker. ///@param olKey The offer list key given by (maker) `outbound_tkn`, (maker) `inbound_tkn`, and `tickSpacing`. ///@param maxTick Must be `>= MIN_TICK` and `<= MAX_TICK`. The log of the limit price the taker is ready to pay (meaning: the log base 1.0001 of the ratio of inbound tokens over outbound tokens). ///@param fillVolume Must be `<= MAX_SAFE_VOLUME`. If `fillWants` is true, the amount of `olKey.outbound_tkn` the taker wants to buy; otherwise, the amount of `olKey.inbound_tkn` the taker wants to sell. ///@param fillWants If true, the matching engine tries to get the taker all they want; otherwise, the matching engine tries to sell all that the taker gives. In both cases subject to the price limit. ///@param taker The taker from which amounts will be transferred from and to. If the `msg.sender`'s allowance for the given `olKey.outbound_tkn`,`olKey.inbound_tkn` is strictly less than the total amount eventually spent by `taker`, the call will fail. ///@return takerGot The amount of `olKey.outbound_tkn` the taker got. ///@return takerGave The amount of `olKey.inbound_tkn` the taker gave. ///@return bounty The amount of native token the taker got as a bounty due to failing offers (in wei). ///@return feePaid The amount of `olKey.outbound_tkn` the taker paid as a fee to Mangrove. ///@dev The `bounty` will be sent to `msg.sender` but transfers will be for `taker`. Requires prior permission. ///@dev See also `marketOrderByTick`. function marketOrderForByTick(OLKey memory olKey, Tick maxTick, uint fillVolume, bool fillWants, address taker) external returns (uint takerGot, uint takerGave, uint bounty, uint feePaid); ///@notice Performs a market order on a specified offer list taking offers up to a limit price defined by a ratio `inbound_tkn/outbound_tkn` of volumes for a specified taker. ///@param olKey The offer list key given by (maker) `outbound_tkn`, (maker) `inbound_tkn`, and `tickSpacing`. ///@param takerWants Must be `<= MAX_SAFE_VOLUME`. The amount the taker wants. This is used along with `takerGives` to derive a max price (`maxTick`) which is the lowest allowed tick in the offer list such that `log_1.0001(takerGives/takerWants) <= maxTick`. ///@param takerGives Must be `<= MAX_SAFE_VOLUME`. The amount the taker gives. This is used along with `takerGives` to derive a max price (`maxTick`) which is the lowest allowed tick in the offer list such that `log_1.0001(takerGives/takerWants) <= maxTick`. ///@param fillWants If true, the matching engine tries to get the taker all they want; otherwise, the matching engine tries to sell all that the taker gives. In both cases subject to the price limit. ///@param taker The taker from which amounts will be transferred from and to the. If the `msg.sender`'s allowance for the given `olKey.outbound_tkn`,`olKey.inbound_tkn` are strictly less than the total amount eventually spent by `taker`, the call will fail. ///@return takerGot The amount of `olKey.outbound_tkn` the taker got. ///@return takerGave The amount of `olKey.inbound_tkn` the taker gave. ///@return bounty The amount of native token the taker got as a bounty due to failing offers (in wei). ///@return feePaid The amount of native token the taker paid as a fee (in wei of `olKey.outbound_tkn`). ///@dev The `bounty` will be send to `msg.sender` but transfers will be for `taker`. Requires prior permission. ///@dev See also `marketOrderByVolume`. function marketOrderForByVolume(OLKey memory olKey, uint takerWants, uint takerGives, bool fillWants, address taker) external returns (uint takerGot, uint takerGave, uint bounty, uint feePaid); // # Cleaning functions /* # Cleaning */ ///@notice Cleans multiple offers, i.e. executes them and removes them from the book if they fail, transferring the failure penalty as bounty to the caller. ///@param olKey The offer list key given by (maker) `outbound_tkn`, (maker) `inbound_tkn`, and `tickSpacing`. ///@param targets The offers to clean, identified by their (`offerId, tick, gasreq, takerWants`) that will make them fail. ///@param taker The taker used for transfers (should be able to deliver token amounts). ///@return successes The number of successfully cleaned offers. ///@return bounty The total bounty received by the caller. ///@dev If an offer succeeds, the execution of that offer is reverted, it stays in the book, and no bounty is paid; The `cleanByImpersonation` function itself will not revert. ///@dev Note that Mangrove won't attempt to execute an offer if the values in a target don't match its offer. To distinguish between a non-executed clean and a failed clean (due to the offer itself not failing), you must inspect the log (see `MgvLib.sol`) or check the received bounty. ///@dev Any `taker` can be impersonated when cleaning because: ///@dev - The function reverts if the offer succeeds, reverting any token transfers. ///@dev - After a `clean` where the offer has failed, all ERC20 token transfers have also been reverted -- but the sender will still have received the bounty of the failing offers. */ function cleanByImpersonation(OLKey memory olKey, MgvLib.CleanTarget[] calldata targets, address taker) external returns (uint successes, uint bounty); // # Maker functions ///@notice Adds funds to Mangrove for the caller (the maker) to use for provisioning offers. receive() external payable; ///@notice Adds funds to Mangrove for the caller (the maker) to use for provisioning offers. function fund() external payable; ///@notice Adds funds to Mangrove for the maker to use for provisioning offers. ///@param maker The maker to add funds for. function fund(address maker) external payable; ///@notice Withdraws the caller's (the maker's) free native tokens (funds for provisioning offers not locked by an offer) by transferring them to the caller. ///@param amount The amount to withdraw. ///@return noRevert Whether the transfer succeeded. function withdraw(uint amount) external returns (bool noRevert); ///@notice Gets the maker's free balance of native tokens (funds for provisioning offers not locked by an offer). ///@param maker The maker to get the balance for. ///@return balance The maker's free balance of native tokens (funds for provisioning offers not locked by an offer). function balanceOf(address maker) external view returns (uint balance); ///@notice Creates a new offer on Mangrove, where the caller is the maker. The maker can implement the `IMaker` interface to be called during offer execution. ///@param olKey The offer list key given by (maker) `outbound_tkn`, (maker) `inbound_tkn`, and `tickSpacing`. ///@param tick Must be `>= MIN_TICK` and `<= MAX_TICK`. The `tick` induces a price which is `1.0001^tick`. The actual tick of the offer will be the smallest tick `offerTick > tick` that satisfies `offerTick % tickSpacing == 0`. ///@param gives Must be `<= MAX_SAFE_VOLUME`. The amount of `olKey.outbound_tkn` the maker gives. ///@param gasreq The amount of gas required to execute the offer logic in the maker's `IMaker` implementation. This will limit the gas available, and the offer will fail if it spends more. ///@param gasprice The maximum gas price the maker is willing to pay a penalty for due to failing execution. ///@return offerId The id of the offer on Mangrove. Can be used to retract or update the offer (even to reuse a taken offer). ///@dev The gasreq and gasprice are used to derive the provision which will be used to pay a penalty if the offer fails. ///@dev This function is payable to enable delivery of the provision along with the offer creation. function newOfferByTick(OLKey memory olKey, Tick tick, uint gives, uint gasreq, uint gasprice) external payable returns (uint offerId); ///@notice Creates a new offer on Mangrove, where the caller is the maker. The maker can implement the `IMaker` interface to be called during offer execution. ///@param olKey The offer list key given by (maker) `outbound_tkn`, (maker) `inbound_tkn`, and `tickSpacing`. ///@param wants Must be less than MAX_SAFE_VOLUME. The amount of `olKey.inbound_tkn` the maker wants. This is used along with `gives` to derive a tick (price). which is the lowest allowed tick in the offer list such that `log_1.0001(takerGives/takerWants) <= maxTick`. ///@param gives Must be less than MAX_SAFE_VOLUME. The amount of `olKey.outbound_tkn` the maker gives. This is used along with `wants` to derive a tick (price). which is the lowest allowed tick in the offer list such that `log_1.0001(takerGives/takerWants) <= maxTick`. Must be less than MAX_SAFE_VOLUME. ///@param gasreq The amount of gas required to execute the offer logic in the maker's `IMaker` implementation. This will limit the gas available, and the offer will fail if it spends more. ///@param gasprice The maximum gas price the maker is willing to pay a penalty for due to failing execution. ///@return offerId The id of the offer on Mangrove. Can be used to retract or update the offer (even to reuse a taken offer). ///@dev This function is just a wrapper for `newOfferByTick`, see that function for details. function newOfferByVolume(OLKey memory olKey, uint wants, uint gives, uint gasreq, uint gasprice) external payable returns (uint offerId); ///@notice Updates an existing offer on Mangrove, where the caller is the maker. ///@param olKey The offer list key given by (maker) `outbound_tkn`, (maker) `inbound_tkn`, and `tickSpacing`. ///@param tick Must be `>= MIN_TICK` and `<= MAX_TICK`. The `tick` induces a price which is `1.0001^tick`. The actual tick of the offer will be the smallest tick `offerTick > tick` that satisfies `offerTick % tickSpacing == 0`. ///@param gives The amount of `olKey.outbound_tkn` the maker gives. Must be less than MAX_SAFE_VOLUME. ///@param gasreq The amount of gas required to execute the offer logic in the maker's `IMaker` implementation. ///@param gasprice The maximum gas price the maker is willing to pay a penalty for due to failing execution. ///@param offerId The id of the offer on Mangrove. ///@dev See `newOfferByTick` for additional details. function updateOfferByTick(OLKey memory olKey, Tick tick, uint gives, uint gasreq, uint gasprice, uint offerId) external payable; ///@notice Updates an existing, owned offer on Mangrove, where the caller is the maker. ///@param olKey The offer list key given by (maker) `outbound_tkn`, (maker) `inbound_tkn`, and `tickSpacing`. ///@param wants The amount of `olKey.inbound_tkn` the maker wants. This is used along with `gives` to derive a tick (price). which is the lowest allowed tick in the offer list such that `log_1.0001(takerGives/takerWants) <= maxTick`. ///@param gives The amount of `olKey.outbound_tkn` the maker gives. This is used along with `wants` to derive a tick (price). which is the lowest allowed tick in the offer list such that `log_1.0001(takerGives/takerWants) <= maxTick`. Must be less than MAX_SAFE_VOLUME. ///@param gasreq The amount of gas required to execute the offer logic in the maker's `IMaker` implementation. ///@param gasprice The maximum gas price the maker is willing to pay a penalty for due to failing execution. ///@param offerId The id of the offer on Mangrove. ///@dev This function is just a wrapper for `updateOfferByTick`, see that function for details. function updateOfferByVolume(OLKey memory olKey, uint wants, uint gives, uint gasreq, uint gasprice, uint offerId) external payable; ///@notice Retracts an offer from Mangrove, where the caller is the maker. ///@param olKey The offer list key given by (maker) `outbound_tkn`, (maker) `inbound_tkn`, and `tickSpacing`. ///@param offerId The id of the offer on Mangrove. ///@param deprovision Whether to deprovision the offer, i.e, return the provision to the maker's balance on Mangrove. ///@return provision The amount of native token deprovisioned for the offer (in wei). ///@dev `withdraw` can be used to withdraw the funds after deprovisioning. ///@dev Leaving funds provisioned can be used to save gas if offer is later updated. function retractOffer(OLKey memory olKey, uint offerId, bool deprovision) external returns (uint provision); // # Global config view functions ///@notice Gets the global configuration for Mangrove. ///@return _global The global configuration for Mangrove. function global() external view returns (Global _global); // # Offer list view functions ///@notice Gets the local configuration for a specific offer list. ///@param olKey The offer list key given by (maker) `outbound_tkn`, (maker) `inbound_tkn`, and `tickSpacing`. ///@return _local The local configuration for the offer list. function local(OLKey memory olKey) external view returns (Local _local); ///@notice Gets the global configuration for Mangrove and local the configuration for a specific offer list. ///@param olKey The offer list key given by (maker) `outbound_tkn`, (maker) `inbound_tkn`, and `tickSpacing`. ///@return _global The global configuration for Mangrove. ///@return _local The local configuration for the offer list. function config(OLKey memory olKey) external view returns (Global _global, Local _local); ///@notice Determines whether the reentrancy lock is in effect for the offer list. ///@param olKey The offer list key given by (maker) `outbound_tkn`, (maker) `inbound_tkn`, and `tickSpacing`. ///@return true If locked; otherwise, false. ///@dev The lock protects modifying or inspecting the offer list while an order is in progress. function locked(OLKey memory olKey) external view returns (bool); ///@notice Gets the `offerId` of the best offer in the offer list. ///@param olKey The offer list key given by (maker) `outbound_tkn`, (maker) `inbound_tkn`, and `tickSpacing`. ///@return offerId The `offerId` of the best offer on the offer list. function best(OLKey memory olKey) external view returns (uint offerId); ///@notice Gets the offer list key with the given hash (if the offer list key has been activated at least once). ///@param olKeyHash The hash of the offer list key. ///@return olKey The olKey. function olKeys(bytes32 olKeyHash) external view returns (OLKey memory olKey); // # Offer view functions ///@notice Gets an offer in packed format. ///@param olKey The offer list key given by (maker) `outbound_tkn`, (maker) `inbound_tkn`, and `tickSpacing`. ///@param offerId The `offerId` of the offer on the offer list. ///@return offer The offer in packed format. function offers(OLKey memory olKey, uint offerId) external view returns (Offer offer); ///@notice Gets an offer's details in packed format. ///@param olKey The offer list key given by (maker) `outbound_tkn`, (maker) `inbound_tkn`, and `tickSpacing`. ///@param offerId The `offerId` of the offer on the offer list. ///@return offerDetail The offer details in packed format. function offerDetails(OLKey memory olKey, uint offerId) external view returns (OfferDetail offerDetail); ///@notice Gets both an offer and its details in packed format. ///@param olKey The offer list key given by (maker) `outbound_tkn`, (maker) `inbound_tkn`, and `tickSpacing`. ///@param offerId The `offerId` of the offer on the offer list. ///@return offer The offer in packed format. ///@return offerDetail The offer details in packed format. function offerData(OLKey memory olKey, uint offerId) external view returns (Offer offer, OfferDetail offerDetail); // # Governance functions ///@notice Gets the governance address. ///@return The governance address. function governance() external view returns (address); ///@notice Activates an offer list. ///@param olKey The offer list key given by (maker) `outbound_tkn`, (maker) `inbound_tkn`, and `tickSpacing`. ///@param fee In basis points, of `olKey.outbound_tkn` given to the taker. This fee is sent to Mangrove. Fee is capped to ~2.5%. ///@param density96X32 The density of the offer list used to define a minimum offer volume. See `setDensity96X32`. ///@param offer_gasbase The gasbase of the offer list used to define a minimum provision necessary for offers. See `setGasbase`. ///@dev If the flipped offer list is active then the offer lists are expected to have the same `tickSpacing`. function activate(OLKey memory olKey, uint fee, uint density96X32, uint offer_gasbase) external; ///@notice Deactivates an offer list. ///@param olKey The offer list key given by (maker) `outbound_tkn`, (maker) `inbound_tkn`, and `tickSpacing`. function deactivate(OLKey memory olKey) external; ///@notice Kills the Mangrove instance. A dead instance cannot have offers executed or funds received, but offers can be retracted and funds can be withdrawn. function kill() external; ///@notice Sets the density. ///@param olKey The offer list key given by (maker) `outbound_tkn`, (maker) `inbound_tkn`, and `tickSpacing`. ///@param density96X32 Is given as a 96.32 fixed point number. It will be stored as a 9-bit float and be approximated towards 0. The maximum error is 20%. See `DensityLib` for more information. ///@dev Useless if `global.useOracle != 0` and oracle returns a valid density. function setDensity96X32(OLKey memory olKey, uint density96X32) external; ///@notice Sets the fee for the offer list. ///@param olKey The offer list key given by (maker) `outbound_tkn`, (maker) `inbound_tkn`, and `tickSpacing`. ///@param fee In basis points, of `olKey.outbound_tkn` given to the taker. This fee is sent to Mangrove. Fee is capped to ~2.5%. function setFee(OLKey memory olKey, uint fee) external; ///@notice Sets the gasbase for the offer list. ///@param olKey The offer list key given by (maker) `outbound_tkn`, (maker) `inbound_tkn`, and `tickSpacing`. ///@param offer_gasbase The gasbase of the offer list used to define a minimum provision necessary for offers. Represents the gas overhead used by processing the offer inside Mangrove + the overhead of initiating an entire order. Stored in thousands in a maximum of 9 bits. function setGasbase(OLKey memory olKey, uint offer_gasbase) external; ///@notice Sets the gasmax for Mangrove, the maximum amount of gas an offer can require to execute. ///@param gasmax The maximum amount of gas required to execute an offer. Must fit in 24 bits. function setGasmax(uint gasmax) external; ///@notice Sets the maximum number of times a market order can recursively execute offers. This is a protection against stack overflows. ///@param maxRecursionDepth The maximum number of times a market order can recursively execute offers. function setMaxRecursionDepth(uint maxRecursionDepth) external; ///@notice Sets the maximum cumulative `gasreq` for failing offers during a market order before doing a partial fill. ///@param maxGasreqForFailingOffers The maximum cumulative `gasreq` for failing offers during a market order before doing a partial fill. 32 bits. function setMaxGasreqForFailingOffers(uint maxGasreqForFailingOffers) external; ///@notice Sets the gasprice (in Mwei, 26 bits). ///@param gasprice The gasprice (in Mwei, 26 bits). function setGasprice(uint gasprice) external; ///@notice Sets a new governance address. ///@param governanceAddress The new governance address. function setGovernance(address governanceAddress) external; ///@notice Sets the monitor/oracle. The `monitor/oracle` can provide real-time values for `gasprice` and `density` to Mangrove. It can also receive liquidity event notifications. ///@param monitor The new monitor/oracle address. function setMonitor(address monitor) external; ///@notice Sets whether Mangrove notifies the Monitor when and offer is taken. ///@param notify Whether Mangrove notifies the Monitor when and offer is taken. function setNotify(bool notify) external; ///@notice Sets whether Mangrove uses the monitor as oracle for `gasprice` and `density` values. ///@param useOracle Whether Mangrove uses the monitor as oracle for `gasprice` and `density` values. function setUseOracle(bool useOracle) external; ///@notice Transfer ERC20 tokens to governance. ///@param tokenAddress The address of the ERC20 token. ///@param value The amount of tokens to transfer. function withdrawERC20(address tokenAddress, uint value) external; // # Tick tree view functions ///@notice Gets a leaf. ///@param olKey The offer list key given by (maker) `outbound_tkn`, (maker) `inbound_tkn`, and `tickSpacing`. ///@param index The index. ///@return The leaf. function leafs(OLKey memory olKey, int index) external view returns (Leaf); ///@notice Gets a level 3 field. ///@param olKey The offer list key given by (maker) `outbound_tkn`, (maker) `inbound_tkn`, and `tickSpacing`. ///@param index The index. ///@return The field. function level3s(OLKey memory olKey, int index) external view returns (Field); ///@notice Gets a level 2 field. ///@param olKey The offer list key given by (maker) `outbound_tkn`, (maker) `inbound_tkn`, and `tickSpacing`. ///@param index The index. ///@return The field. function level2s(OLKey memory olKey, int index) external view returns (Field); ///@notice Gets a level 1 field. ///@param olKey The offer list key given by (maker) `outbound_tkn`, (maker) `inbound_tkn`, and `tickSpacing`. ///@param index The index. ///@return Yhe field. function level1s(OLKey memory olKey, int index) external view returns (Field); ///@notice Gets the root from local. ///@param olKey The offer list key given by (maker) `outbound_tkn`, (maker) `inbound_tkn`, and `tickSpacing`. ///@return The root. function root(OLKey memory olKey) external view returns (Field); // # Internal functions ///@notice Internal function used to flashloan tokens from taker to maker, for maker to source the promised liquidity. ///@param sor Data about an order-offer match. ///@param taker The taker. ///@return gasused The amount of gas used for `makerExecute`. ///@return makerData The data returned by `makerExecute`. ///@dev Not to be called externally - only external to be able to revert. function flashloan(MgvLib.SingleOrder memory sor, address taker) external returns (uint gasused, bytes32 makerData); ///@notice Internal function used to clean failing offers. ///@param olKey The offer list key given by (maker) `outbound_tkn`, (maker) `inbound_tkn`, and `tickSpacing`. ///@param offerId The id of the offer on Mangrove. ///@param tick Must be `>= MIN_TICK` and `<= MAX_TICK`. The `tick` induces a price which is `1.0001^tick`. The actual tick of the offer will be the smallest tick `offerTick > tick` that satisfies `offerTick % tickSpacing == 0`. ///@param gasreq The gas required for the offer. ///@param takerWants Must be `<= MAX_SAFE_VOLUME`. The amount of `olKey.outbound_tkn` the taker wants. ///@param taker The taker used for transfers (should be able to deliver token amounts). ///@return bounty The bounty paid. ///@dev Not to be called externally - only external to be able to revert. function internalCleanByImpersonation( OLKey memory olKey, uint offerId, Tick tick, uint gasreq, uint takerWants, address taker ) external returns (uint bounty); ///@notice Fall back function (forwards calls to `MgvAppendix`). ///@param callData The call data. ///@return The result. fallback(bytes calldata callData) external returns (bytes memory); }lib/mangrove-strats/src/strategies/offer_maker/market_making/kandel/abstract/CoreKandel.sol
// SPDX-License-Identifier: MIT pragma solidity ^0.8.10; import {MgvLib, OLKey, Offer} from "@mgv/src/core/MgvLib.sol"; import {IMangrove} from "@mgv/src/IMangrove.sol"; import {IERC20} from "@mgv/lib/IERC20.sol"; import {OfferType} from "./TradesBaseQuotePair.sol"; import {DirectWithBidsAndAsksDistribution} from "./DirectWithBidsAndAsksDistribution.sol"; import {TradesBaseQuotePair} from "./TradesBaseQuotePair.sol"; import {TransferLib} from "@mgv/lib/TransferLib.sol"; import {KandelLib} from "./KandelLib.sol"; import {MAX_SAFE_VOLUME} from "@mgv/lib/core/Constants.sol"; ///@title the core of Kandel strategies which creates or updates a dual offer whenever an offer is taken. ///@notice `CoreKandel` is agnostic to the chosen price distribution. abstract contract CoreKandel is DirectWithBidsAndAsksDistribution, TradesBaseQuotePair { ///@notice the gasprice has been set. ///@param value the gasprice for offers. ///@notice By emitting this data, an indexer will be able to keep track of what gasprice is used. event SetGasprice(uint value); ///@notice the gasreq has been set. ///@param value the gasreq (including router's gasreq) for offers ///@notice By emitting this data, an indexer will be able to keep track of what gasreq is used. event SetGasreq(uint value); ///@notice the step size has been set. ///@param value the step size in amount of price points to jump for posting dual offer event SetStepSize(uint value); ///@notice the Kandel instance is credited of `amount` by its owner. ///@param token the asset. This is indexed so that RPC calls can filter on it. ///@param amount the amount. ///@notice By emitting this data, an indexer will be able to keep track of what credits are made. event Credit(IERC20 indexed token, uint amount); ///@notice the Kandel instance is debited of `amount` by its owner. ///@param token the asset. This is indexed so that RPC calls can filter on it. ///@param amount the amount. ///@notice By emitting this data, an indexer will be able to keep track of what debits are made. event Debit(IERC20 indexed token, uint amount); ///@notice Core Kandel parameters ///@param gasprice the gasprice to use for offers. Must hold on 26 bits. ///@param gasreq the gasreq to use for offers ///@param stepSize in amount of price points to jump for posting dual offer. ///@param pricePoints the number of price points for the Kandel instance. struct Params { uint32 gasprice; uint24 gasreq; uint32 stepSize; uint32 pricePoints; } ///@notice Storage of the parameters for the strat. Params public params; ///@notice sets the step size ///@param stepSize the step size. function setStepSize(uint stepSize) public onlyAdmin { uint32 stepSize_ = uint32(stepSize); require(stepSize > 0, "Kandel/stepSizeTooLow"); require(stepSize_ == stepSize && stepSize < params.pricePoints, "Kandel/stepSizeTooHigh"); params.stepSize = stepSize_; emit SetStepSize(stepSize); } ///@notice sets the gasprice for offers ///@param gasprice the gasprice. function setGasprice(uint gasprice) public onlyAdmin { require(gasprice < 1 << 26, "Kandel/gaspriceTooHigh"); params.gasprice = uint32(gasprice); emit SetGasprice(gasprice); } ///@notice sets the gasreq (including router's gasreq) for offers ///@param gasreq the gasreq. function setGasreq(uint gasreq) public onlyAdmin { uint24 gasreq_ = uint24(gasreq); require(gasreq_ == gasreq, "Kandel/gasreqTooHigh"); params.gasreq = gasreq_; emit SetGasreq(gasreq_); } /// @notice Updates the params to new values. /// @param newParams the new params to set. function setParams(Params calldata newParams) internal { Params memory oldParams = params; if (oldParams.pricePoints != newParams.pricePoints) { uint32 pricePoints_ = newParams.pricePoints; require(pricePoints_ >= 2, "Kandel/invalidPricePoints"); setLength(pricePoints_); params.pricePoints = pricePoints_; } if (oldParams.stepSize != newParams.stepSize) { setStepSize(newParams.stepSize); } if (newParams.gasprice != 0 && newParams.gasprice != oldParams.gasprice) { setGasprice(newParams.gasprice); } if (newParams.gasreq != 0 && newParams.gasreq != oldParams.gasreq) { setGasreq(newParams.gasreq); } } ///@notice Constructor ///@param mgv The Mangrove deployment. ///@param olKeyBaseQuote The OLKey for the outbound_tkn base and inbound_tkn quote offer list Kandel will act on, the flipped OLKey is used for the opposite offer list. ///@param routerParams parameters when using a router. constructor(IMangrove mgv, OLKey memory olKeyBaseQuote, RouterParams memory routerParams) TradesBaseQuotePair(olKeyBaseQuote) DirectWithBidsAndAsksDistribution(mgv, routerParams) {} ///@notice publishes bids/asks for the distribution in the `indices`. Care must be taken to publish offers in meaningful chunks. For Kandel an offer and its dual should be published in the same chunk (one being optionally initially dead). ///@param distribution the distribution of bids and asks to populate ///@param parameters the parameters for Kandel. Only changed parameters will cause updates. Set `gasreq` and `gasprice` to 0 to keep existing values. ///@param baseAmount base amount to deposit ///@param quoteAmount quote amount to deposit ///@dev This function is used at initialization and can fund with provision for the offers. ///@dev Use `populateChunk` to split up initialization or re-initialization with same parameters, as this function will emit. ///@dev If this function is invoked with different pricePoints or stepSize, then first retract all offers. ///@dev msg.value must be enough to provision all posted offers (for chunked initialization only one call needs to send native tokens). function populate(Distribution memory distribution, Params calldata parameters, uint baseAmount, uint quoteAmount) public payable onlyAdmin { if (msg.value > 0) { MGV.fund{value: msg.value}(); } setParams(parameters); depositFunds(baseAmount, quoteAmount); populateChunkInternal(distribution, params.gasreq, params.gasprice); } ///@notice Publishes bids/asks for the distribution in the `indices`. Care must be taken to publish offers in meaningful chunks. For Kandel an offer and its dual should be published in the same chunk (one being optionally initially dead). ///@notice This function is used externally after `populate` to reinitialize some indices or if multiple transactions are needed to split initialization due to gas cost. ///@notice This function is not payable, use `populate` to fund along with populate. ///@param distribution the distribution of bids and asks to populate function populateChunk(Distribution calldata distribution) external onlyAdmin { Params memory parameters = params; populateChunkInternal(distribution, parameters.gasreq, parameters.gasprice); } ///@notice the total balance available for the strat of the offered token for the given offer type. ///@param ba the offer type. ///@return balance the balance of the token. function reserveBalance(OfferType ba) public view virtual returns (uint balance) { IERC20 token = outboundOfOfferType(ba); return token.balanceOf(address(this)); } ///@notice takes care of status for updating dual and logging of potential issues. ///@param offerId the Mangrove offer id. ///@param args the arguments of the offer. ///@param updateOfferStatus the status returned from the `_updateOffer` function. function logUpdateOfferStatus(uint offerId, OfferArgs memory args, bytes32 updateOfferStatus) internal { if (updateOfferStatus == REPOST_SUCCESS || updateOfferStatus == "mgv/writeOffer/density/tooLow") { // Low density will mean some amount is not posted and will be available for withdrawal or later posting via populate. return; } emit LogIncident(args.olKey.hash(), offerId, "Kandel/updateOfferFailed", updateOfferStatus); } ///@notice update or create dual offer according to transport logic ///@param order is a recall of the taker order that is at the origin of the current trade. function transportSuccessfulOrder(MgvLib.SingleOrder calldata order) internal { OfferType ba = offerTypeOfOutbound(IERC20(order.olKey.outbound_tkn)); // adds any unpublished liquidity to pending[Base/Quote] // preparing arguments for the dual offer (uint offerId, OfferArgs memory args) = transportLogic(ba, order); // All offers are created up front (see populateChunk), so here we update to set new gives. bytes32 updateOfferStatus = _updateOffer(args, offerId); logUpdateOfferStatus(offerId, args, updateOfferStatus); } ///@notice transport logic followed by Kandel ///@param ba whether the offer that was executed is a bid or an ask ///@param order a recap of the taker order (order.offer is the executed offer) ///@return dualOfferId the offer id of the dual offer ///@return args the argument for updating an offer function transportLogic(OfferType ba, MgvLib.SingleOrder calldata order) internal virtual returns (uint dualOfferId, OfferArgs memory args) { uint index = indexOfOfferId(ba, order.offerId); Params memory memoryParams = params; OfferType baDual = dual(ba); uint dualIndex = KandelLib.transportDestination(baDual, index, memoryParams.stepSize, memoryParams.pricePoints); dualOfferId = offerIdOfIndex(baDual, dualIndex); args.olKey = offerListOfOfferType(baDual); Offer dualOffer = MGV.offers(args.olKey, dualOfferId); // gives from order.takerGives:127 dualOffer.gives():127, so args.gives:128 args.gives = order.takerGives + dualOffer.gives(); if (args.gives > MAX_SAFE_VOLUME) { // this should not be reached under normal circumstances unless strat is posting on top of an existing offer with an abnormal volume // to prevent gives to be too high, we let the surplus become "pending" (unpublished liquidity) args.gives = MAX_SAFE_VOLUME; // There is no similar limit to dualOffer.wants() for allowed ticks and gives. } // keep existing price of offer args.tick = dualOffer.tick(); // args.fund = 0; the offers are already provisioned // posthook should not fail if unable to post offers, we capture the error as incidents args.noRevert = true; // use newest gasreq and gasprice args.gasprice = memoryParams.gasprice; args.gasreq = memoryParams.gasreq; } /// @notice gets pending liquidity for base (ask) or quote (bid). Will be negative if funds are not enough to cover all offer's promises. /// @param ba offer type. /// @return the pending amount /// @dev Gas costly function, better suited for off chain calls. function pending(OfferType ba) external view returns (int) { return int(reserveBalance(ba)) - int(offeredVolume(ba)); } ///@notice Deposits funds to the contract's reserve ///@param baseAmount the amount of base tokens to deposit. ///@param quoteAmount the amount of quote tokens to deposit. function depositFunds(uint baseAmount, uint quoteAmount) public virtual { require(TransferLib.transferTokenFrom(BASE, msg.sender, address(this), baseAmount), "Kandel/baseTransferFail"); emit Credit(BASE, baseAmount); require(TransferLib.transferTokenFrom(QUOTE, msg.sender, address(this), quoteAmount), "Kandel/quoteTransferFail"); emit Credit(QUOTE, quoteAmount); } ///@notice withdraws funds from the contract's reserve ///@param baseAmount the amount of base tokens to withdraw. Use type(uint).max to denote the entire reserve balance. ///@param quoteAmount the amount of quote tokens to withdraw. Use type(uint).max to denote the entire reserve balance. ///@param recipient the address to which the withdrawn funds should be sent to. ///@dev it is up to the caller to make sure there are still enough funds for live offers. function withdrawFunds(uint baseAmount, uint quoteAmount, address recipient) public virtual onlyAdmin { withdrawFundsForToken(BASE, baseAmount, recipient); withdrawFundsForToken(QUOTE, quoteAmount, recipient); } ///@notice withdraws funds from the contract's reserve for the given token ///@param token the token to withdraw. ///@param amount the amount of tokens to withdraw. Use type(uint).max to denote the entire reserve balance. ///@param recipient the address to which the withdrawn funds should be sent to. function withdrawFundsForToken(IERC20 token, uint amount, address recipient) internal virtual { if (amount == type(uint).max) { amount = token.balanceOf(address(this)); } require(TransferLib.transferToken(token, recipient, amount), "Kandel/transferFail"); emit Debit(token, amount); } ///@notice Retracts offers, withdraws funds, and withdraws free wei from Mangrove. ///@param from retract offers starting from this index. ///@param to retract offers until this index. ///@param baseAmount the amount of base tokens to withdraw. Use type(uint).max to denote the entire reserve balance. ///@param quoteAmount the amount of quote tokens to withdraw. Use type(uint).max to denote the entire reserve balance. ///@param freeWei the amount of wei to withdraw from Mangrove. Use type(uint).max to withdraw entire available balance. ///@param recipient the recipient of the funds. function retractAndWithdraw( uint from, uint to, uint baseAmount, uint quoteAmount, uint freeWei, address payable recipient ) external onlyAdmin { retractOffers(from, to); withdrawFunds(baseAmount, quoteAmount, recipient); withdrawFromMangrove(freeWei, recipient); } }lib/mangrove-core/src/core/MgvLib.sol
// SPDX-License-Identifier: MIT /* `MgvLib` contains data structures returned by external calls to Mangrove and the interfaces it uses for its own external calls. */ pragma solidity ^0.8.10; import "@mgv/src/preprocessed/Structs.post.sol"; import {IERC20} from "@mgv/lib/IERC20.sol"; import {Density, DensityLib} from "@mgv/lib/core/DensityLib.sol"; import "@mgv/lib/core/TickTreeLib.sol"; import "@mgv/lib/core/TickLib.sol"; /* `OLKey` (for "OfferListKey") contains the information that characterizes an offer list: * `outbound_tkn`, token that goes from the maker to the taker (to remember the direction, imagine the token as going out of the Mangrove offer, towards the taker) * `inbound_tkn`, token that goes from the taker to the maker (to remember the direction, imagine the token as going into Mangrove from the taker) * `tickSpacing`, how many ticks should be jumped between available price points. More volatile outbound/inbound pairs should have a larger `tickSpacing`. */ struct OLKey { address outbound_tkn; address inbound_tkn; uint tickSpacing; } /* Globally enable `olKey.method(...)` */ using OLLib for OLKey global; library OLLib { /* The id of an `OLKey` should be `keccak256(abi.encode(olKey))` To save gas, `id()` directly hashes the memory (which matches the ABI encoding; there is a fuzz test on that). If the memory layout changes, this function must be updated. */ function hash(OLKey memory olKey) internal pure returns (bytes32 _id) { assembly ("memory-safe") { _id := keccak256(olKey, 96) } } /* Creates a flipped copy of the `olKey` with same `tickSpacing`. */ function flipped(OLKey memory olKey) internal pure returns (OLKey memory) { return OLKey(olKey.inbound_tkn, olKey.outbound_tkn, olKey.tickSpacing); } /* Convert `tick` to bin according to `olKey.tickSpacing` */ function nearestBin(OLKey memory olKey, Tick _tick) internal pure returns (Bin) { return _tick.nearestBin(olKey.tickSpacing); } /* Convert `bin` to `tick` according to `olKey.tickSpacing` */ function tick(OLKey memory olKey, Bin bin) internal pure returns (Tick) { return bin.tick(olKey.tickSpacing); } } /* # Structs The structs defined in `structs.js` have their counterpart as solidity structs that are easy to manipulate for outside contracts / callers of view functions. */ library MgvLib { /* Some miscellaneous data types useful to `Mangrove` and external contracts */ //+clear+ /* `SingleOrder` holds data about an order-offer match in a struct. Used by `marketOrder` (and some of its nested functions) to avoid stack too deep errors. It is used in market order and cleaning. */ struct SingleOrder { OLKey olKey; uint offerId; /* The `offer` given to the maker will be cleaned of `prev`/`next` pointers. */ Offer offer; /* `takerWants`/`takerGives` mutate over execution. Initially the `wants`/`gives` from the taker's pov, then actual `wants`/`gives` adjusted by offer's price and volume. */ uint takerWants; uint takerGives; /* `offerDetail` is only populated when necessary. */ OfferDetail offerDetail; Global global; /* The `local` given to the maker will be cleaned of tick tree information. */ Local local; } /* <a id="MgvLib/OrderResult"></a> `OrderResult` holds additional data for the maker and is given to them _after_ they fulfilled an offer. It gives them their own returned data from the previous call and an `mgvData` specifying whether Mangrove encountered an error. */ struct OrderResult { /* `makerData` holds a message that was either returned by the maker or passed as revert message at the end of the trade execution*/ bytes32 makerData; /* `mgvData` is an [internal Mangrove status code](#MgvOfferTaking/statusCodes) code. */ bytes32 mgvData; } /* `CleanTarget` holds data about an offer that should be cleaned, i.e. made to fail by executing it with the specified volume. It is used in `MgvOfferTaking.cleanByImpersonation`. */ struct CleanTarget { uint offerId; Tick tick; uint gasreq; uint takerWants; } } /* # Events The events emitted are listed here: */ interface HasMgvEvents { /* Events in solidity is a hard thing to do in a optimal way. If you look at it as a purely gas efficient issue, you want to emit as few events as possible and with as few fields as possible. But as events also has to be usable for an off chain user, then doing this is not always the best solution. We tried to list the main forces that drive which events and data to emit: 1. Use as little gas as possible. 2. An indexer should be able to keep track of the state of Mangrove. 3. Doing RPC calls directly, should be able to find offers and other information based on offer list, maker, taker, offer id, etc. These forces are in conflict and it is impossible to find a solution that is optimal for all three. The following events are therefore an attempt to balance the trade-offs. */ /* ### Mangrove Creation Emitted at the creation of the new Mangrove contract */ event NewMgv(); /* ### Mangrove adds or removes wei from `maker`'s account */ /* * Credit event occurs when an offer is removed from Mangrove or when the `fund` function is called This is emitted when a user's account on Mangrove is credited with some native funds, to be used as provision for offers. It emits the `maker`'s address and the `amount` credited The `maker` address is indexed so that we can filter on it when doing RPC calls. These are the scenarios where it can happen: - Fund Mangrove directly - Fund Mangrove when posting an offer - When updating an offer - Funding Mangrove or - the updated offer needs less provision, than it already has. Meaning the user gets credited the difference. - When retracting an offer and deprovisioning it. Meaning the user gets credited the provision that was locked by the offer. - When an offer fails. The remaining provision gets credited back to the maker A challenge for an indexer is to know how much provision each offer locks. With the current events, an indexer is going to have to know the liveness, gasreq and gasprice of the offer. If the offer is not live, then also know if it has been deprovisioned. And know what the gasbase of the offer list was when the offer was posted. With this information an indexer can calculate the exact provision locked by the offer. The indexer also cannot deduce what scenario the credit event happened. E.g., we don't know if the credit event happened because an offer failed or because the user simply funded Mangrove. */ event Credit(address indexed maker, uint amount); /* * Debit event occurs when an offer is posted or when the `withdraw` function is called This is emitted when a user's account on Mangrove is debited with some native funds. It emits the `maker`'s address and the `amount` debited. The `maker` address is indexed so that we can filter on it when doing RPC calls. These are the scenarios where it can happen: - Withdraw funds from Mangrove directly - When posting an offer. The user gets debited the provision that the offer locks. - When updating an offer and it requires more provision that it already has. Meaning the user gets debited the difference. Same challenges as for Credit */ event Debit(address indexed maker, uint amount); /* ### Mangrove reconfiguration */ /* This event is emitted when an offer list is activated or deactivated. Meaning one half of a market is opened. It emits the `olKeyHash` and the boolean `value`. By emitting this, an indexer will be able to keep track of what offer lists are active and what their hash is. The `olKeyHash` and both token addresses are indexed, so that we can filter on it when doing RPC calls. */ event SetActive( bytes32 indexed olKeyHash, address indexed outbound_tkn, address indexed inbound_tkn, uint tickSpacing, bool value ); /* This event is emitted when the fee of an offer list is changed. It emits the `olKeyHash` and the `value`. By emitting this, an indexer will be able to keep track of what fee each offer list has. The `olKeyHash` is indexed, so that we can filter on it when doing RPC calls. */ event SetFee(bytes32 indexed olKeyHash, uint value); /* This event is emitted when the gasbase of an offer list is changed. It emits the `olKeyHash` and the `offer_gasbase`. By emitting this, an indexer will be able to keep track of what gasbase each offer list has. The `olKeyHash` is indexed, so that we can filter on it when doing RPC calls. */ event SetGasbase(bytes32 indexed olKeyHash, uint offer_gasbase); /* This event is emitted when the governance of Mangrove is set. It emits the `governance` address. By emitting this, an indexer will be able to keep track of what governance address Mangrove has. No fields are indexed as there is no need for RPC calls to filter on this. */ event SetGovernance(address value); /* This event is emitted when the monitor address of Mangrove is set. Be aware that the address for Monitor is also the address for the oracle. It emits the `monitor` / `oracle` address. By emitting this, an indexer will be able to keep track of what monitor/oracle address Mangrove use. No fields are indexed as there is no need for RPC calls to filter on this. */ event SetMonitor(address value); /* This event is emitted when the configuration for whether to use an oracle or not is set. It emits the `useOracle`, which is a boolean value, that controls whether or not Mangrove reads its `gasprice` and `density` from an oracle or uses its own local values. By emitting this, an indexer will be able to keep track of if Mangrove is using an oracle. No fields are indexed as there is no need for RPC calls to filter on this. */ event SetUseOracle(bool value); /* This event is emitted when the configuration for notify on Mangrove is set. It emits a boolean value, to tell whether or not notify is active. By emitting this, an indexer will be able to keep track of whether or not Mangrove notifies the Monitor/Oracle when and offer is taken, either successfully or not. No fields are indexed as there is no need for RPC calls to filter on this. */ event SetNotify(bool value); /* This event is emitted when the gasmax of Mangrove is set. It emits the `gasmax`. By emitting this, an indexer will be able to keep track of what gasmax Mangrove has. Read more about Mangroves gasmax on [docs.mangrove.exchange](https://docs.mangrove.exchange) No fields are indexed as there is no need for RPC calls to filter on this. */ event SetGasmax(uint value); /* This event is emitted when the density of an offer list is changed. It emits the `olKeyHash` and the `density`. By emitting this, an indexer will be able to keep track of what density each offer list has. The `olKeyHash` is indexed, so that we can filter on it when doing RPC calls. */ event SetDensity96X32(bytes32 indexed olKeyHash, uint value); /* This event is emitted when the max recursion depth of Mangrove is set. It emits the max depth `value`. By emitting this, an indexer will be able to keep track of what max recursion depth Mangrove has. Read more about Mangroves max recursion depth on [docs.mangrove.exchange](https://docs.mangrove.exchange) */ event SetMaxRecursionDepth(uint value); /* This event is emitted when the max gasreq for failing offers of Mangrove is set. It emits the max gasreq for failing offers `value`. By emitting this, an indexer will be able to keep track of what max gasreq for failing offers Mangrove has. Read more about Mangroves max gasreq for failing offers on [docs.mangrove.exchange](https://docs.mangrove.exchange) */ event SetMaxGasreqForFailingOffers(uint value); /* This event is emitted when the gasprice of Mangrove is set. It emits the `gasprice`. By emitting this, an indexer will be able to keep track of what gasprice Mangrove has. Read more about Mangroves gasprice on [docs.mangrove.exchange](https://docs.mangrove.exchange) No fields are indexed as there is no need for RPC calls to filter on this. */ event SetGasprice(uint value); /* ### Clean order execution */ /* This event is emitted when a user tries to clean offers on Mangrove, using the build in clean functionality. It emits the `olKeyHash`, the `taker` address and `offersToBeCleaned`, which is the number of offers that should be cleaned. By emitting this event, an indexer can save what `offer list` the user is trying to clean and what `taker` is being used. This way it can keep a context for the following events being emitted (Just like `OrderStart`). The `offersToBeCleaned` is emitted so that an indexer can keep track of how many offers the user tried to clean. Combining this with the amount of `OfferFail` events emitted, then an indexer can know how many offers the user actually managed to clean. This could be used for analytics. The fields `olKeyHash` and `taker` are indexed, so that we can filter on them when doing RPC calls. */ event CleanStart(bytes32 indexed olKeyHash, address indexed taker, uint offersToBeCleaned); /* This event is emitted when a Clean operation is completed. It does not emit any fields. This event is only needed in order to know that the clean operation is completed. This way an indexer can know exactly in what context we are in. It could emit the total bounty received, but in order to know who got the bounty, an indexer would still be needed or we would have to emit the taker address as well, in order for an RPC call to find the data. But an indexer would still be able to find this info, by collecting all the previous events. So we do not emit the bounty. */ event CleanComplete(); /* ### Market order execution */ /* This event is emitted when a market order is started on Mangrove. It emits the `olKeyHash`, the `taker`, the `maxTick`, `fillVolume` and `fillWants`. The fields `olKeyHash` and `taker` are indexed, so that we can filter on them when doing RPC calls. By emitting this an indexer can keep track of what context the current market order is in. E.g. if a user starts a market order and one of the offers taken also starts a market order, then we can in an indexer have a stack of started market orders and thereby know exactly what offer list the order is running on and the taker. By emitting `maxTick`, `fillVolume` and `fillWants`, we can now also know how much of the market order was filled and if it matches the price given. See OrderComplete for more. */ event OrderStart(bytes32 indexed olKeyHash, address indexed taker, Tick maxTick, uint fillVolume, bool fillWants); /* This event is emitted when a market order is finished. It emits `olKeyHash`, the `taker` and the total `fee paid`. We need this event, in order to know that a market order is completed. This way an indexer can know exactly in what context we are in. The total `fee paid` for that market order, is needed, as we do not emit this any other places. The fields `olKeyHash` and `taker` is not needed for an indexer, but they are emitted and indexed in order for RPC calls to filter and find the fee. */ event OrderComplete(bytes32 indexed olKeyHash, address indexed taker, uint fee); /* ### Offer execution */ /* This event is emitted when an offer is successfully taken. Meaning both maker and taker has gotten their funds. It emits the `olKeyHash`, `taker` address, the `offerId` and the `takerWants` and `takerGives` that the offer was taken at. Just as for `OfferFail`, the `olKeyHash` and `taker` are not needed for an indexer to work, but are needed for RPC calls. `olKeyHash` `taker` and `id` are indexed so that we can filter on them when doing RPC calls. As `maker` can be a strategy and not the actual owner, then we chose not to emit it here and to mark the field `id` indexed, the strat should emit the relation between maker and offerId. This way you can still by RPC calls find the relevant offer successes So they are emitted and indexed for that reason. Just like `OfferFail` this event is emitted during posthook end, so it is emitted in reverse order compared to what order they are taken. This event could be emitted at offer execution, but for consistency we emit it at posthook end. If the posthook of the offer fails. Then we emit `OfferSuccessWithPosthookData` instead of just `OfferSuccess`. This event has one extra field, which is the reason for the posthook failure. By emitting the posthook data, an indexer can keep track of the reason posthook fails, this could for example be used for analytics. By emitting `offerId`, `wants` and `gives`, an indexer can keep track of whether the offer was partially or fully taken, by looking at the what the offer was posted at. */ event OfferSuccess( bytes32 indexed olKeyHash, address indexed taker, uint indexed id, uint takerWants, uint takerGives ); event OfferSuccessWithPosthookData( bytes32 indexed olKeyHash, address indexed taker, uint indexed id, uint takerWants, uint takerGives, bytes32 posthookData ); /* This event is emitted when an offer fails, because of a maker error. It emits `olKeyHash`, `taker`, the `offerId`, the offers `takerWants`, `takerGives`, `penalty` and the `reason` for failure. `olKeyHash` and `taker` are all fields that we do not need, in order for an indexer to work, as an indexer will be able the get that info from the former `OrderStart` and `OfferWrite` events. But in order for RPC call to filter on this, we need to emit them. `olKeyHash` `taker` and `id` are indexed so that we can filter on them when doing RPC calls. As `maker` can be a strategy and not the actual owner, then we chose to not emit it here and to mark the field `id` indexed, the strat should emit the relation between maker and offerId. This way you can still by RPC calls find the relevant offer successes If the posthook of the offer fails. Then we emit `OfferFailWithPosthookData` instead of just `OfferFail`. This event has one extra field, which is the reason for the posthook failure. By emitting the posthook data, an indexer can keep track of the reason posthook fails, this could for example be used for analytics. This event is emitted during posthook end, we wait to emit this event to the end, because we need the information of `penalty`, which is only available at the end of the posthook. This means that `OfferFail` events are emitted in reverse order, compared to what order they are taken. This is due to the way we handle posthooks. The same goes for `OfferSuccess`. By emitting this event, an indexer can keep track of, if an offer failed and thereby if the offer is live. By emitting the `takerWants` and `takerGives` that the offer was taken with, then an indexer can keep track of these amounts, which could be useful for e.g. strategy manager, to know if their offers fail at a certain amount. */ event OfferFail( bytes32 indexed olKeyHash, address indexed taker, uint indexed id, uint takerWants, uint takerGives, uint penalty, // `mgvData` may only be `"mgv/makerRevert"`, `"mgv/makerTransferFail"` or `"mgv/makerReceiveFail"` bytes32 mgvData ); event OfferFailWithPosthookData( bytes32 indexed olKeyHash, address indexed taker, uint indexed id, uint takerWants, uint takerGives, uint penalty, // `mgvData` may only be `"mgv/makerRevert"`, `"mgv/makerTransferFail"` or `"mgv/makerReceiveFail"` bytes32 mgvData, bytes32 posthookData ); /* ### After `permit` and `approve` This is emitted when a user permits another address to use a certain amount of its funds to do market orders, or when a user revokes another address to use a certain amount of its funds. Approvals are based on the pair of outbound and inbound token. Be aware that it is not offer list bases, as an offer list also holds the tick spacing. We emit `outbound` token, `inbound` token, `owner`, msg.sender (`spender`), `value`. Where `owner` is the one who owns the funds, `spender` is the one who is allowed to use the funds and `value` is the amount of funds that is allowed to be used. Outbound, inbound and owner is indexed, this way one can filter on these fields when doing RPC calls. If we could somehow combine outbound and inbound into one field, then we could also index the spender. */ event Approval( address indexed outbound_tkn, address indexed inbound_tkn, address indexed owner, address spender, uint value ); /* ### Mangrove closure */ event Kill(); /* ### An offer was created or updated. This event is emitted when an offer is posted on Mangrove. It emits the `olKeyHash`, the `maker` address, the `tick`, the `gives`, the `gasprice`, `gasreq` and the offers `id`. By emitting the `olKeyHash` and `id`, an indexer will be able to keep track of each offer, because offer list and id together create a unique id for the offer. By emitting the `maker` address, we are able to keep track of who has posted what offer. The `tick` and `gives`, enables an indexer to know exactly how much an offer is willing to give and at what price, this could for example be used to calculate a return. The `gasprice` and `gasreq`, enables an indexer to calculate how much provision is locked by the offer, see `Credit` for more information. The fields `olKeyHash` and `maker` are indexed, so that we can filter on them when doing RPC calls. */ event OfferWrite( bytes32 indexed olKeyHash, address indexed maker, int tick, uint gives, uint gasprice, uint gasreq, uint id ); /* This event is emitted when a user retracts his offer. It emits the `olKeyHash`, the `maker` address, `offerId` and whether or not the user chose to `deprovision` the offer. By emitting this event an indexer knows whether or not an offer is live. And whether or not an offer is deprovisioned. This is important because we need to know this, when we try to calculate how much an offer locks in provision. See the description of `Credit` for more info. The `maker` is not needed for an indexer to work, but is needed for RPC calls, so it is emitted and indexed for that reason. The `olKeyHash` is only indexed because it is needed for RPC calls. */ event OfferRetract(bytes32 indexed olKeyHash, address indexed maker, uint id, bool deprovision); } /* # IMaker interface */ interface IMaker { /* Called upon offer execution. - If the call throws, Mangrove will not try to transfer funds and the first 32 bytes of revert reason are passed to `makerPosthook` as `makerData` - If the call returns normally, `returnData` is passed to `makerPosthook` as `makerData` and Mangrove will attempt to transfer the funds. */ function makerExecute(MgvLib.SingleOrder calldata order) external returns (bytes32); /* Called after all offers of an order have been executed. Posthook of the last executed order is called first and full reentrancy into Mangrove is enabled at this time. `order` recalls key arguments of the order that was processed and `result` recalls important information for updating the current offer. (see [above](#MgvLib/OrderResult))*/ function makerPosthook(MgvLib.SingleOrder calldata order, MgvLib.OrderResult calldata result) external; } /* # Monitor interface If enabled, the monitor receives notification after each offer execution and is read for each offer list's `gasprice` and `density`. */ interface IMgvMonitor { function notifySuccess(MgvLib.SingleOrder calldata sor, address taker) external; function notifyFail(MgvLib.SingleOrder calldata sor, address taker) external; function read(OLKey memory olKey) external view returns (uint gasprice, Density density); }lib/mangrove-strats/src/strategies/offer_maker/abstract/Direct.sol
// SPDX-License-Identifier: MIT pragma solidity ^0.8.10; import {MangroveOffer, TransferLib} from "@mgv-strats/src/strategies/MangroveOffer.sol"; import {AbstractRouter, RL} from "@mgv-strats/src/strategies/routers/abstract/AbstractRouter.sol"; import {IMangrove} from "@mgv/src/IMangrove.sol"; import {MgvLib, OLKey} from "@mgv/src/core/MgvLib.sol"; import {IERC20} from "@mgv/lib/IERC20.sol"; import {IOfferLogic} from "@mgv-strats/src/strategies/interfaces/IOfferLogic.sol"; ///@title `Direct` strats is an extension of MangroveOffer that allows contract's admin to manage offers on Mangrove. abstract contract Direct is MangroveOffer { ///@notice address of the fund owner. Is used for the proxy owner if strat is using one. address public immutable FUND_OWNER; ///@notice whether this contract requires that routing pull orders provide *at most* the required amount. bool public immutable STRICT_PULLING; struct RouterParams { AbstractRouter routerImplementation; // 0x if not routing address fundOwner; // address must be controlled by msg.sender bool strict; } ///@notice `Direct`'s constructor. ///@param mgv The Mangrove deployment that is allowed to call `this` for trade execution and posthook. ///@param routerParams routing parameters. Use `noRouter()` to get an empty struct constructor(IMangrove mgv, RouterParams memory routerParams) MangroveOffer(mgv, routerParams.routerImplementation) { address fundOwner = routerParams.fundOwner == address(0) ? address(this) : routerParams.fundOwner; STRICT_PULLING = routerParams.strict; FUND_OWNER = fundOwner; } ///@notice convenience function to get an empty RouterParams struct ///@return an empty RouterParams struct function noRouter() public pure returns (RouterParams memory) {} ///@inheritdoc IOfferLogic ///@dev Returns the router to which pull/push calls must be done. ///@dev if strat is not routing, the call does not revert but returns a casted `address(0)` function router(address) public view override returns (AbstractRouter) { return ROUTER_IMPLEMENTATION; } ///@notice convenience function ///@return the router to which pull/push calls must be done. function router() public view returns (AbstractRouter) { return router(address(0)); } ///@inheritdoc IOfferLogic ///@notice activates asset exchange with router function activate(IERC20 token) public virtual override { super.activate(token); if (_isRouting()) { require(TransferLib.approveToken(token, address(router()), type(uint).max), "Direct/RouterActivationFailed"); } } /// @notice Inserts a new offer in Mangrove Offer List. /// @param args Function arguments stored in memory. /// @return offerId Identifier of the newly created offer. Returns 0 if offer creation was rejected by Mangrove and `args.noRevert` is set to `true`. /// @return status NEW_OFFER_SUCCESS if the offer was successfully posted on Mangrove. Returns Mangrove's revert reason otherwise. function _newOffer(OfferArgs memory args) internal returns (uint offerId, bytes32 status) { try MGV.newOfferByTick{value: args.fund}(args.olKey, args.tick, args.gives, args.gasreq, args.gasprice) returns ( uint offerId_ ) { offerId = offerId_; status = NEW_OFFER_SUCCESS; } catch Error(string memory reason) { require(args.noRevert, reason); status = bytes32(bytes(reason)); } } ///@inheritdoc MangroveOffer function _updateOffer(OfferArgs memory args, uint offerId) internal override returns (bytes32 status) { try MGV.updateOfferByTick{value: args.fund}(args.olKey, args.tick, args.gives, args.gasreq, args.gasprice, offerId) { status = REPOST_SUCCESS; } catch Error(string memory reason) { require(args.noRevert, reason); status = bytes32(bytes(reason)); } } ///@notice Retracts an offer from an Offer List of Mangrove. ///@param olKey the offer list key. ///@param offerId the identifier of the offer in the offer list ///@param deprovision if set to `true` if offer admin wishes to redeem the offer's provision. ///@return freeWei the amount of native tokens (in WEI) that have been retrieved by retracting the offer. ///@dev An offer that is retracted without `deprovision` is retracted from the offer list, but still has its provisions locked by Mangrove. ///@dev Calling this function, with the `deprovision` flag, on an offer that is already retracted must be used to retrieve the locked provisions. function _retractOffer( OLKey memory olKey, uint offerId, bool deprovision // if set to `true`, `this` contract will receive the remaining provision (in WEI) associated to `offerId`. ) internal returns (uint freeWei) { freeWei = MGV.retractOffer(olKey, offerId, deprovision); } ///@inheritdoc IOfferLogic function provisionOf(OLKey memory olKey, uint offerId) external view override returns (uint provision) { provision = _provisionOf(olKey, offerId); } ///@notice direct contract do not need to do anything specific with incoming funds during trade ///@dev one should override this function if one wishes to leverage taker's fund during trade execution ///@inheritdoc MangroveOffer function __put__(uint, MgvLib.SingleOrder calldata) internal virtual override returns (uint) { return 0; } ///@notice `__get__` hook for `Direct` is to ask the router to pull liquidity if using a router /// otherwise the function simply returns what's missing in the local balance ///@inheritdoc MangroveOffer function __get__(uint amount, MgvLib.SingleOrder calldata order) internal virtual override returns (uint) { uint balance = IERC20(order.olKey.outbound_tkn).balanceOf(address(this)); uint missing = balance >= amount ? 0 : amount - balance; if (!_isRouting()) { return missing; } else { uint pulled = router().pull( RL.RoutingOrder({ token: IERC20(order.olKey.outbound_tkn), olKeyHash: order.olKey.hash(), offerId: order.offerId, fundOwner: FUND_OWNER }), missing, STRICT_PULLING ); return pulled >= missing ? 0 : missing - pulled; } } ///@notice Direct posthook flushes outbound and inbound token back to the router (if any) ///@inheritdoc MangroveOffer function __posthookSuccess__(MgvLib.SingleOrder calldata order, bytes32 makerData) internal virtual override returns (bytes32) { bytes32 olKeyHash = order.olKey.hash(); if (_isRouting()) { RL.RoutingOrder[] memory routingOrders = new RL.RoutingOrder[](2); routingOrders[0].token = IERC20(order.olKey.outbound_tkn); // flushing outbound tokens if this contract pulled more liquidity than required during `makerExecute` routingOrders[0].fundOwner = FUND_OWNER; routingOrders[0].olKeyHash = olKeyHash; routingOrders[0].offerId = order.offerId; routingOrders[1].token = IERC20(order.olKey.inbound_tkn); // flushing liquidity brought by taker routingOrders[1].fundOwner = FUND_OWNER; routingOrders[1].olKeyHash = olKeyHash; routingOrders[1].offerId = order.offerId; router().flush(routingOrders); } // reposting offer residual if any return super.__posthookSuccess__(order, makerData); } }lib/solady/src/utils/SafeTransferLib.sol
// SPDX-License-Identifier: MIT pragma solidity ^0.8.4; /// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values. /// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SafeTransferLib.sol) /// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol) /// @author Permit2 operations from (https://github.com/Uniswap/permit2/blob/main/src/libraries/Permit2Lib.sol) /// /// @dev Note: /// - For ETH transfers, please use `forceSafeTransferETH` for DoS protection. library SafeTransferLib { /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* CUSTOM ERRORS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev The ETH transfer has failed. error ETHTransferFailed(); /// @dev The ERC20 `transferFrom` has failed. error TransferFromFailed(); /// @dev The ERC20 `transfer` has failed. error TransferFailed(); /// @dev The ERC20 `approve` has failed. error ApproveFailed(); /// @dev The ERC20 `totalSupply` query has failed. error TotalSupplyQueryFailed(); /// @dev The Permit2 operation has failed. error Permit2Failed(); /// @dev The Permit2 amount must be less than `2**160 - 1`. error Permit2AmountOverflow(); /// @dev The Permit2 approve operation has failed. error Permit2ApproveFailed(); /// @dev The Permit2 lockdown operation has failed. error Permit2LockdownFailed(); /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* CONSTANTS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Suggested gas stipend for contract receiving ETH that disallows any storage writes. uint256 internal constant GAS_STIPEND_NO_STORAGE_WRITES = 2300; /// @dev Suggested gas stipend for contract receiving ETH to perform a few /// storage reads and writes, but low enough to prevent griefing. uint256 internal constant GAS_STIPEND_NO_GRIEF = 100000; /// @dev The unique EIP-712 domain separator for the DAI token contract. bytes32 internal constant DAI_DOMAIN_SEPARATOR = 0xdbb8cf42e1ecb028be3f3dbc922e1d878b963f411dc388ced501601c60f7c6f7; /// @dev The address for the WETH9 contract on Ethereum mainnet. address internal constant WETH9 = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; /// @dev The canonical Permit2 address. /// [Github](https://github.com/Uniswap/permit2) /// [Etherscan](https://etherscan.io/address/0x000000000022D473030F116dDEE9F6B43aC78BA3) address internal constant PERMIT2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3; /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* ETH OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ // If the ETH transfer MUST succeed with a reasonable gas budget, use the force variants. // // The regular variants: // - Forwards all remaining gas to the target. // - Reverts if the target reverts. // - Reverts if the current contract has insufficient balance. // // The force variants: // - Forwards with an optional gas stipend // (defaults to `GAS_STIPEND_NO_GRIEF`, which is sufficient for most cases). // - If the target reverts, or if the gas stipend is exhausted, // creates a temporary contract to force send the ETH via `SELFDESTRUCT`. // Future compatible with `SENDALL`: https://eips.ethereum.org/EIPS/eip-4758. // - Reverts if the current contract has insufficient balance. // // The try variants: // - Forwards with a mandatory gas stipend. // - Instead of reverting, returns whether the transfer succeeded. /// @dev Sends `amount` (in wei) ETH to `to`. function safeTransferETH(address to, uint256 amount) internal { /// @solidity memory-safe-assembly assembly { if iszero(call(gas(), to, amount, codesize(), 0x00, codesize(), 0x00)) { mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`. revert(0x1c, 0x04) } } } /// @dev Sends all the ETH in the current contract to `to`. function safeTransferAllETH(address to) internal { /// @solidity memory-safe-assembly assembly { // Transfer all the ETH and check if it succeeded or not. if iszero(call(gas(), to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) { mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`. revert(0x1c, 0x04) } } } /// @dev Force sends `amount` (in wei) ETH to `to`, with a `gasStipend`. function forceSafeTransferETH(address to, uint256 amount, uint256 gasStipend) internal { /// @solidity memory-safe-assembly assembly { if lt(selfbalance(), amount) { mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`. revert(0x1c, 0x04) } if iszero(call(gasStipend, to, amount, codesize(), 0x00, codesize(), 0x00)) { mstore(0x00, to) // Store the address in scratch space. mstore8(0x0b, 0x73) // Opcode `PUSH20`. mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`. if iszero(create(amount, 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation. } } } /// @dev Force sends all the ETH in the current contract to `to`, with a `gasStipend`. function forceSafeTransferAllETH(address to, uint256 gasStipend) internal { /// @solidity memory-safe-assembly assembly { if iszero(call(gasStipend, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) { mstore(0x00, to) // Store the address in scratch space. mstore8(0x0b, 0x73) // Opcode `PUSH20`. mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`. if iszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation. } } } /// @dev Force sends `amount` (in wei) ETH to `to`, with `GAS_STIPEND_NO_GRIEF`. function forceSafeTransferETH(address to, uint256 amount) internal { /// @solidity memory-safe-assembly assembly { if lt(selfbalance(), amount) { mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`. revert(0x1c, 0x04) } if iszero(call(GAS_STIPEND_NO_GRIEF, to, amount, codesize(), 0x00, codesize(), 0x00)) { mstore(0x00, to) // Store the address in scratch space. mstore8(0x0b, 0x73) // Opcode `PUSH20`. mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`. if iszero(create(amount, 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation. } } } /// @dev Force sends all the ETH in the current contract to `to`, with `GAS_STIPEND_NO_GRIEF`. function forceSafeTransferAllETH(address to) internal { /// @solidity memory-safe-assembly assembly { // forgefmt: disable-next-item if iszero(call(GAS_STIPEND_NO_GRIEF, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) { mstore(0x00, to) // Store the address in scratch space. mstore8(0x0b, 0x73) // Opcode `PUSH20`. mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`. if iszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation. } } } /// @dev Sends `amount` (in wei) ETH to `to`, with a `gasStipend`. function trySafeTransferETH(address to, uint256 amount, uint256 gasStipend) internal returns (bool success) { /// @solidity memory-safe-assembly assembly { success := call(gasStipend, to, amount, codesize(), 0x00, codesize(), 0x00) } } /// @dev Sends all the ETH in the current contract to `to`, with a `gasStipend`. function trySafeTransferAllETH(address to, uint256 gasStipend) internal returns (bool success) { /// @solidity memory-safe-assembly assembly { success := call(gasStipend, to, selfbalance(), codesize(), 0x00, codesize(), 0x00) } } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* ERC20 OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Sends `amount` of ERC20 `token` from `from` to `to`. /// Reverts upon failure. /// /// The `from` account must have at least `amount` approved for /// the current contract to manage. function safeTransferFrom(address token, address from, address to, uint256 amount) internal { /// @solidity memory-safe-assembly assembly { let m := mload(0x40) // Cache the free memory pointer. mstore(0x60, amount) // Store the `amount` argument. mstore(0x40, to) // Store the `to` argument. mstore(0x2c, shl(96, from)) // Store the `from` argument. mstore(0x0c, 0x23b872dd000000000000000000000000) // `transferFrom(address,address,uint256)`. let success := call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20) if iszero(and(eq(mload(0x00), 1), success)) { if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) { mstore(0x00, 0x7939f424) // `TransferFromFailed()`. revert(0x1c, 0x04) } } mstore(0x60, 0) // Restore the zero slot to zero. mstore(0x40, m) // Restore the free memory pointer. } } /// @dev Sends `amount` of ERC20 `token` from `from` to `to`. /// /// The `from` account must have at least `amount` approved for the current contract to manage. function trySafeTransferFrom(address token, address from, address to, uint256 amount) internal returns (bool success) { /// @solidity memory-safe-assembly assembly { let m := mload(0x40) // Cache the free memory pointer. mstore(0x60, amount) // Store the `amount` argument. mstore(0x40, to) // Store the `to` argument. mstore(0x2c, shl(96, from)) // Store the `from` argument. mstore(0x0c, 0x23b872dd000000000000000000000000) // `transferFrom(address,address,uint256)`. success := call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20) if iszero(and(eq(mload(0x00), 1), success)) { success := lt(or(iszero(extcodesize(token)), returndatasize()), success) } mstore(0x60, 0) // Restore the zero slot to zero. mstore(0x40, m) // Restore the free memory pointer. } } /// @dev Sends all of ERC20 `token` from `from` to `to`. /// Reverts upon failure. /// /// The `from` account must have their entire balance approved for the current contract to manage. function safeTransferAllFrom(address token, address from, address to) internal returns (uint256 amount) { /// @solidity memory-safe-assembly assembly { let m := mload(0x40) // Cache the free memory pointer. mstore(0x40, to) // Store the `to` argument. mstore(0x2c, shl(96, from)) // Store the `from` argument. mstore(0x0c, 0x70a08231000000000000000000000000) // `balanceOf(address)`. // Read the balance, reverting upon failure. if iszero( and( // The arguments of `and` are evaluated from right to left. gt(returndatasize(), 0x1f), // At least 32 bytes returned. staticcall(gas(), token, 0x1c, 0x24, 0x60, 0x20) ) ) { mstore(0x00, 0x7939f424) // `TransferFromFailed()`. revert(0x1c, 0x04) } mstore(0x00, 0x23b872dd) // `transferFrom(address,address,uint256)`. amount := mload(0x60) // The `amount` is already at 0x60. We'll need to return it. // Perform the transfer, reverting upon failure. let success := call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20) if iszero(and(eq(mload(0x00), 1), success)) { if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) { mstore(0x00, 0x7939f424) // `TransferFromFailed()`. revert(0x1c, 0x04) } } mstore(0x60, 0) // Restore the zero slot to zero. mstore(0x40, m) // Restore the free memory pointer. } } /// @dev Sends `amount` of ERC20 `token` from the current contract to `to`. /// Reverts upon failure. function safeTransfer(address token, address to, uint256 amount) internal { /// @solidity memory-safe-assembly assembly { mstore(0x14, to) // Store the `to` argument. mstore(0x34, amount) // Store the `amount` argument. mstore(0x00, 0xa9059cbb000000000000000000000000) // `transfer(address,uint256)`. // Perform the transfer, reverting upon failure. let success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20) if iszero(and(eq(mload(0x00), 1), success)) { if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) { mstore(0x00, 0x90b8ec18) // `TransferFailed()`. revert(0x1c, 0x04) } } mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten. } } /// @dev Sends all of ERC20 `token` from the current contract to `to`. /// Reverts upon failure. function safeTransferAll(address token, address to) internal returns (uint256 amount) { /// @solidity memory-safe-assembly assembly { mstore(0x00, 0x70a08231) // Store the function selector of `balanceOf(address)`. mstore(0x20, address()) // Store the address of the current contract. // Read the balance, reverting upon failure. if iszero( and( // The arguments of `and` are evaluated from right to left. gt(returndatasize(), 0x1f), // At least 32 bytes returned. staticcall(gas(), token, 0x1c, 0x24, 0x34, 0x20) ) ) { mstore(0x00, 0x90b8ec18) // `TransferFailed()`. revert(0x1c, 0x04) } mstore(0x14, to) // Store the `to` argument. amount := mload(0x34) // The `amount` is already at 0x34. We'll need to return it. mstore(0x00, 0xa9059cbb000000000000000000000000) // `transfer(address,uint256)`. // Perform the transfer, reverting upon failure. let success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20) if iszero(and(eq(mload(0x00), 1), success)) { if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) { mstore(0x00, 0x90b8ec18) // `TransferFailed()`. revert(0x1c, 0x04) } } mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten. } } /// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract. /// Reverts upon failure. function safeApprove(address token, address to, uint256 amount) internal { /// @solidity memory-safe-assembly assembly { mstore(0x14, to) // Store the `to` argument. mstore(0x34, amount) // Store the `amount` argument. mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`. let success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20) if iszero(and(eq(mload(0x00), 1), success)) { if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) { mstore(0x00, 0x3e3f8f73) // `ApproveFailed()`. revert(0x1c, 0x04) } } mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten. } } /// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract. /// If the initial attempt to approve fails, attempts to reset the approved amount to zero, /// then retries the approval again (some tokens, e.g. USDT, requires this). /// Reverts upon failure. function safeApproveWithRetry(address token, address to, uint256 amount) internal { /// @solidity memory-safe-assembly assembly { mstore(0x14, to) // Store the `to` argument. mstore(0x34, amount) // Store the `amount` argument. mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`. // Perform the approval, retrying upon failure. let success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20) if iszero(and(eq(mload(0x00), 1), success)) { if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) { mstore(0x34, 0) // Store 0 for the `amount`. mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`. pop(call(gas(), token, 0, 0x10, 0x44, codesize(), 0x00)) // Reset the approval. mstore(0x34, amount) // Store back the original `amount`. // Retry the approval, reverting upon failure. success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20) if iszero(and(eq(mload(0x00), 1), success)) { // Check the `extcodesize` again just in case the token selfdestructs lol. if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) { mstore(0x00, 0x3e3f8f73) // `ApproveFailed()`. revert(0x1c, 0x04) } } } } mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten. } } /// @dev Returns the amount of ERC20 `token` owned by `account`. /// Returns zero if the `token` does not exist. function balanceOf(address token, address account) internal view returns (uint256 amount) { /// @solidity memory-safe-assembly assembly { mstore(0x14, account) // Store the `account` argument. mstore(0x00, 0x70a08231000000000000000000000000) // `balanceOf(address)`. amount := mul( // The arguments of `mul` are evaluated from right to left. mload(0x20), and( // The arguments of `and` are evaluated from right to left. gt(returndatasize(), 0x1f), // At least 32 bytes returned. staticcall(gas(), token, 0x10, 0x24, 0x20, 0x20) ) ) } } /// @dev Performs a `token.balanceOf(account)` check. /// `implemented` denotes whether the `token` does not implement `balanceOf`. /// `amount` is zero if the `token` does not implement `balanceOf`. function checkBalanceOf(address token, address account) internal view returns (bool implemented, uint256 amount) { /// @solidity memory-safe-assembly assembly { mstore(0x14, account) // Store the `account` argument. mstore(0x00, 0x70a08231000000000000000000000000) // `balanceOf(address)`. implemented := and( // The arguments of `and` are evaluated from right to left. gt(returndatasize(), 0x1f), // At least 32 bytes returned. staticcall(gas(), token, 0x10, 0x24, 0x20, 0x20) ) amount := mul(mload(0x20), implemented) } } /// @dev Returns the total supply of the `token`. /// Reverts if the token does not exist or does not implement `totalSupply()`. function totalSupply(address token) internal view returns (uint256 result) { /// @solidity memory-safe-assembly assembly { mstore(0x00, 0x18160ddd) // `totalSupply()`. if iszero( and(gt(returndatasize(), 0x1f), staticcall(gas(), token, 0x1c, 0x04, 0x00, 0x20)) ) { mstore(0x00, 0x54cd9435) // `TotalSupplyQueryFailed()`. revert(0x1c, 0x04) } result := mload(0x00) } } /// @dev Sends `amount` of ERC20 `token` from `from` to `to`. /// If the initial attempt fails, try to use Permit2 to transfer the token. /// Reverts upon failure. /// /// The `from` account must have at least `amount` approved for the current contract to manage. function safeTransferFrom2(address token, address from, address to, uint256 amount) internal { if (!trySafeTransferFrom(token, from, to, amount)) { permit2TransferFrom(token, from, to, amount); } } /// @dev Sends `amount` of ERC20 `token` from `from` to `to` via Permit2. /// Reverts upon failure. function permit2TransferFrom(address token, address from, address to, uint256 amount) internal { /// @solidity memory-safe-assembly assembly { let m := mload(0x40) mstore(add(m, 0x74), shr(96, shl(96, token))) mstore(add(m, 0x54), amount) mstore(add(m, 0x34), to) mstore(add(m, 0x20), shl(96, from)) // `transferFrom(address,address,uint160,address)`. mstore(m, 0x36c78516000000000000000000000000) let p := PERMIT2 let exists := eq(chainid(), 1) if iszero(exists) { exists := iszero(iszero(extcodesize(p))) } if iszero( and( call(gas(), p, 0, add(m, 0x10), 0x84, codesize(), 0x00), lt(iszero(extcodesize(token)), exists) // Token has code and Permit2 exists. ) ) { mstore(0x00, 0x7939f4248757f0fd) // `TransferFromFailed()` or `Permit2AmountOverflow()`. revert(add(0x18, shl(2, iszero(iszero(shr(160, amount))))), 0x04) } } } /// @dev Permit a user to spend a given amount of /// another user's tokens via native EIP-2612 permit if possible, falling /// back to Permit2 if native permit fails or is not implemented on the token. function permit2( address token, address owner, address spender, uint256 amount, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) internal { bool success; /// @solidity memory-safe-assembly assembly { for {} shl(96, xor(token, WETH9)) {} { mstore(0x00, 0x3644e515) // `DOMAIN_SEPARATOR()`. if iszero( and( // The arguments of `and` are evaluated from right to left. lt(iszero(mload(0x00)), eq(returndatasize(), 0x20)), // Returns 1 non-zero word. // Gas stipend to limit gas burn for tokens that don't refund gas when // an non-existing function is called. 5K should be enough for a SLOAD. staticcall(5000, token, 0x1c, 0x04, 0x00, 0x20) ) ) { break } // After here, we can be sure that token is a contract. let m := mload(0x40) mstore(add(m, 0x34), spender) mstore(add(m, 0x20), shl(96, owner)) mstore(add(m, 0x74), deadline) if eq(mload(0x00), DAI_DOMAIN_SEPARATOR) { mstore(0x14, owner) mstore(0x00, 0x7ecebe00000000000000000000000000) // `nonces(address)`. mstore( add(m, 0x94), lt(iszero(amount), staticcall(gas(), token, 0x10, 0x24, add(m, 0x54), 0x20)) ) mstore(m, 0x8fcbaf0c000000000000000000000000) // `IDAIPermit.permit`. // `nonces` is already at `add(m, 0x54)`. // `amount != 0` is already stored at `add(m, 0x94)`. mstore(add(m, 0xb4), and(0xff, v)) mstore(add(m, 0xd4), r) mstore(add(m, 0xf4), s) success := call(gas(), token, 0, add(m, 0x10), 0x104, codesize(), 0x00) break } mstore(m, 0xd505accf000000000000000000000000) // `IERC20Permit.permit`. mstore(add(m, 0x54), amount) mstore(add(m, 0x94), and(0xff, v)) mstore(add(m, 0xb4), r) mstore(add(m, 0xd4), s) success := call(gas(), token, 0, add(m, 0x10), 0xe4, codesize(), 0x00) break } } if (!success) simplePermit2(token, owner, spender, amount, deadline, v, r, s); } /// @dev Simple permit on the Permit2 contract. function simplePermit2( address token, address owner, address spender, uint256 amount, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) internal { /// @solidity memory-safe-assembly assembly { let m := mload(0x40) mstore(m, 0x927da105) // `allowance(address,address,address)`. { let addressMask := shr(96, not(0)) mstore(add(m, 0x20), and(addressMask, owner)) mstore(add(m, 0x40), and(addressMask, token)) mstore(add(m, 0x60), and(addressMask, spender)) mstore(add(m, 0xc0), and(addressMask, spender)) } let p := mul(PERMIT2, iszero(shr(160, amount))) if iszero( and( // The arguments of `and` are evaluated from right to left. gt(returndatasize(), 0x5f), // Returns 3 words: `amount`, `expiration`, `nonce`. staticcall(gas(), p, add(m, 0x1c), 0x64, add(m, 0x60), 0x60) ) ) { mstore(0x00, 0x6b836e6b8757f0fd) // `Permit2Failed()` or `Permit2AmountOverflow()`. revert(add(0x18, shl(2, iszero(p))), 0x04) } mstore(m, 0x2b67b570) // `Permit2.permit` (PermitSingle variant). // `owner` is already `add(m, 0x20)`. // `token` is already at `add(m, 0x40)`. mstore(add(m, 0x60), amount) mstore(add(m, 0x80), 0xffffffffffff) // `expiration = type(uint48).max`. // `nonce` is already at `add(m, 0xa0)`. // `spender` is already at `add(m, 0xc0)`. mstore(add(m, 0xe0), deadline) mstore(add(m, 0x100), 0x100) // `signature` offset. mstore(add(m, 0x120), 0x41) // `signature` length. mstore(add(m, 0x140), r) mstore(add(m, 0x160), s) mstore(add(m, 0x180), shl(248, v)) if iszero( // Revert if token does not have code, or if the call fails. mul(extcodesize(token), call(gas(), p, 0, add(m, 0x1c), 0x184, codesize(), 0x00))) { mstore(0x00, 0x6b836e6b) // `Permit2Failed()`. revert(0x1c, 0x04) } } } /// @dev Approves `spender` to spend `amount` of `token` for `address(this)`. function permit2Approve(address token, address spender, uint160 amount, uint48 expiration) internal { /// @solidity memory-safe-assembly assembly { let addressMask := shr(96, not(0)) let m := mload(0x40) mstore(m, 0x87517c45) // `approve(address,address,uint160,uint48)`. mstore(add(m, 0x20), and(addressMask, token)) mstore(add(m, 0x40), and(addressMask, spender)) mstore(add(m, 0x60), and(addressMask, amount)) mstore(add(m, 0x80), and(0xffffffffffff, expiration)) if iszero(call(gas(), PERMIT2, 0, add(m, 0x1c), 0xa0, codesize(), 0x00)) { mstore(0x00, 0x324f14ae) // `Permit2ApproveFailed()`. revert(0x1c, 0x04) } } } /// @dev Revokes an approval for `token` and `spender` for `address(this)`. function permit2Lockdown(address token, address spender) internal { /// @solidity memory-safe-assembly assembly { let m := mload(0x40) mstore(m, 0xcc53287f) // `Permit2.lockdown`. mstore(add(m, 0x20), 0x20) // Offset of the `approvals`. mstore(add(m, 0x40), 1) // `approvals.length`. mstore(add(m, 0x60), shr(96, shl(96, token))) mstore(add(m, 0x80), shr(96, shl(96, spender))) if iszero(call(gas(), PERMIT2, 0, add(m, 0x1c), 0xa0, codesize(), 0x00)) { mstore(0x00, 0x96b3de23) // `Permit2LockdownFailed()`. revert(0x1c, 0x04) } } } }src/base/KandelManagementRebalancing.sol
// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import {KandelManagement} from "./KandelManagement.sol"; import {OracleData, OracleLib} from "../libraries/OracleLib.sol"; import {TickLib, Tick} from "@mgv/lib/core/TickLib.sol"; import {AbstractKandelSeeder} from "@mgv-strats/src/strategies/offer_maker/market_making/kandel/abstract/AbstractKandelSeeder.sol"; import {SafeTransferLib} from "lib/solady/src/utils/SafeTransferLib.sol"; import {LibCall} from "lib/solady/src/utils/LibCall.sol"; import {ReentrancyGuardTransient} from "lib/solady/src/utils/ReentrancyGuardTransient.sol"; import {FixedPointMathLib} from "lib/solady/src/utils/FixedPointMathLib.sol"; /** * @title KandelManagementRebalancing * @notice Extends KandelManagement with whitelist functionality for authorized rebalancing operations * @dev This contract adds a timelock-based whitelist system to KandelManagement, allowing the owner * to propose addresses for whitelisting with guardian oversight. Whitelisted addresses can * be targets for rebalancing operations for the vault. * @author Mangrove */ contract KandelManagementRebalancing is KandelManagement, ReentrancyGuardTransient { using OracleLib for OracleData; using SafeTransferLib for address; using LibCall for address; /*////////////////////////////////////////////////////////////// ERRORS //////////////////////////////////////////////////////////////*/ /// @notice Thrown when trying to propose an address that is already proposed error AlreadyProposed(); /// @notice Thrown when trying to accept/reject an address that was not proposed error NotProposed(); /// @notice Thrown when trying to accept a whitelist proposal before the timelock has expired error TimelockNotExpired(); /// @notice Thrown when trying to whitelist an invalid address (Kandel contract, base token, or quote token) error InvalidWhitelistAddress(); /// @notice Thrown when trying to rebalance with a non-whitelisted target address error InvalidRebalanceAddress(); /// @notice Thrown when there is insufficient token balance for the requested operation error InsufficientBalanceForRebalance(); /// @notice Thrown when the trade tick is outside the oracle's acceptable range error InvalidTradeTick(); /// @notice Thrown when the trade slippage is too high error SlippageExceeded(); /*////////////////////////////////////////////////////////////// EVENTS //////////////////////////////////////////////////////////////*/ /** * @notice Emitted when an address is proposed for whitelisting * @param _address The address proposed for whitelisting */ event WhitelistProposed(address indexed _address); /** * @notice Emitted when a proposed address is accepted and added to the whitelist * @param _address The address that was accepted and whitelisted */ event WhitelistAccepted(address indexed _address); /** * @notice Emitted when a proposed address is rejected by the guardian or owner * @dev Can also be emitted when an address is removed from the whitelist by the guardian or owner * @param _address The address that was rejected */ event WhitelistRejected(address indexed _address); /** * @notice Emitted when a rebalancing operation is performed * @param target The target contract for rebalancing * @param isSell True if selling base token for quote token, false if buying base token with quote token * @param amountIn The amount of tokens received in the swap * @param amountOut The amount of tokens sent in the swap * @param baseBalanceAfter The balance of the base token after the rebalancing operation * @param quoteBalanceAfter The balance of the quote token after the rebalancing operation */ event Rebalanced( address indexed target, bool isSell, uint256 amountIn, uint256 amountOut, uint256 baseBalanceAfter, uint256 quoteBalanceAfter ); /*////////////////////////////////////////////////////////////// STATE VARIABLES //////////////////////////////////////////////////////////////*/ /// @notice Mapping to track which addresses are whitelisted for rebalancing operations mapping(address => bool) public isWhitelisted; /// @notice Mapping to track whitelist propositions with their timestamps /// @dev Maps address to the timestamp when it was proposed for whitelisting (0 = not proposed) /// @dev This mapping is used to implement timelock functionality for whitelist proposals mapping(address => uint40) public whitelistProposedAt; /*////////////////////////////////////////////////////////////// CONSTRUCTOR //////////////////////////////////////////////////////////////*/ /** * @notice Initializes the KandelManagementRebalancing contract * @param _seeder The Kandel seeder contract used to deploy the Kandel instance * @param _base The base token address for the trading pair * @param _quote The quote token address for the trading pair * @param _tickSpacing The tick spacing for the trading pair * @param _manager The initial manager address * @param _managementFee The management fee in basis points (10000 = 100%) * @param _oracle The initial oracle configuration * @param _owner The owner address (inherited from OracleRange) * @param _guardian The guardian address (inherited from OracleRange) * @dev Inherits all functionality from KandelManagement and adds whitelist capabilities */ constructor( AbstractKandelSeeder _seeder, address _base, address _quote, uint256 _tickSpacing, address _manager, uint16 _managementFee, OracleData memory _oracle, address _owner, address _guardian ) KandelManagement(_seeder, _base, _quote, _tickSpacing, _manager, _managementFee, _oracle, _owner, _guardian) {} /*////////////////////////////////////////////////////////////// REBALANCING FUNCTION //////////////////////////////////////////////////////////////*/ /** * @notice Parameters for rebalancing operations * @param isSell True if selling base token for quote token, false if buying base token with quote token * @param amountIn The amount of tokens to send for the swap * @param amountInKandel The amount of tokens to withdraw from the Kandel balance * @param amountOut The amount of tokens expected to receive from the swap * @param minAmountOut The minimum amount of tokens expected to receive (slippage protection) * @param target The whitelisted contract address to perform the swap * @param data The call data to send to the target contract for the swap */ struct RebalanceParams { bool isSell; uint256 amountIn; uint256 amountInKandel; uint256 minAmountOut; address target; bytes data; } /** * @notice Deposits any remaining tokens in the vault back to the Kandel strategy * @dev Checks balances of both base and quote tokens in the vault * @dev Only calls depositFunds if there are tokens to deposit * @dev Approves Kandel to spend tokens before depositing */ function _sendTokenToKandel() internal { uint256 baseBalance = BASE.balanceOf(address(this)); uint256 quoteBalance = QUOTE.balanceOf(address(this)); bool deposit; if (baseBalance > 0) { BASE.safeApprove(address(KANDEL), baseBalance); deposit = true; } if (quoteBalance > 0) { QUOTE.safeApprove(address(KANDEL), quoteBalance); deposit = true; } if (deposit) { KANDEL.depositFunds(baseBalance, quoteBalance); } } /** * @notice Performs a rebalancing operation by swapping tokens through a whitelisted target * @param _params The rebalancing parameters including swap direction, amounts, and target * @return sent The actual amount of tokens sent in the swap * @return received The actual amount of tokens received from the swap * @return callResult The return data from the target contract call * @dev Only callable by the manager * @dev Only whitelisted addresses can be used as swap targets * @dev Validates the trade price against oracle constraints * @dev Automatically deposits remaining tokens back to Kandel after the swap * @dev Reverts with InvalidRebalanceAddress if target is not whitelisted * @dev Reverts with InvalidTradeTick if the trade price is outside oracle range * @dev Reverts with InsufficientBalanceForRebalance if there are not enough tokens available */ function rebalance(RebalanceParams memory _params) external payable onlyManager nonReentrant returns (uint256 sent, uint256 received, bytes memory callResult) { if (!isWhitelisted[_params.target]) revert InvalidRebalanceAddress(); // take the tokens from the kandel KANDEL.withdrawFunds( FixedPointMathLib.ternary(_params.isSell, _params.amountInKandel, 0), FixedPointMathLib.ternary(_params.isSell, 0, _params.amountInKandel), address(this) ); address sellToken = FixedPointMathLib.ternary(_params.isSell, BASE, QUOTE); address buyToken = FixedPointMathLib.ternary(_params.isSell, QUOTE, BASE); // take a snapshot of the balances uint256 sellBalance = sellToken.balanceOf(address(this)); received = buyToken.balanceOf(address(this)); // check if we have enough balance for the swap if (sellBalance < _params.amountIn) revert InsufficientBalanceForRebalance(); // give allowance of the funds to the target sellToken.safeApprove(_params.target, _params.amountIn); // call the target contract callResult = _params.target.callContract(msg.value, _params.data); // get the amount of token we sent (if underflow, it means we received which is not expected) uint256 finalSentBalance = sellToken.balanceOf(address(this)); received = buyToken.balanceOf(address(this)) - received; // check if the amount received is greater than the minimum amount out if (received < _params.minAmountOut) revert SlippageExceeded(); if (finalSentBalance < sellBalance) { unchecked { sent = sellBalance - finalSentBalance; if (!oracle.acceptsTrade(_params.isSell, received, sent)) revert InvalidTradeTick(); } } // remove allowance sellToken.safeApprove(_params.target, 0); // send the tokens to the kandel if needed if (state.inKandel) _sendTokenToKandel(); // get the total balance after the rebalancing operation (uint256 baseBalanceAfter, uint256 quoteBalanceAfter) = totalBalances(); // emit the event emit Rebalanced(_params.target, _params.isSell, received, sent, baseBalanceAfter, quoteBalanceAfter); } /*////////////////////////////////////////////////////////////// WHITELIST FUNCTIONS //////////////////////////////////////////////////////////////*/ /** * @notice Proposes an address for whitelisting with timelock protection * @param _address The address to propose for whitelisting * @dev Only owner can propose addresses. The proposal must go through a timelock period * before it can be accepted. Guardian can reject during the timelock period. * @dev Reverts with AlreadyProposed if the address is already proposed or whitelisted */ function proposeWhitelist(address _address) external onlyOwner { if (!_canWhitelist(_address)) revert InvalidWhitelistAddress(); uint40 existing = whitelistProposedAt[_address]; bool isWhitelisted_ = isWhitelisted[_address]; if (existing > 0 || isWhitelisted_) revert AlreadyProposed(); whitelistProposedAt[_address] = uint40(block.timestamp); emit WhitelistProposed(_address); } /** * @notice Accepts a proposed address and adds it to the whitelist * @param _address The address to accept and whitelist * @dev Only owner can accept proposals. The proposal must have existed for at least * the timelock duration (oracle.timelockMinutes). Once accepted, the proposal * is cleared and the address is added to the whitelist. * @dev Reverts with NotProposed if the address was not proposed * @dev Reverts with TimelockNotExpired if the timelock period hasn't passed */ function acceptWhitelist(address _address) external onlyOwner { uint40 existing = whitelistProposedAt[_address]; if (existing == 0) revert NotProposed(); if (oracle.timelocked(existing)) { revert TimelockNotExpired(); } isWhitelisted[_address] = true; delete whitelistProposedAt[_address]; emit WhitelistAccepted(_address); } /** * @notice Rejects a proposed address and removes it from consideration or from the whitelist * @param _address The address to reject * @dev Only guardian can reject proposals. This clears the proposal without * adding the address to the whitelist. Guardian can reject at any time * during the timelock period. * @dev Reverts with NotProposed if the address was not proposed */ function rejectWhitelist(address _address) external { if (msg.sender != guardian) _checkOwner(); uint40 existing = whitelistProposedAt[_address]; bool isWhitelisted_ = isWhitelisted[_address]; if (existing == 0 && !isWhitelisted_) revert NotProposed(); if (existing > 0) delete whitelistProposedAt[_address]; if (isWhitelisted_) delete isWhitelisted[_address]; emit WhitelistRejected(_address); } /*////////////////////////////////////////////////////////////// INTERNAL FUNCTIONS //////////////////////////////////////////////////////////////*/ /** * @notice Validates whether an address can be whitelisted * @param _address The address to validate * @return canWhitelist True if the address can be whitelisted, false otherwise * @dev Virtual function that can be overridden in derived contracts for custom validation * @dev Current implementation prevents whitelisting the Kandel contract, base token, and quote token */ function _canWhitelist(address _address) internal view virtual returns (bool canWhitelist) { // Cannot whitelist the Kandel contract, base token, or quote token return !(_address == address(KANDEL) || _address == BASE || _address == QUOTE); } }lib/mangrove-strats/src/strategies/offer_maker/market_making/kandel/abstract/KandelLib.sol
// SPDX-License-Identifier: MIT pragma solidity ^0.8.10; import {OfferType} from "./TradesBaseQuotePair.sol"; import {MAX_TICK, MIN_TICK} from "@mgv/lib/core/Constants.sol"; import {DirectWithBidsAndAsksDistribution} from "./DirectWithBidsAndAsksDistribution.sol"; import {Tick} from "@mgv/lib/core/TickLib.sol"; ///@title Library of helper functions for Kandel, mainly to reduce deployed size. library KandelLib { ///@notice returns the destination index to transport received liquidity to - a better (for Kandel) price index for the offer type. ///@param ba the offer type to transport to ///@param index the price index one is willing to improve ///@param step the number of price steps improvements ///@param pricePoints the number of price points ///@return better destination index function transportDestination(OfferType ba, uint index, uint step, uint pricePoints) internal pure returns (uint better) { if (ba == OfferType.Ask) { better = index + step; if (better >= pricePoints) { better = pricePoints - 1; } } else { if (index >= step) { better = index - step; } // else better = 0 } } ///@notice Creates a distribution of bids and asks given by the parameters. Dual offers are included with gives=0. ///@param from populate offers starting from this index (inclusive). Must be at most `pricePoints`. ///@param to populate offers until this index (exclusive). Must be at most `pricePoints`. ///@param baseQuoteTickIndex0 the tick for the price point at index 0 given as a tick on the `base, quote` offer list, i.e. corresponding to an ask with a quote/base ratio. It is recommended that this is a multiple of tickSpacing for the offer lists to avoid rounding. ///@param _baseQuoteTickOffset the tick offset used for the geometric progression deployment. Must be at least 1. It is recommended that this is a multiple of tickSpacing for the offer lists to avoid rounding. ///@param firstAskIndex the (inclusive) index after which offer should be an ask. Must be at most `pricePoints`. ///@param bidGives The initial amount of quote to give for all bids. If 0, only book the offer, if type(uint).max then askGives is used as base for bids, and the quote the bid gives is set to according to the price. ///@param askGives The initial amount of base to give for all asks. If 0, only book the offer, if type(uint).max then bidGives is used as quote for asks, and the base the ask gives is set to according to the price. ///@param stepSize in amount of price points to jump for posting dual offer. Must be less than `pricePoints`. ///@param pricePoints the number of price points for the Kandel instance. Must be at least 2. ///@return distribution the distribution of bids and asks to populate ///@dev the absolute price of an offer is the ratio of quote/base volumes of tokens it trades ///@dev the tick of offers on Mangrove are in relative taker price of maker's inbound/outbound volumes of tokens it trades ///@dev for Bids, outbound_tkn=quote, inbound_tkn=base so relative taker price of a a bid is the inverse of the absolute price. ///@dev for Asks, outbound_tkn=base, inbound_tkn=quote so relative taker price of an ask coincides with absolute price. ///@dev Index0 will contain the ask with the lowest relative price and the bid with the highest relative price. Absolute price is geometrically increasing over indexes. ///@dev tickOffset moves an offer relative price s.t. `AskTick_{i+1} = AskTick_i + tickOffset` and `BidTick_{i+1} = BidTick_i - tickOffset` ///@dev A hole is left in the middle at the size of stepSize - either an offer or its dual is posted, not both. ///@dev The caller should make sure the minimum and maximum tick does not exceed the MIN_TICK and MAX_TICK from respectively; otherwise, populate will fail for those offers. ///@dev If type(uint).max is used for `bidGives` or `askGives` then very high or low prices can yield gives=0 (which results in both offer an dual being dead) or gives>=type(uin96).max which is not supported by Mangrove. function createGeometricDistribution( uint from, uint to, Tick baseQuoteTickIndex0, uint _baseQuoteTickOffset, uint firstAskIndex, uint bidGives, uint askGives, uint pricePoints, uint stepSize ) external pure returns (DirectWithBidsAndAsksDistribution.Distribution memory distribution) { require(bidGives != type(uint).max || askGives != type(uint).max, "Kandel/bothGivesVariable"); // First we restrict boundaries of bids and asks. // Create live bids up till first ask, except stop where live asks will have a dual bid. uint bidBound; { // Rounding - we skip an extra live bid if stepSize is odd. uint bidHoleSize = stepSize / 2 + stepSize % 2; // If first ask is close to start, then there are no room for live bids. bidBound = firstAskIndex > bidHoleSize ? firstAskIndex - bidHoleSize : 0; // If stepSize is large there is not enough room for dual outside uint lastBidWithPossibleDualAsk = pricePoints - stepSize; if (bidBound > lastBidWithPossibleDualAsk) { bidBound = lastBidWithPossibleDualAsk; } } // Here firstAskIndex becomes the index of the first actual ask, and not just the boundary - we need to take `stepSize` and `from` into account. firstAskIndex = firstAskIndex + stepSize / 2; // We should not place live asks near the beginning, there needs to be room for the dual bid. if (firstAskIndex < stepSize) { firstAskIndex = stepSize; } // Finally, account for the from/to boundaries if (to < bidBound) { bidBound = to; } if (firstAskIndex < from) { firstAskIndex = from; } // Allocate distributions - there should be room for live bids and asks, and their duals. { uint count = (from < bidBound ? bidBound - from : 0) + (firstAskIndex < to ? to - firstAskIndex : 0); distribution.bids = new DirectWithBidsAndAsksDistribution.DistributionOffer[](count); distribution.asks = new DirectWithBidsAndAsksDistribution.DistributionOffer[](count); } // Start bids at from uint index = from; // Calculate the taker relative tick of the first price point int tick = -(Tick.unwrap(baseQuoteTickIndex0) + int(_baseQuoteTickOffset) * int(index)); // A counter for insertion in the distribution structs uint i = 0; for (; index < bidBound; ++index) { // Add live bid // Use askGives unless it should be derived from bid at the price distribution.bids[i] = DirectWithBidsAndAsksDistribution.DistributionOffer({ index: index, tick: Tick.wrap(tick), gives: bidGives == type(uint).max ? Tick.wrap(tick).outboundFromInbound(askGives) : bidGives }); // Add dual (dead) ask uint dualIndex = transportDestination(OfferType.Ask, index, stepSize, pricePoints); distribution.asks[i] = DirectWithBidsAndAsksDistribution.DistributionOffer({ index: dualIndex, tick: Tick.wrap((Tick.unwrap(baseQuoteTickIndex0) + int(_baseQuoteTickOffset) * int(dualIndex))), gives: 0 }); // Next tick tick -= int(_baseQuoteTickOffset); ++i; } // Start asks from (adjusted) firstAskIndex index = firstAskIndex; // Calculate the taker relative tick of the first ask tick = (Tick.unwrap(baseQuoteTickIndex0) + int(_baseQuoteTickOffset) * int(index)); for (; index < to; ++index) { // Add live ask // Use askGives unless it should be derived from bid at the price distribution.asks[i] = DirectWithBidsAndAsksDistribution.DistributionOffer({ index: index, tick: Tick.wrap(tick), gives: askGives == type(uint).max ? Tick.wrap(tick).outboundFromInbound(bidGives) : askGives }); // Add dual (dead) bid uint dualIndex = transportDestination(OfferType.Bid, index, stepSize, pricePoints); distribution.bids[i] = DirectWithBidsAndAsksDistribution.DistributionOffer({ index: dualIndex, tick: Tick.wrap(-(Tick.unwrap(baseQuoteTickIndex0) + int(_baseQuoteTickOffset) * int(dualIndex))), gives: 0 }); // Next tick tick += int(_baseQuoteTickOffset); ++i; } } }src/base/KandelManagement.sol
// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; // External dependencies (Mangrove protocol) import {AbstractKandelSeeder} from "@mgv-strats/src/strategies/offer_maker/market_making/kandel/abstract/AbstractKandelSeeder.sol"; import {GeometricKandel} from "@mgv-strats/src/strategies/offer_maker/market_making/kandel/abstract/GeometricKandel.sol"; import {DirectWithBidsAndAsksDistribution} from "@mgv-strats/src/strategies/offer_maker/market_making/kandel/abstract/DirectWithBidsAndAsksDistribution.sol"; import {CoreKandel} from "@mgv-strats/src/strategies/offer_maker/market_making/kandel/abstract/CoreKandel.sol"; import {OfferType} from "@mgv-strats/src/strategies/offer_maker/market_making/kandel/abstract/TradesBaseQuotePair.sol"; import {OLKey} from "@mgv/src/core/MgvLib.sol"; import {Tick} from "@mgv/lib/core/TickLib.sol"; import {MAX_TICK} from "@mgv/lib/core/Constants.sol"; // External dependencies (solady) import {SafeTransferLib} from "lib/solady/src/utils/SafeTransferLib.sol"; import {FixedPointMathLib} from "lib/solady/src/utils/FixedPointMathLib.sol"; // Internal dependencies import {OracleRange} from "./OracleRange.sol"; import {OracleData, OracleLib} from "../libraries/OracleLib.sol"; /** * @title KandelManagement * @notice Manages a Kandel market-making strategy with oracle-based position validation * @dev This contract extends OracleRange to provide oracle-validated Kandel strategy management. * It ensures that all Kandel positions respect oracle-defined price ranges by validating * bid and ask distributions before deployment. * @author Mangrove */ contract KandelManagement is OracleRange { using OracleLib for OracleData; using SafeTransferLib for address; /*////////////////////////////////////////////////////////////// ERRORS //////////////////////////////////////////////////////////////*/ /// @notice Thrown when caller is not the manager error NotManager(); /// @notice Thrown when the proposed distribution doesn't respect oracle constraints error InvalidDistribution(); /// @notice Thrown when the management fee exceeds the maximum allowed fee error MaxManagementFeeExceeded(); /*////////////////////////////////////////////////////////////// EVENTS //////////////////////////////////////////////////////////////*/ /** * @notice Emitted when a new manager is set * @param manager The address of the new manager */ event SetManager(address indexed manager); /** * @notice Emitted when fee data is updated * @param feeRecipient The address of the new fee recipient * @param managementFee The new management fee in basis points */ event SetFeeData(address indexed feeRecipient, uint16 managementFee); /** * @notice Emitted when funds are deposited into the Kandel strategy * @dev This indicates that funds are now actively managed by the Kandel market-making strategy */ event FundsEnteredKandel(); /** * @notice Emitted when funds are withdrawn from the Kandel strategy * @dev This indicates that funds are no longer actively managed by Kandel and returned to the vault */ event FundsExitedKandel(); /*////////////////////////////////////////////////////////////// CONSTANTS //////////////////////////////////////////////////////////////*/ /// @notice Maximum allowed management fee (precision is 100_000) uint16 private constant MAX_MANAGEMENT_FEE = 10_000; // 10% /*////////////////////////////////////////////////////////////// IMMUTABLE VARIABLES //////////////////////////////////////////////////////////////*/ /// @notice The Kandel strategy instance managed by this contract GeometricKandel public immutable KANDEL; /// @notice The base token address for the trading pair address internal immutable BASE; /// @notice The quote token address for the trading pair address internal immutable QUOTE; /// @notice The tick spacing for the trading pair uint256 internal immutable TICK_SPACING; /*////////////////////////////////////////////////////////////// STRUCTS //////////////////////////////////////////////////////////////*/ /** * @notice Packed state variables for gas optimization * @param inKandel Whether the contract is currently managing Kandel positions * @param feeRecipient Address that receives management fees * @param managementFee Management fee in basis points (10000 = 100%) * @param lastTimestamp Last timestamp when state was updated * @param paused Whether the contract is paused (can be used for minting/burning) */ struct State { bool inKandel; // 8 bits address feeRecipient; // 160 bits -> 168 bits uint16 managementFee; // 16 bits -> 184 bits uint40 lastTimestamp; // 40 bits -> 224 bits bool paused; // 8 bits -> 232 bits } /*////////////////////////////////////////////////////////////// STATE VARIABLES //////////////////////////////////////////////////////////////*/ /// @notice Address that can execute Kandel management operations /// @dev Manager has operational control while owner has governance control address public manager; /// @notice Packed state variables for the contract State public state; /*////////////////////////////////////////////////////////////// MODIFIERS //////////////////////////////////////////////////////////////*/ /** * @notice Restricts function access to the manager only * @dev Reverts with NotManager if caller is not the manager */ modifier onlyManager() { if (msg.sender != manager) revert NotManager(); _; } /*////////////////////////////////////////////////////////////// CONSTRUCTOR //////////////////////////////////////////////////////////////*/ /** * @notice Initializes the KandelManagement contract * @param seeder The Kandel seeder contract used to deploy the Kandel instance * @param base The base token address for the trading pair * @param quote The quote token address for the trading pair * @param tickSpacing The tick spacing for the trading pair * @param _manager The initial manager address * @param _managementFee The management fee in basis points (10000 = 100%) * @param _oracle The initial oracle configuration * @param _owner The owner address (inherited from OracleRange) * @param _guardian The guardian address (inherited from OracleRange) * @dev Deploys a new GeometricKandel instance through the seeder * @dev Emits SetManager and SetFeeData events for initial state indexing */ constructor( AbstractKandelSeeder seeder, address base, address quote, uint256 tickSpacing, address _manager, uint16 _managementFee, OracleData memory _oracle, address _owner, address _guardian ) OracleRange(_oracle, _owner, _guardian) { BASE = base; QUOTE = quote; TICK_SPACING = tickSpacing; manager = _manager; // Validate management fee if (_managementFee > MAX_MANAGEMENT_FEE) revert MaxManagementFeeExceeded(); // Initialize state with default values state = State({ inKandel: false, feeRecipient: _owner, managementFee: _managementFee, lastTimestamp: uint40(block.timestamp), paused: false }); // Deploy Kandel instance with reneging disabled (false parameter) KANDEL = seeder.sow(OLKey(base, quote, tickSpacing), false); // Emit events for initial state for indexing purposes emit SetManager(_manager); emit SetFeeData(_owner, _managementFee); } /*////////////////////////////////////////////////////////////// EXTERNAL FUNCTIONS //////////////////////////////////////////////////////////////*/ /** * @notice Sets a new manager for the contract * @param _manager The address of the new manager * @dev Only owner can set the manager. Manager has operational control over Kandel functions */ function setManager(address _manager) external onlyOwner { manager = _manager; emit SetManager(_manager); } /** * @notice Sets fee data (recipient and management fee) for the contract * @param _feeRecipient The address of the new fee recipient * @param _managementFee The new management fee in basis points (10000 = 100%) * @dev Only owner can set fee data. Fee recipient receives management fees * @dev Virtual function can be overridden by inheriting contracts */ function setFeeData(address _feeRecipient, uint16 _managementFee) public virtual onlyOwner { if (_managementFee > MAX_MANAGEMENT_FEE) revert MaxManagementFeeExceeded(); state.feeRecipient = _feeRecipient; state.managementFee = _managementFee; emit SetFeeData(_feeRecipient, _managementFee); } /** * @notice Populates Kandel offers from scratch with oracle validation * @param from The starting index for offer placement * @param to The ending index for offer placement * @param baseQuoteTickIndex0 The base quote tick index at position 0 * @param _baseQuoteTickOffset The tick offset between price points * @param firstAskIndex The index where asks start (bids are below this) * @param bidGives The amount of quote token each bid offer gives * @param askGives The amount of base token each ask offer gives * @param parameters The Kandel parameters (gas price, gas req, step size, price points) * @dev Validates the distribution against oracle constraints before populating * @dev Requires ETH payment for offer provisioning (passed via msg.value) */ function populateFromOffset( uint256 from, uint256 to, Tick baseQuoteTickIndex0, uint256 _baseQuoteTickOffset, uint256 firstAskIndex, uint256 bidGives, uint256 askGives, CoreKandel.Params calldata parameters ) external payable onlyManager { // Create distribution based on provided parameters DirectWithBidsAndAsksDistribution.Distribution memory distribution = _createDistribution( from, to, baseQuoteTickIndex0, _baseQuoteTickOffset, firstAskIndex, bidGives, askGives, parameters ); // Set inKandel to true when populating (emit event only if state changes) if (!state.inKandel) { state.inKandel = true; emit FundsEnteredKandel(); } // Deposit all available funds to the Kandel strategy uint256 baseBalance = BASE.balanceOf(address(this)); uint256 quoteBalance = QUOTE.balanceOf(address(this)); // Approve Kandel to spend our tokens for offer funding if (baseBalance > 0) { BASE.safeApprove(address(KANDEL), baseBalance); } if (quoteBalance > 0) { QUOTE.safeApprove(address(KANDEL), quoteBalance); } // Populate Kandel with validated distribution, depositing all available funds KANDEL.populate{value: msg.value}(distribution, parameters, baseBalance, quoteBalance); } /** * @notice Populates a chunk of existing Kandel offers with oracle validation * @param from The starting index for offer placement * @param to The ending index for offer placement * @param baseQuoteTickIndex0 The base quote tick index at position 0 * @param firstAskIndex The index where asks start (bids are below this) * @param bidGives The amount of quote token each bid offer gives * @param askGives The amount of base token each ask offer gives * @dev Uses existing Kandel parameters and validates against oracle constraints * @dev No ETH payment required as this updates existing offers */ function populateChunkFromOffset( uint256 from, uint256 to, Tick baseQuoteTickIndex0, uint256 firstAskIndex, uint256 bidGives, uint256 askGives ) external onlyManager { // Get current Kandel parameters CoreKandel.Params memory parameters = _params(); uint256 baseQuoteTickOffset = KANDEL.baseQuoteTickOffset(); // Create distribution with current parameters DirectWithBidsAndAsksDistribution.Distribution memory distribution = _createDistribution( from, to, baseQuoteTickIndex0, baseQuoteTickOffset, firstAskIndex, bidGives, askGives, parameters ); // Update Kandel chunk with validated distribution KANDEL.populateChunk(distribution); } /** * @notice Retracts Kandel offers from the market with optional fund and provision withdrawal * @param from The starting index of offers to retract * @param to The ending index of offers to retract * @param baseAmount Amount of base tokens to withdraw (0 = no withdrawal, type(uint256).max = maximum) * @param quoteAmount Amount of quote tokens to withdraw (0 = no withdrawal, type(uint256).max = maximum) * @param withdrawProvisions Whether to withdraw ETH provisions from Mangrove to manager * @param recipient The address to which the withdrawn provisions should be sent to. * @dev Retracting offers removes them from the market but keeps funds in Kandel unless withdrawn * @dev If both baseAmount and quoteAmount are 0, no funds are withdrawn and inKandel remains true * @dev If either amount is non-zero, funds are withdrawn and inKandel is set to false * @dev Passing type(uint256).max for amounts will withdraw maximum available tokens */ function retractOffers( uint256 from, uint256 to, uint256 baseAmount, uint256 quoteAmount, bool withdrawProvisions, address recipient ) external onlyManager { unchecked { KANDEL.retractAndWithdraw( from, to, baseAmount, quoteAmount, FixedPointMathLib.ternary(withdrawProvisions, type(uint256).max, 0), payable(address(this)) ); if (baseAmount > 0 || quoteAmount > 0) { if (state.inKandel) { state.inKandel = false; emit FundsExitedKandel(); } } if (withdrawProvisions) { recipient.safeTransferAllETH(); } } } /** * @notice Withdraws token funds from Kandel strategy back to this contract * @param baseAmount Amount of base tokens to withdraw (use type(uint256).max for maximum) * @param quoteAmount Amount of quote tokens to withdraw (use type(uint256).max for maximum) * @dev Passing type(uint256).max will withdraw the maximum available amount for each token * @dev Not passing the full amount can be useful if underlying vault strategies don't have all liquidity available yet * @dev Always sets inKandel state to false, meaning any new funds will go to the vault and not the Kandel */ function withdrawFunds(uint256 baseAmount, uint256 quoteAmount) external onlyManager { KANDEL.withdrawFunds(baseAmount, quoteAmount, address(this)); if (state.inKandel) { state.inKandel = false; emit FundsExitedKandel(); } } /** * @notice Withdraws ETH provisions from Mangrove to the manager * @param freeWei Amount of ETH (in wei) to withdraw from Mangrove provisions * @param recipient The address to which the withdrawn provisions should be sent to. * @dev This withdraws ETH that was deposited for offer gas provisioning */ function withdrawFromMangrove(uint256 freeWei, address payable recipient) external onlyManager { KANDEL.withdrawFromMangrove(freeWei, recipient); } /*////////////////////////////////////////////////////////////// VIEW FUNCTIONS //////////////////////////////////////////////////////////////*/ /** * @notice Returns the token balances held in this management contract (vault) * @return baseBalance The amount of base tokens in the management contract * @return quoteBalance The amount of quote tokens in the management contract * @dev These are tokens that are not yet deposited into the Kandel strategy */ function vaultBalances() public view returns (uint256 baseBalance, uint256 quoteBalance) { baseBalance = BASE.balanceOf(address(this)); quoteBalance = QUOTE.balanceOf(address(this)); } /** * @notice Returns the token balances held in the Kandel strategy * @return baseBalance The amount of base tokens in the Kandel strategy * @return quoteBalance The amount of quote tokens in the Kandel strategy * @dev These are tokens actively used by the Kandel strategy for market making */ function kandelBalances() public view returns (uint256 baseBalance, uint256 quoteBalance) { // Ask offers sell base tokens, so reserveBalance for asks returns base token balance baseBalance = KANDEL.reserveBalance(OfferType.Ask); // Bid offers sell quote tokens, so reserveBalance for bids returns quote token balance quoteBalance = KANDEL.reserveBalance(OfferType.Bid); } /** * @notice Returns the total token balances across both vault and Kandel strategy * @return baseBalance The total amount of base tokens (vault + Kandel) * @return quoteBalance The total amount of quote tokens (vault + Kandel) * @dev This represents the total underlying assets controlled by this management contract */ function totalBalances() public view returns (uint256 baseBalance, uint256 quoteBalance) { (uint256 vaultBase, uint256 vaultQuote) = vaultBalances(); (uint256 kandelBase, uint256 kandelQuote) = kandelBalances(); baseBalance = vaultBase + kandelBase; quoteBalance = vaultQuote + kandelQuote; } /** * @notice Returns the market configuration for this Kandel management contract * @return base The base token address * @return quote The quote token address * @return tickSpacing The tick spacing for the market * @dev This information defines the trading pair and market parameters */ function market() external view returns (address base, address quote, uint256 tickSpacing) { base = BASE; quote = QUOTE; tickSpacing = TICK_SPACING; } /*////////////////////////////////////////////////////////////// INTERNAL FUNCTIONS //////////////////////////////////////////////////////////////*/ /** * @notice Finds the minimum tick from an array of distribution offers * @param offers Array of distribution offers to search * @return Tick The minimum tick value found, or MAX_TICK if no valid offers * @dev Skips offers with zero gives amount as they are inactive */ function _minTick(DirectWithBidsAndAsksDistribution.DistributionOffer[] memory offers) private pure returns (Tick) { int256 minTick = MAX_TICK; for (uint256 i = 0; i < offers.length; i++) { // Skip inactive offers (zero gives) if (offers[i].gives == 0) continue; minTick = FixedPointMathLib.min(minTick, Tick.unwrap(offers[i].tick)); } return Tick.wrap(minTick); } /** * @notice Creates and validates a Kandel offer distribution with oracle price constraints * @param from The starting index of the price range (inclusive) * @param to The ending index of the price range (exclusive) * @param baseQuoteTickIndex0 The tick index for the base/quote price at index 0 * @param _baseQuoteTickOffset The tick offset between consecutive price points * @param firstAskIndex The index where ask offers begin (bids are placed before this index) * @param bidGives The amount of quote tokens each bid offer should give (type(uint256).max = derive from askGives at current price) * @param askGives The amount of base tokens each ask offer should give (type(uint256).max = derive from bidGives at current price) * @param parameters The Kandel parameters containing pricePoints and stepSize * @return distribution The validated distribution containing arrays of bid and ask offers * @dev This function acts as a wrapper around KANDEL.createDistribution() with additional oracle validation * @dev The distribution contains: * - bids: Array of bid offers (selling quote tokens for base tokens) * - asks: Array of ask offers (selling base tokens for quote tokens) * @dev Each offer in the distribution includes: * - index: The position in the Kandel price grid * - tick: The price tick for this offer * - gives: The amount of tokens this offer provides (0 = inactive/dead offer) * @dev Oracle validation ensures that: * - The minimum ask tick is within oracle.maxDeviation of oracle price * - The minimum bid tick is within oracle.maxDeviation of oracle price * - This prevents creating distributions with prices too far from market rates * @dev Reverts with InvalidDistribution if the distribution's price range violates oracle constraints * @dev Used internally by populateFromOffset() and populateChunk() to ensure all distributions respect oracle bounds */ function _createDistribution( uint256 from, uint256 to, Tick baseQuoteTickIndex0, uint256 _baseQuoteTickOffset, uint256 firstAskIndex, uint256 bidGives, uint256 askGives, CoreKandel.Params memory parameters ) internal view returns (DirectWithBidsAndAsksDistribution.Distribution memory distribution) { unchecked { distribution = KANDEL.createDistribution( from, to, baseQuoteTickIndex0, _baseQuoteTickOffset, firstAskIndex, bidGives, askGives, parameters.pricePoints, parameters.stepSize ); OracleData memory _oracle = oracle; if (!_oracle.accepts(_minTick(distribution.asks), _minTick(distribution.bids))) revert InvalidDistribution(); return distribution; } } /** * @notice Retrieves current Kandel parameters * @return params The current Kandel parameters struct * @dev Fetches gas price, gas requirement, step size, and price points from Kandel */ function _params() private view returns (CoreKandel.Params memory params) { address target = address(KANDEL); /// @solidity memory-safe-assembly assembly { mstore(params, 0xcff0ab96) // params() if iszero(staticcall(gas(), target, add(params, 0x1c), 0x04, params, 0x80)) { returndatacopy(params, 0x00, returndatasize()) revert(params, returndatasize()) } } } /** * @notice Receives ETH from Mangrove provisions * @dev This function is used to receive ETH from Mangrove provisions */ receive() external payable {} }lib/mangrove-core/lib/IERC20.sol
// SPDX-License-Identifier: MIT pragma solidity >=0.6.2; interface IERC20 { function totalSupply() external view returns (uint); function balanceOf(address account) external view returns (uint); function transfer(address recipient, uint amount) external returns (bool); function allowance(address owner, address spender) external view returns (uint); function approve(address spender, uint amount) external returns (bool); function transferFrom(address sender, address recipient, uint amount) external returns (bool); function symbol() external view returns (string memory); event Transfer(address indexed from, address indexed to, uint value); event Approval(address indexed owner, address indexed spender, uint value); function decimals() external view returns (uint8); function name() external view returns (string memory); }lib/mangrove-core/lib/core/Constants.sol
// SPDX-License-Identifier: MIT pragma solidity ^0.8.17; /* The constants below are written as literals to optimize gas. For all the relevant constants here, the non-literal expression that computes them is checked in `Constants.t.sol`. */ uint constant ONE = 1; uint constant ONES = type(uint).max; uint constant TOPBIT = 0x8000000000000000000000000000000000000000000000000000000000000000; uint constant NOT_TOPBIT = 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; /* **sizes must match field sizes in `structs.ts` where relevant** */ /* Number of bits used to represent a tick */ uint constant TICK_BITS = 21; /* Number of bits used to represent an offer id */ uint constant OFFER_BITS = 32; /* Maximum possible size of a field -- protects against wrong code changes. Constraint given by `BitLib.ctz64`. */ uint constant MAX_FIELD_SIZE = 64; /* `X_SIZE_BITS` is 1+log2 of the size of `X`, where size is the number of elements it holds.In a field, an element is a bit; in a leaf, an element is a pair of offer ids. For `LEVEL`s and `ROOT`, the value must be exact, so only power-of-2 sizes are allowed. */ uint constant LEAF_SIZE_BITS = 2; uint constant LEVEL_SIZE_BITS = 6; uint constant ROOT_SIZE_BITS = 1; /* `X_SIZE` is `2**X_SIZE_BITS` */ int constant LEAF_SIZE = 4; int constant LEVEL_SIZE = 64; int constant ROOT_SIZE = 2; /* `X_SIZE_MASK` is `0...01...1` where the number of 1s is `X_SIZE_BITS` */ uint constant LEAF_SIZE_MASK = 0x3; uint constant LEVEL_SIZE_MASK = 0x3f; uint constant ROOT_SIZE_MASK = 0x1; /* `0...01...1` with `OFFER_BITS` 1s at the end */ uint constant OFFER_MASK = 0xffffffff; /* Same as `ROOT_SIZE` */ int constant NUM_LEVEL1 = 2; /* Same as `NUM_LEVEL1 * LEVEL_SIZE` */ int constant NUM_LEVEL2 = 128; /* Same as `NUM_LEVEL2 * LEVEL_SIZE` */ int constant NUM_LEVEL3 = 8192; /* Same as `NUM_LEVEL3 * LEVEL_SIZE` */ int constant NUM_LEAFS = 524288; /* Same as `NUM_LEAFS * LEAF` */ int constant NUM_BINS = 2097152; /* min and max bins are defined like min and max int. */ int constant MIN_BIN = -1048576; int constant MAX_BIN = 1048575; /* The tick range is the largest such that the mantissa of `1.0001^MAX_TICK` fits on 128 bits (and thus can be multiplied by volumes). */ int constant MIN_TICK = -887272; int constant MAX_TICK = 887272; /* These are reference values for what the function `tickFromRatio` function will return, not the most possible accurate values for the min and max tick. */ uint constant MIN_RATIO_MANTISSA = 170153974464283981435225617938057077692; int constant MIN_RATIO_EXP = 255; uint constant MAX_RATIO_MANTISSA = 340256786836388094050805785052946541084; int constant MAX_RATIO_EXP = 0; /* `MANTISSA_BITS` is the number of bits used in the mantissa of normalized floats that represent ratios. 128 means we can multiply all allowed volumes by the mantissa and not overflow. */ uint constant MANTISSA_BITS = 128; uint constant MANTISSA_BITS_MINUS_ONE = 127; /* With `|tick|<=887272` and normalized mantissas on 128 bits, the maximum possible mantissa is `340282295208261841796968287475569060645`, so the maximum safe volume before overflow is `NO_OVERFLOW_AMOUNT = 340282438633630198193436196978374475856` (slightly above `2**128`). For ease of use, we could pick a simpler, slightly smaller max safe volume: `(1<<128)-1`. However the `*ByVolume` functions get a price by (abstractly) performing `outboundAmount/inboundAmount`. If we limit all volumes to `NO_OVERFLOW_AMOUNT` but aren't more restrictive than that, since `type(uint128).max > 1.0001**MAX_TICK`, we will get ratios that are outside the price boundaries. We thus pick a uniform, easy to remember constraint on volumes that works everywhere: `(1<<127)-1` */ uint constant MAX_SAFE_VOLUME = 170141183460469231731687303715884105727; /* When a market order consumes offers, the implementation uses recursion consumes additional EVM stack space at each new offer. To avoid reverting due to stack overflow, Mangrove keeps a counter and stops the market order when it reaches a maximum recursion depth. `INITIAL_MAX_RECURSION_DEPTH` is the maximum recursion depth given at deployment time. See `maxRecursionDepth` in `structs.ts` Without optimizer enabled it fails above 79. With optimizer and 200 runs it fails above 80. Set default a bit lower to be safe. */ uint constant INITIAL_MAX_RECURSION_DEPTH = 75; /* When a market order consumes offers, it may use gas on offers that fail to deliver. To avoid reverts after a string of failing offers that consumes more gas than is available in a block, Mangrove stops a market order after it has gone through failing offers such that their cumulative `gasreq` is greater than the global `maxGasreqForFailingOffers` parameter. At deployment, `maxGasreqForFailingOffers` is set to: ``` INITIAL_MAX_GASREQ_FOR_FAILING_OFFERS_MULTIPLIER * gasmax ``` */ uint constant INITIAL_MAX_GASREQ_FOR_FAILING_OFFERS_MULTIPLIER = 3; /* Those two constants are used in `TickLib.ratioFromTick` to convert a log base 1.0001 to a log base 2. */ uint constant LOG_BP_SHIFT = 235; uint constant LOG_BP_2X235 = 382733217082594961806491056566382061424140926068392360945012727618364717537;lib/mangrove-core/lib/core/OfferDetailExtra.sol
// SPDX-License-Identifier: MIT pragma solidity ^0.8.17; import {OfferDetail,OfferDetailUnpacked} from "@mgv/src/preprocessed/OfferDetail.post.sol"; /* Extra functions for the offer details */ library OfferDetailExtra { function offer_gasbase(OfferDetail offerDetail) internal pure returns (uint) { unchecked { return offerDetail.kilo_offer_gasbase() * 1e3; }} function offer_gasbase(OfferDetail offerDetail,uint val) internal pure returns (OfferDetail) { unchecked { return offerDetail.kilo_offer_gasbase(val/1e3); }} } /* Extra functions for the struct version of the offer details */ library OfferDetailUnpackedExtra { function offer_gasbase(OfferDetailUnpacked memory offerDetail) internal pure returns (uint) { unchecked { return offerDetail.kilo_offer_gasbase * 1e3; }} function offer_gasbase(OfferDetailUnpacked memory offerDetail,uint val) internal pure { unchecked { offerDetail.kilo_offer_gasbase = val/1e3; }} }lib/mangrove-strats/src/strategies/offer_maker/market_making/kandel/abstract/AbstractKandelSeeder.sol
// SPDX-License-Identifier: MIT pragma solidity ^0.8.10; import {GeometricKandel} from "./GeometricKandel.sol"; import {IMangrove} from "@mgv/src/IMangrove.sol"; import {OLKey, Local} from "@mgv/src/core/MgvLib.sol"; ///@title Abstract Kandel strat deployer. ///@notice This seeder deploys Kandel strats on demand and binds them to an AAVE router if needed. ///@dev deployer of this contract will gain aave manager power on the AAVE router (power to claim rewards and enter/exit markets) ///@dev when deployer is a contract one must therefore make sure it is able to call the corresponding functions on the router abstract contract AbstractKandelSeeder { ///@notice The Mangrove deployment. IMangrove public immutable MGV; ///@notice the gasreq to use for offers. uint public immutable KANDEL_GASREQ; ///@notice constructor for `AbstractKandelSeeder`. ///@param mgv The Mangrove deployment. ///@param kandelGasreq the gasreq to use for offers constructor(IMangrove mgv, uint kandelGasreq) { MGV = mgv; KANDEL_GASREQ = kandelGasreq; } ///@notice deploys a new Kandel contract for the given seed parameters. ///@param olKeyBaseQuote The OLKey for the outbound_tkn base and inbound_tkn quote offer list Kandel will act on, the flipped OLKey is used for the opposite offer list. ///@param liquiditySharing if true, `msg.sender` will be used to identify the shares of the deployed Kandel strat. If msg.sender deploys several instances, reserve of the strats will be shared, but this will require a transfer from router to maker contract for each taken offer, since we cannot transfer the full amount to the first maker contract hit in a market order in case later maker contracts need the funds. Still, only a single AAVE redeem will take place. ///@return kandel the Kandel contract. function sow(OLKey memory olKeyBaseQuote, bool liquiditySharing) external returns (GeometricKandel kandel) { // Seeder must set Kandel owner to an address that is controlled by `msg.sender` (msg.sender or Kandel's address for instance) // owner MUST not be freely chosen (it is immutable in Kandel) otherwise one would allow the newly deployed strat to pull from another's strat reserve // allowing owner to be modified by Kandel's admin would require approval from owner's address controller (, Local local) = MGV.config(olKeyBaseQuote); (, Local local_) = MGV.config(olKeyBaseQuote.flipped()); require(local.active() && local_.active(), "KandelSeeder/inactiveMarket"); kandel = _deployKandel(olKeyBaseQuote, liquiditySharing); kandel.setAdmin(msg.sender); } ///@notice deploys a new Kandel contract for the given seed parameters. ///@param olKeyBaseQuote The OLKey for the outbound_tkn base and inbound_tkn quote offer list Kandel will act on, the flipped OLKey is used for the opposite offer list. ///@param liquiditySharing if true, `msg.sender` will be used to identify the shares of the deployed Kandel strat. If msg.sender deploys several instances, reserve of the strats will be shared, but this will require a transfer from router to maker contract for each taken offer, since we cannot transfer the full amount to the first maker contract hit in a market order in case later maker contracts need the funds. Still, only a single AAVE redeem will take place. ///@return kandel the Kandel contract. function _deployKandel(OLKey memory olKeyBaseQuote, bool liquiditySharing) internal virtual returns (GeometricKandel kandel); }
ABI · 111 entries
json
[
{
"type": "constructor",
"inputs": [
{
"name": "_params",
"type": "tuple",
"components": [
{
"name": "seeder",
"type": "address",
"internalType": "contract AbstractKandelSeeder"
},
{
"name": "base",
"type": "address",
"internalType": "address"
},
{
"name": "quote",
"type": "address",
"internalType": "address"
},
{
"name": "tickSpacing",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "manager",
"type": "address",
"internalType": "address"
},
{
"name": "managementFee",
"type": "uint16",
"internalType": "uint16"
},
{
"name": "oracle",
"type": "tuple",
"components": [
{
"name": "isStatic",
"type": "bool",
"internalType": "bool"
},
{
"name": "oracle",
"type": "address",
"internalType": "contract IOracle"
},
{
"name": "staticValue",
"type": "int24",
"internalType": "int24"
},
{
"name": "maxDeviation",
"type": "uint16",
"internalType": "uint16"
},
{
"name": "proposedAt",
"type": "uint40",
"internalType": "uint40"
},
{
"name": "timelockMinutes",
"type": "uint8",
"internalType": "uint8"
}
],
"internalType": "struct OracleData"
},
{
"name": "owner",
"type": "address",
"internalType": "address"
},
{
"name": "guardian",
"type": "address",
"internalType": "address"
},
{
"name": "name",
"type": "string",
"internalType": "string"
},
{
"name": "symbol",
"type": "string",
"internalType": "string"
},
{
"name": "quoteOffsetDecimals",
"type": "uint8",
"internalType": "uint8"
}
],
"internalType": "struct MangroveVaultV2.VaultInitParams"
}
],
"stateMutability": "nonpayable"
},
{
"name": "AllowanceOverflow",
"type": "error",
"inputs": []
},
{
"name": "AllowanceUnderflow",
"type": "error",
"inputs": []
},
{
"name": "AlreadyInitialized",
"type": "error",
"inputs": []
},
{
"name": "AlreadyProposed",
"type": "error",
"inputs": []
},
{
"name": "BurnSlippageExceeded",
"type": "error",
"inputs": []
},
{
"name": "InsufficientAllowance",
"type": "error",
"inputs": []
},
{
"name": "InsufficientBalance",
"type": "error",
"inputs": []
},
{
"name": "InsufficientBalanceForRebalance",
"type": "error",
"inputs": []
},
{
"name": "InsufficientSharesOut",
"type": "error",
"inputs": []
},
{
"name": "InvalidDistribution",
"type": "error",
"inputs": []
},
{
"name": "InvalidInitialMintAmounts",
"type": "error",
"inputs": []
},
{
"name": "InvalidOracle",
"type": "error",
"inputs": []
},
{
"name": "InvalidPermit",
"type": "error",
"inputs": []
},
{
"name": "InvalidRebalanceAddress",
"type": "error",
"inputs": []
},
{
"name": "InvalidTradeTick",
"type": "error",
"inputs": []
},
{
"name": "InvalidWhitelistAddress",
"type": "error",
"inputs": []
},
{
"name": "MaxManagementFeeExceeded",
"type": "error",
"inputs": []
},
{
"name": "MaxMintAmountsExceeded",
"type": "error",
"inputs": []
},
{
"name": "NewOwnerIsZeroAddress",
"type": "error",
"inputs": []
},
{
"name": "NoHandoverRequest",
"type": "error",
"inputs": []
},
{
"name": "NotGuardian",
"type": "error",
"inputs": []
},
{
"name": "NotManager",
"type": "error",
"inputs": []
},
{
"name": "NotProposed",
"type": "error",
"inputs": []
},
{
"name": "OracleTimelocked",
"type": "error",
"inputs": []
},
{
"name": "Paused",
"type": "error",
"inputs": []
},
{
"name": "PausedStateNotChanged",
"type": "error",
"inputs": []
},
{
"name": "Permit2AllowanceIsFixedAtInfinity",
"type": "error",
"inputs": []
},
{
"name": "PermitExpired",
"type": "error",
"inputs": []
},
{
"name": "Reentrancy",
"type": "error",
"inputs": []
},
{
"name": "SlippageExceeded",
"type": "error",
"inputs": []
},
{
"name": "TimelockNotExpired",
"type": "error",
"inputs": []
},
{
"name": "TotalSupplyOverflow",
"type": "error",
"inputs": []
},
{
"name": "Unauthorized",
"type": "error",
"inputs": []
},
{
"name": "AcceptedOracle",
"type": "event",
"inputs": [
{
"name": "key",
"type": "bytes32",
"indexed": true,
"internalType": "bytes32"
}
],
"anonymous": false
},
{
"name": "AccruedFees",
"type": "event",
"inputs": [
{
"name": "feeShares",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
}
],
"anonymous": false
},
{
"name": "Approval",
"type": "event",
"inputs": [
{
"name": "owner",
"type": "address",
"indexed": true,
"internalType": "address"
},
{
"name": "spender",
"type": "address",
"indexed": true,
"internalType": "address"
},
{
"name": "amount",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
}
],
"anonymous": false
},
{
"name": "FundsEnteredKandel",
"type": "event",
"inputs": [],
"anonymous": false
},
{
"name": "FundsExitedKandel",
"type": "event",
"inputs": [],
"anonymous": false
},
{
"name": "GuardianChanged",
"type": "event",
"inputs": [
{
"name": "oldGuardian",
"type": "address",
"indexed": true,
"internalType": "address"
},
{
"name": "newGuardian",
"type": "address",
"indexed": true,
"internalType": "address"
}
],
"anonymous": false
},
{
"name": "OwnershipHandoverCanceled",
"type": "event",
"inputs": [
{
"name": "pendingOwner",
"type": "address",
"indexed": true,
"internalType": "address"
}
],
"anonymous": false
},
{
"name": "OwnershipHandoverRequested",
"type": "event",
"inputs": [
{
"name": "pendingOwner",
"type": "address",
"indexed": true,
"internalType": "address"
}
],
"anonymous": false
},
{
"name": "OwnershipTransferred",
"type": "event",
"inputs": [
{
"name": "oldOwner",
"type": "address",
"indexed": true,
"internalType": "address"
},
{
"name": "newOwner",
"type": "address",
"indexed": true,
"internalType": "address"
}
],
"anonymous": false
},
{
"name": "ProposedOracle",
"type": "event",
"inputs": [
{
"name": "key",
"type": "bytes32",
"indexed": true,
"internalType": "bytes32"
},
{
"name": "oracle",
"type": "tuple",
"indexed": false,
"components": [
{
"name": "isStatic",
"type": "bool",
"internalType": "bool"
},
{
"name": "oracle",
"type": "address",
"internalType": "contract IOracle"
},
{
"name": "staticValue",
"type": "int24",
"internalType": "int24"
},
{
"name": "maxDeviation",
"type": "uint16",
"internalType": "uint16"
},
{
"name": "proposedAt",
"type": "uint40",
"internalType": "uint40"
},
{
"name": "timelockMinutes",
"type": "uint8",
"internalType": "uint8"
}
],
"internalType": "struct OracleData"
}
],
"anonymous": false
},
{
"name": "Rebalanced",
"type": "event",
"inputs": [
{
"name": "target",
"type": "address",
"indexed": true,
"internalType": "address"
},
{
"name": "isSell",
"type": "bool",
"indexed": false,
"internalType": "bool"
},
{
"name": "amountIn",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
},
{
"name": "amountOut",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
},
{
"name": "baseBalanceAfter",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
},
{
"name": "quoteBalanceAfter",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
}
],
"anonymous": false
},
{
"name": "ReceivedTokens",
"type": "event",
"inputs": [
{
"name": "baseIn",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
},
{
"name": "quoteIn",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
},
{
"name": "totalBaseBalance",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
},
{
"name": "totalQuoteBalance",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
}
],
"anonymous": false
},
{
"name": "RejectedOracle",
"type": "event",
"inputs": [
{
"name": "key",
"type": "bytes32",
"indexed": true,
"internalType": "bytes32"
}
],
"anonymous": false
},
{
"name": "SentTokens",
"type": "event",
"inputs": [
{
"name": "baseOut",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
},
{
"name": "quoteOut",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
},
{
"name": "totalBaseBalance",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
},
{
"name": "totalQuoteBalance",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
}
],
"anonymous": false
},
{
"name": "SetFeeData",
"type": "event",
"inputs": [
{
"name": "feeRecipient",
"type": "address",
"indexed": true,
"internalType": "address"
},
{
"name": "managementFee",
"type": "uint16",
"indexed": false,
"internalType": "uint16"
}
],
"anonymous": false
},
{
"name": "SetManager",
"type": "event",
"inputs": [
{
"name": "manager",
"type": "address",
"indexed": true,
"internalType": "address"
}
],
"anonymous": false
},
{
"name": "SetMaxMintAmounts",
"type": "event",
"inputs": [
{
"name": "maxBase",
"type": "uint128",
"indexed": false,
"internalType": "uint128"
},
{
"name": "maxQuote",
"type": "uint128",
"indexed": false,
"internalType": "uint128"
}
],
"anonymous": false
},
{
"name": "SetPaused",
"type": "event",
"inputs": [
{
"name": "paused",
"type": "bool",
"indexed": false,
"internalType": "bool"
}
],
"anonymous": false
},
{
"name": "Transfer",
"type": "event",
"inputs": [
{
"name": "from",
"type": "address",
"indexed": true,
"internalType": "address"
},
{
"name": "to",
"type": "address",
"indexed": true,
"internalType": "address"
},
{
"name": "amount",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
}
],
"anonymous": false
},
{
"name": "WhitelistAccepted",
"type": "event",
"inputs": [
{
"name": "_address",
"type": "address",
"indexed": true,
"internalType": "address"
}
],
"anonymous": false
},
{
"name": "WhitelistProposed",
"type": "event",
"inputs": [
{
"name": "_address",
"type": "address",
"indexed": true,
"internalType": "address"
}
],
"anonymous": false
},
{
"name": "WhitelistRejected",
"type": "event",
"inputs": [
{
"name": "_address",
"type": "address",
"indexed": true,
"internalType": "address"
}
],
"anonymous": false
},
{
"name": "DOMAIN_SEPARATOR",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "result",
"type": "bytes32",
"internalType": "bytes32"
}
],
"stateMutability": "view"
},
{
"name": "KANDEL",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "address",
"internalType": "contract GeometricKandel"
}
],
"stateMutability": "view"
},
{
"name": "acceptOracle",
"type": "function",
"inputs": [],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"name": "acceptWhitelist",
"type": "function",
"inputs": [
{
"name": "_address",
"type": "address",
"internalType": "address"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"name": "allowance",
"type": "function",
"inputs": [
{
"name": "owner",
"type": "address",
"internalType": "address"
},
{
"name": "spender",
"type": "address",
"internalType": "address"
}
],
"outputs": [
{
"name": "result",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"name": "approve",
"type": "function",
"inputs": [
{
"name": "spender",
"type": "address",
"internalType": "address"
},
{
"name": "amount",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [
{
"name": "",
"type": "bool",
"internalType": "bool"
}
],
"stateMutability": "nonpayable"
},
{
"name": "balanceOf",
"type": "function",
"inputs": [
{
"name": "owner",
"type": "address",
"internalType": "address"
}
],
"outputs": [
{
"name": "result",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"name": "burn",
"type": "function",
"inputs": [
{
"name": "from",
"type": "address",
"internalType": "address"
},
{
"name": "receiver",
"type": "address",
"internalType": "address"
},
{
"name": "shares",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "minBaseOut",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "minQuoteOut",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [
{
"name": "baseOut",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "quoteOut",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "nonpayable"
},
{
"name": "cancelOwnershipHandover",
"type": "function",
"inputs": [],
"outputs": [],
"stateMutability": "payable"
},
{
"name": "completeOwnershipHandover",
"type": "function",
"inputs": [
{
"name": "pendingOwner",
"type": "address",
"internalType": "address"
}
],
"outputs": [],
"stateMutability": "payable"
},
{
"name": "decimals",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint8",
"internalType": "uint8"
}
],
"stateMutability": "view"
},
{
"name": "feeData",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "managementFee",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "feeRecipient",
"type": "address",
"internalType": "address"
},
{
"name": "pendingFeeShares",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"name": "getCurrentTickInfo",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "currentTick",
"type": "int256",
"internalType": "Tick"
},
{
"name": "isStatic",
"type": "bool",
"internalType": "bool"
},
{
"name": "maxDeviation",
"type": "uint16",
"internalType": "uint16"
}
],
"stateMutability": "view"
},
{
"name": "getMintAmounts",
"type": "function",
"inputs": [
{
"name": "baseAmountMax",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "quoteAmountMax",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [
{
"name": "sharesOut",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "baseIn",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "quoteIn",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"name": "guardian",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "address",
"internalType": "address"
}
],
"stateMutability": "view"
},
{
"name": "isWhitelisted",
"type": "function",
"inputs": [
{
"name": "",
"type": "address",
"internalType": "address"
}
],
"outputs": [
{
"name": "",
"type": "bool",
"internalType": "bool"
}
],
"stateMutability": "view"
},
{
"name": "kandelBalances",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "baseBalance",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "quoteBalance",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"name": "manager",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "address",
"internalType": "address"
}
],
"stateMutability": "view"
},
{
"name": "market",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "base",
"type": "address",
"internalType": "address"
},
{
"name": "quote",
"type": "address",
"internalType": "address"
},
{
"name": "tickSpacing",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"name": "maxMintAmounts",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "maxBase",
"type": "uint128",
"internalType": "uint128"
},
{
"name": "maxQuote",
"type": "uint128",
"internalType": "uint128"
}
],
"stateMutability": "view"
},
{
"name": "mint",
"type": "function",
"inputs": [
{
"name": "to",
"type": "address",
"internalType": "address"
},
{
"name": "baseAmountMax",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "quoteAmountMax",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "minSharesOut",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [
{
"name": "sharesOut",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "baseIn",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "quoteIn",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "nonpayable"
},
{
"name": "name",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "string",
"internalType": "string"
}
],
"stateMutability": "view"
},
{
"name": "nonces",
"type": "function",
"inputs": [
{
"name": "owner",
"type": "address",
"internalType": "address"
}
],
"outputs": [
{
"name": "result",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"name": "oracle",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "isStatic",
"type": "bool",
"internalType": "bool"
},
{
"name": "oracle",
"type": "address",
"internalType": "contract IOracle"
},
{
"name": "staticValue",
"type": "int24",
"internalType": "int24"
},
{
"name": "maxDeviation",
"type": "uint16",
"internalType": "uint16"
},
{
"name": "proposedAt",
"type": "uint40",
"internalType": "uint40"
},
{
"name": "timelockMinutes",
"type": "uint8",
"internalType": "uint8"
}
],
"stateMutability": "view"
},
{
"name": "owner",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "result",
"type": "address",
"internalType": "address"
}
],
"stateMutability": "view"
},
{
"name": "ownershipHandoverExpiresAt",
"type": "function",
"inputs": [
{
"name": "pendingOwner",
"type": "address",
"internalType": "address"
}
],
"outputs": [
{
"name": "result",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"name": "permit",
"type": "function",
"inputs": [
{
"name": "owner",
"type": "address",
"internalType": "address"
},
{
"name": "spender",
"type": "address",
"internalType": "address"
},
{
"name": "value",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "deadline",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "v",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "r",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "s",
"type": "bytes32",
"internalType": "bytes32"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"name": "populateChunkFromOffset",
"type": "function",
"inputs": [
{
"name": "from",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "to",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "baseQuoteTickIndex0",
"type": "int256",
"internalType": "Tick"
},
{
"name": "firstAskIndex",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "bidGives",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "askGives",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"name": "populateFromOffset",
"type": "function",
"inputs": [
{
"name": "from",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "to",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "baseQuoteTickIndex0",
"type": "int256",
"internalType": "Tick"
},
{
"name": "_baseQuoteTickOffset",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "firstAskIndex",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "bidGives",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "askGives",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "parameters",
"type": "tuple",
"components": [
{
"name": "gasprice",
"type": "uint32",
"internalType": "uint32"
},
{
"name": "gasreq",
"type": "uint24",
"internalType": "uint24"
},
{
"name": "stepSize",
"type": "uint32",
"internalType": "uint32"
},
{
"name": "pricePoints",
"type": "uint32",
"internalType": "uint32"
}
],
"internalType": "struct CoreKandel.Params"
}
],
"outputs": [],
"stateMutability": "payable"
},
{
"name": "proposeOracle",
"type": "function",
"inputs": [
{
"name": "_oracle",
"type": "tuple",
"components": [
{
"name": "isStatic",
"type": "bool",
"internalType": "bool"
},
{
"name": "oracle",
"type": "address",
"internalType": "contract IOracle"
},
{
"name": "staticValue",
"type": "int24",
"internalType": "int24"
},
{
"name": "maxDeviation",
"type": "uint16",
"internalType": "uint16"
},
{
"name": "proposedAt",
"type": "uint40",
"internalType": "uint40"
},
{
"name": "timelockMinutes",
"type": "uint8",
"internalType": "uint8"
}
],
"internalType": "struct OracleData"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"name": "proposeWhitelist",
"type": "function",
"inputs": [
{
"name": "_address",
"type": "address",
"internalType": "address"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"name": "proposedOracle",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "isStatic",
"type": "bool",
"internalType": "bool"
},
{
"name": "oracle",
"type": "address",
"internalType": "contract IOracle"
},
{
"name": "staticValue",
"type": "int24",
"internalType": "int24"
},
{
"name": "maxDeviation",
"type": "uint16",
"internalType": "uint16"
},
{
"name": "proposedAt",
"type": "uint40",
"internalType": "uint40"
},
{
"name": "timelockMinutes",
"type": "uint8",
"internalType": "uint8"
}
],
"stateMutability": "view"
},
{
"name": "rebalance",
"type": "function",
"inputs": [
{
"name": "_params",
"type": "tuple",
"components": [
{
"name": "isSell",
"type": "bool",
"internalType": "bool"
},
{
"name": "amountIn",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "amountInKandel",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "minAmountOut",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "target",
"type": "address",
"internalType": "address"
},
{
"name": "data",
"type": "bytes",
"internalType": "bytes"
}
],
"internalType": "struct KandelManagementRebalancing.RebalanceParams"
}
],
"outputs": [
{
"name": "sent",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "received",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "callResult",
"type": "bytes",
"internalType": "bytes"
}
],
"stateMutability": "payable"
},
{
"name": "rejectOracle",
"type": "function",
"inputs": [],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"name": "rejectWhitelist",
"type": "function",
"inputs": [
{
"name": "_address",
"type": "address",
"internalType": "address"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"name": "renounceOwnership",
"type": "function",
"inputs": [],
"outputs": [],
"stateMutability": "payable"
},
{
"name": "requestOwnershipHandover",
"type": "function",
"inputs": [],
"outputs": [],
"stateMutability": "payable"
},
{
"name": "retractOffers",
"type": "function",
"inputs": [
{
"name": "from",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "to",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "baseAmount",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "quoteAmount",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "withdrawProvisions",
"type": "bool",
"internalType": "bool"
},
{
"name": "recipient",
"type": "address",
"internalType": "address"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"name": "setFeeData",
"type": "function",
"inputs": [
{
"name": "_feeRecipient",
"type": "address",
"internalType": "address"
},
{
"name": "_managementFee",
"type": "uint16",
"internalType": "uint16"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"name": "setGuardian",
"type": "function",
"inputs": [
{
"name": "newGuardian",
"type": "address",
"internalType": "address"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"name": "setManager",
"type": "function",
"inputs": [
{
"name": "_manager",
"type": "address",
"internalType": "address"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"name": "setMaxMintAmounts",
"type": "function",
"inputs": [
{
"name": "maxBase",
"type": "uint128",
"internalType": "uint128"
},
{
"name": "maxQuote",
"type": "uint128",
"internalType": "uint128"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"name": "setPaused",
"type": "function",
"inputs": [
{
"name": "_paused",
"type": "bool",
"internalType": "bool"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"name": "state",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "inKandel",
"type": "bool",
"internalType": "bool"
},
{
"name": "feeRecipient",
"type": "address",
"internalType": "address"
},
{
"name": "managementFee",
"type": "uint16",
"internalType": "uint16"
},
{
"name": "lastTimestamp",
"type": "uint40",
"internalType": "uint40"
},
{
"name": "paused",
"type": "bool",
"internalType": "bool"
}
],
"stateMutability": "view"
},
{
"name": "symbol",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "string",
"internalType": "string"
}
],
"stateMutability": "view"
},
{
"name": "totalBalances",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "baseBalance",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "quoteBalance",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"name": "totalSupply",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "result",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"name": "transfer",
"type": "function",
"inputs": [
{
"name": "to",
"type": "address",
"internalType": "address"
},
{
"name": "amount",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [
{
"name": "",
"type": "bool",
"internalType": "bool"
}
],
"stateMutability": "nonpayable"
},
{
"name": "transferFrom",
"type": "function",
"inputs": [
{
"name": "from",
"type": "address",
"internalType": "address"
},
{
"name": "to",
"type": "address",
"internalType": "address"
},
{
"name": "amount",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [
{
"name": "",
"type": "bool",
"internalType": "bool"
}
],
"stateMutability": "nonpayable"
},
{
"name": "transferOwnership",
"type": "function",
"inputs": [
{
"name": "newOwner",
"type": "address",
"internalType": "address"
}
],
"outputs": [],
"stateMutability": "payable"
},
{
"name": "vaultBalances",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "baseBalance",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "quoteBalance",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"name": "whitelistProposedAt",
"type": "function",
"inputs": [
{
"name": "",
"type": "address",
"internalType": "address"
}
],
"outputs": [
{
"name": "",
"type": "uint40",
"internalType": "uint40"
}
],
"stateMutability": "view"
},
{
"name": "withdrawFromMangrove",
"type": "function",
"inputs": [
{
"name": "freeWei",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "recipient",
"type": "address",
"internalType": "address payable"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"name": "withdrawFunds",
"type": "function",
"inputs": [
{
"name": "baseAmount",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "quoteAmount",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"type": "receive",
"stateMutability": "payable"
}
]