Source Code
Overview
POL Balance
POL Value
$0.00Latest 1 from a total of 1 transactions
| Transaction Hash |
|
Block
|
From
|
To
|
|||||
|---|---|---|---|---|---|---|---|---|---|
| Setup | 84624104 | 17 days ago | IN | 0 POL | 0.02009749 |
Cross-Chain Transactions
Loading...
Loading
This contract contains unverified libraries: BasketLib
This contract may be a proxy contract. Click on More Options and select Is this a proxy? to confirm and enable the "Read as Proxy" & "Write as Proxy" tabs.
Contract Name:
Basket
Compiler Version
v0.8.26+commit.8a97fa7a
Optimization Enabled:
Yes with 200 runs
Other Settings:
cancun EvmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import {Aux} from "./Aux.sol";
import {UMA} from "./UMA.sol";
import {Link} from "./Link.sol";
import {Jury} from "./Jury.sol";
import {Court} from "./Court.sol";
import {OFT} from "./imports/OFT.sol";
import {Types} from "./imports/Types.sol";
import {Origin} from "./imports/oapp/OApp.sol";
import {BasketLib} from "./imports/BasketLib.sol";
import {SortedSetLib} from "./imports/SortedSet.sol";
import {ERC6909} from "solmate/src/tokens/ERC6909.sol";
import {IERC20} from "forge-std/interfaces/IERC20.sol";
import {MessageCodec} from "./imports/MessageCodec.sol";
import {IERC4626} from "forge-std/interfaces/IERC4626.sol";
import {FullMath} from "v4-core/src/libraries/FullMath.sol";
import {SendParam} from "./imports/oapp/interfaces/IOFT.sol";
import {OFTMsgCodec} from "./imports/oapp/libs/OFTMsgCodec.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {ReentrancyGuard} from "solmate/src/utils/ReentrancyGuard.sol";
import {MessagingReceipt, MessagingFee} from "./imports/oapp/OAppSender.sol";
contract Basket is OFT, ERC6909, ReentrancyGuard {
using SortedSetLib for SortedSetLib.Set;
using OFTMsgCodec for bytes32;
using OFTMsgCodec for bytes;
uint constant WAD = 1e18; // dollario
uint internal _deployed; // quidmint
uint constant CAP = 900_000 * 1e18;
error Unauthorized(); error NoBalance();
error AlreadyIn(); error NotIn();
error Locked();
error EmptyPayload(); // LZ send: no basket payload in calldata
error MismatchedArrays(); // ids.length != amounts.length or empty
error InsufficientBalance(); // sender balance < amount for a batch id
error PayloadMismatch(); // sum of batch amounts != OFT amountSentLD
error WrongChain(); // LZ message arrived from unexpected srcEid
error InsufficientFee(); // msg.value < LayerZero native fee
error InsufficientUnlocked(); // transfer would exceed unlocked balance
error NoEndpoint(); error BadType();
uint internal seeded; // seed round
uint public target; // ^ backing
address payable internal court;
bool public marketCreated;
address payable public V4;
UMA internal UMA_ORACLE;
address internal jury;
IERC20 internal USDC;
Link public LINK;
Aux public AUX;
modifier onlyUs() {
if (!auth(msg.sender)) revert Unauthorized();
_;
}
function auth(address who) public view returns (bool) {
return (who == address(AUX) || who == V4 // AAVEUni
|| who == address(LINK) || who == jury ||
who == court); // vanilla...
}
// ═══════════════════════════════════════════════════════════════
// QD holders call optInJury() to volunteer for jury duty.
// Selection in Jury.voirDire draws from this pool via
// juryPoolSize() and juryPoolMember(idx).
// ═══════════════════════════════════════════════════════════════
address constant LZ = 0x1a44076050125825900e736c501f859c50fE728c;
address[] internal juryPool; mapping(address => uint) internal juryLocked;
mapping(address => uint) internal juryPoolIndex;
mapping(uint => uint) internal totalSupplies;
mapping(address => uint) internal untouchables;
mapping(address => bool) internal isL2Basket;
address[] internal l2Baskets;
uint public l2Deposits;
uint32 public constant SOL_MAINNET_EID = 30168;
constructor(address _vogue, address _aux,
address _uma, address _usdc)
OFT("QU!D", "QD", LZ, msg.sender)
Ownable(msg.sender) {
V4 = payable(_vogue);
USDC = IERC20(_usdc);
UMA_ORACLE = UMA(_uma);
AUX = Aux(payable(_aux));
_deployed = block.timestamp;
}
function setup(address _hook, address _court,
address _jury) external { if (msg.sender != owner()
|| address(LINK) != address(0))
revert Unauthorized();
LINK = Link(payable(_hook)); court = payable(_court);
jury = _jury; //
}
function registerL2Basket(address _l2) external {
if (msg.sender != owner()
|| isL2Basket[_l2]) revert Unauthorized();
isL2Basket[_l2] = true;
l2Baskets.push(_l2);
}
/// @notice Distribute proportional L2 basket tokens on redeem.
function distributeL2(address to,
uint burned, uint total) external
onlyUs returns (uint totalOut) {
if (l2Deposits == 0) return 0;
totalOut = BasketLib.distributeL2(
l2Baskets, to, burned, total);
l2Deposits -= Math.min(l2Deposits, totalOut);
}
mapping(address => SortedSetLib.Set) private perMonth;
function currentMonth() public view returns (uint month) {
month = (block.timestamp - _deployed) / BasketLib.MONTH;
}
function lockForJury(address juror,
uint amount) onlyUs external {
juryLocked[juror] += amount;
}
function unlockFromJury(address juror,
uint amount) onlyUs external {
juryLocked[juror] -= Math.min(
juryLocked[juror], amount);
}
/// @notice Opt in to the jury pool. Any QD holder can volunteer.
function optInJury() external {
if (super.balanceOf(msg.sender) <= 500e18) revert NoBalance();
if (juryPoolIndex[msg.sender] != 0) revert AlreadyIn();
juryPool.push(msg.sender); juryPoolIndex[msg.sender] = juryPool.length;
}
/// @notice Opt out of the jury pool. Cannot opt out while
/// stake is locked (actively serving on a jury).
function optOutJury() external {
uint idx = juryPoolIndex[msg.sender];
if (idx == 0) revert NotIn();
if (juryLocked[msg.sender] != 0) revert Locked();
uint last = juryPool.length;
if (idx != last) {
address lastAddr = juryPool[last - 1];
juryPool[idx - 1] = lastAddr;
juryPoolIndex[lastAddr] = idx;
} juryPool.pop();
juryPoolIndex[msg.sender] = 0;
} function juryPoolSize()
external view returns (uint) {
return juryPool.length;
}
/// @notice jury pool member by index.
/// Used by Jury.voirDire for RANDAO
/// seeded by random selection...
function juryPoolMember(uint idx)
external view returns (address) {
return juryPool[idx];
}
function _debit(uint _amountLD, uint _minAmountLD,
uint32 _dstEid) internal override returns
(uint amountSentLD, uint amountReceivedLD) {
// keep OFT safety math (fees, min amount)
(amountSentLD, amountReceivedLD) = _debitView(
_amountLD, _minAmountLD, _dstEid);
// pull basket payload from send(...) calldata...
bytes memory payload = BasketLib.extract(msg.data);
if (payload.length == 0) revert EmptyPayload(); uint total;
(uint[] memory ids,
uint[] memory amounts) = abi.decode(
payload, (uint[], uint[]));
if (ids.length != amounts.length
|| ids.length == 0) revert MismatchedArrays();
uint rate = this.decimalConversionRate();
for (uint i = 0; i < ids.length; ++i) {
uint id = ids[i]; uint amt = amounts[i] * rate;
if (balanceOf[msg.sender][id] < amt) revert InsufficientBalance();
balanceOf[msg.sender][id] -= amt;
totalSupplies[id] -= amt;
if (balanceOf[msg.sender][id] == 0)
perMonth[msg.sender].remove(id);
total += amt;
}
if (total != amountSentLD) revert PayloadMismatch();
super._update(msg.sender, address(0), total);
}
function _lzReceive(Origin calldata _origin,
bytes32 _guid, bytes calldata _message,
address, bytes calldata) internal override {
// peer check is sufficient; LZ nonces prevent replay
if (_origin.sender != peers[_origin.srcEid]) revert Unauthorized();
uint64 amountSD = _message.amountSD();
uint amountReceivedLD = _toLD(amountSD);
bytes memory composeMsg = _message.composeMsg();
address to = _message.sendTo().bytes32ToAddress();
uint8 msgType = MessageCodec.getMessageType(composeMsg);
if (msgType == MessageCodec.RESOLUTION_REQUEST) {
if (_origin.srcEid != SOL_MAINNET_EID) revert WrongChain();
Court(court).receiveResolutionRequest(composeMsg);
emit OFTReceived(_guid, _origin.srcEid, court, 0);
} else if (msgType == MessageCodec.JURY_COMPENSATION) {
if (_origin.srcEid != SOL_MAINNET_EID) revert WrongChain();
_handleJuryCompensation(_guid, _origin.srcEid, composeMsg);
} else require(_handleBasketTransfer(
composeMsg, to) == amountReceivedLD);
}
function _handleJuryCompensation(bytes32 _guid,
uint32 srcEid, bytes memory composeMsg) internal {
(uint64 marketId, uint64 amountSolana) =
MessageCodec.decodeJuryCompensation(composeMsg);
uint amount = MessageCodec.toEthereumAmount(amountSolana);
_mint(jury, currentMonth() + 1, amount);
Jury(jury).receiveJuryFunds(marketId, amount);
emit OFTReceived(_guid, srcEid, jury, amount);
}
function sendToSolana(bytes memory composeMsg)
external onlyUs payable returns (bytes32) {
if (composeMsg.length == 0) revert EmptyPayload();
if (address(endpoint) == address(0)) revert NoEndpoint();
uint8 msgType = MessageCodec.getMessageType(composeMsg);
if (msgType != MessageCodec.FINAL_RULING) revert BadType();
// little train, wait for me; once was blind but now I see
uint32 dstEid = SOL_MAINNET_EID;
bytes memory options = BasketLib.buildOptions(msgType);
MessagingFee memory fee = _quote(dstEid,
composeMsg, options, false);
if (msg.value < fee.nativeFee) revert InsufficientFee();
MessagingReceipt memory receipt = _lzSend(dstEid,
composeMsg, options, fee, msg.sender);
if (msg.value > fee.nativeFee)
payable(msg.sender).transfer(
msg.value - fee.nativeFee);
return receipt.guid;
}
function _handleBasketTransfer(
bytes memory msg, address to)
internal returns (uint total) {
(uint[] memory ids,
uint[] memory amounts) = abi.decode(msg, (uint[], uint[]));
if (ids.length != amounts.length || ids.length == 0) revert MismatchedArrays();
uint rate = this.decimalConversionRate();
for (uint i = 0; i < ids.length; ++i) {
uint scaled = amounts[i] * rate;
_mint(to, ids[i], scaled);
total += scaled;
}
}
function turn(address from, uint value) external
onlyUs returns (uint sent, uint seedBurned) {
uint seedBefore = untouchables[from];
address destination = (msg.sender == jury) ?
jury : address(0);
sent = _transferHelper(from, destination, value);
seedBurned = seedBefore - untouchables[from];
}
function _mint(address receiver,
uint when, uint amount)
internal override {
totalSupplies[when] += amount;
perMonth[receiver].insert(when);
super._update(address(0), receiver, amount);
balanceOf[receiver][when] += amount;
emit Transfer(msg.sender, address(0),
receiver, when, amount);
}
function mint(address pledge, uint amount,
address token, uint when) external
nonReentrant returns (uint normalized) {
uint nextMonth = currentMonth() + 1;
// this is used by Vogue.withdraw()
if (auth(msg.sender)) { _mint(pledge,
nextMonth, amount);
return amount;
} if (isL2Basket[token]) {
IERC20(token).transferFrom(
pledge, address(this), amount);
l2Deposits += amount;
_mint(pledge, nextMonth, amount);
return amount;
} uint deposited = AUX.deposit(
pledge, token, amount);
if (!marketCreated && token == address(USDC)) {
uint[14] memory deposits = AUX.get_deposits();
if (deposits[1] >= 1_515e18) {
address[] memory stables = AUX.getStables();
uint bond = 1_515e6; bond = AUX.take(// $1515
address(this), bond, address(USDC), 0);
USDC.transfer(address(UMA_ORACLE), bond / 1e12);
UMA_ORACLE.registerMarket(stables);
LINK.createMarket(stables);
marketCreated = true;
}
} uint decimals = IERC20(token).decimals();
uint month = Math.max(when, nextMonth);
bool isSeed = month - nextMonth > 12 &&
month < 24 && seeded < CAP;
(normalized, month) = BasketLib.calcMintYield(
deposited, decimals, when, nextMonth,
currentMonth(), seeded, AUX.getAverageYield(), isSeed);
if (isSeed) { seeded += normalized;
untouchables[pledge] += normalized;
target += normalized; }
_mint(pledge, month, normalized);
}
function transfer(address to,
uint value) public override returns (bool) {
require(value == _transferHelper(msg.sender,
to, value)); return true;
}
function transferFrom(address from,
address to, uint value) public
override returns (bool) {
address spender = _msgSender();
_spendAllowance(from, spender, value);
_transferHelper(from, to, value); return true;
}
// times flies...make a statement; take a stand...
function _transferHelper(address from, address to,
uint amount) internal returns (uint sent) {
if (super.balanceOf(from) - juryLocked[from] < amount) revert InsufficientUnlocked();
uint[] memory batches = perMonth[from].getSortedSet();
bool turning = to == address(0); int i = turning &&
from != address(LINK) ? BasketLib.matureBatches(
batches, block.timestamp, _deployed):
int(batches.length - 1);
while (amount > 0 && i >= 0) {
uint k = batches[uint(i)];
uint amt = balanceOf[from][k];
if (amt > 0) {
amt = Math.min(amount, amt);
balanceOf[from][k] -= amt;
if (!turning) {
perMonth[to].insert(k);
balanceOf[to][k] += amt;
} else
totalSupplies[k] -= amt;
if (balanceOf[from][k] == 0)
perMonth[from].remove(k);
amount -= amt; sent += amt;
} i -= 1; // liable to be -1
} // -1 means "no mature batches"
if (sent > 0) { super._update(from, to, sent);
// ^ should burn from totalSupply
// if necessary (to = address(0))
if (untouchables[from] > 0) {
uint seed = Math.min(sent,
untouchables[from]);
untouchables[from] -= seed;
if (to == address(0))
target -= Math.min(target, seed);
else untouchables[to] += seed;
}
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import {Amp} from "./Amp.sol";
import {Link} from "./Link.sol";
import {Vogue} from "./Vogue.sol";
import {Rover} from "./Rover.sol";
import {Basket} from "./Basket.sol";
import {Types} from "./imports/Types.sol";
import {VogueCore} from "./VogueCore.sol";
import {FeeLib} from "./imports/FeeLib.sol";
import {BasketLib} from "./imports/BasketLib.sol";
import {IERC20} from "forge-std/interfaces/IERC20.sol";
import {WETH as WETH9} from "solmate/src/tokens/WETH.sol";
import {IERC4626} from "forge-std/interfaces/IERC4626.sol";
import {FullMath} from "v4-core/src/libraries/FullMath.sol";
import {IUniswapV3Pool} from "./imports/v3/IUniswapV3Pool.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {ReentrancyGuard} from "solmate/src/utils/ReentrancyGuard.sol";
interface IFlashBorrower {
function onFlashLoan(address initiator,
address token, uint amount,
uint shareBps, bytes calldata data)
external returns (bytes32);
}
interface AAVEv3 {
function getReserveAToken(address asset)
external view returns (address);
function supply(address asset, uint amount,
address onBehalfOf, uint16 referralCode) external;
function withdraw(address asset, uint amount,
address to) external returns (uint);
}
interface IHub {
function getAssetId(address underlying)
external view returns (uint256);
}
interface AAVEv4 {
function getReserveId(address hub,
uint assetId) external returns (uint);
function withdraw(uint reserveId,
uint amount, address onBehalfOf)
external returns (uint, uint);
function supply(uint reserveId,
uint amount, address onBehalfOf)
external returns (uint256, uint256);
function getUserSuppliedAssets(uint reserveId,
address user) external view returns (uint);
function getUserSuppliedShares(uint reserveId,
address user) external view returns (uint);
}
contract Aux is // Auxiliary
Ownable, ReentrancyGuard {
address[] public stables;
bool public token1isWETH;
IERC20 internal USDC; Basket internal QUID;
Vogue internal V4; VogueCore internal CORE;
WETH9 public WETH; Rover internal V3;
IUniswapV3Pool internal v3PoolWETH;
BasketLib.Metrics internal metrics;
uint internal spValue; // current BOLD principal in SP
uint internal spTotalYield; // cumulative harvested USD
uint internal spPrincipalTime; // (principal * seconds)
uint internal spLastUpdate; // last update timestamp...
uint internal _lastTotalETH; // AAVE WETH at last sync
uint public vogueETH; // WETH attributed to Vogue pool
mapping(address => uint) public untouchables;
mapping(address => address) public vaults;
mapping(address => address) public tokens;
mapping(address => uint) internal toIndex;
uint public untouchable;
// ^ in vault shares, 1e18
address internal v3Router;
uint constant WAD = 1e18;
uint constant RAY = 1e27;
uint internal lastBlock;
address internal JAM;
AAVEv3 internal POOL;
uint24 internal v3Fee;
AAVEv4 internal SPOKE;
address internal LINK;
IHub internal HUB;
Amp internal AMP;
error LengthMismatch();
error Unauthorized();
modifier onlyUs {
if (msg.sender != address(V4)
&& msg.sender != address(CORE)
&& msg.sender != address(QUID)
&& msg.sender != address(this))
revert Unauthorized(); _;
}
bytes32 constant CALLBACK_SUCCESS = keccak256(
"ERC3156FlashBorrower.onFlashLoan");
/// @notice init (plug) Aux with addresses
/// @dev optional: V3 rover & AAVE amp...
constructor(address _vogue, address _core,
address _amp, address _aave,
address _v3poolWETH,
address _v3router, address _v3,
address[] memory _stables,
address[] memory _vaults)
Ownable(msg.sender) {
POOL = AAVEv3(_aave);
v3Router = _v3router;
lastBlock = block.number - 1;
v3PoolWETH = IUniswapV3Pool(_v3poolWETH);
address token0 = v3PoolWETH.token0();
address token1 = v3PoolWETH.token1();
if (IERC20(token1).decimals() >
IERC20(token0).decimals()) {
WETH = WETH9(payable(token1));
USDC = IERC20(token0);
token1isWETH = true;
} else { token1isWETH = false;
WETH = WETH9(payable(token0));
USDC = IERC20(token1);
} v3Fee = v3PoolWETH.fee();
V4 = Vogue(payable(_vogue));
CORE = VogueCore(_core);
if (_amp != address(0))
AMP = Amp(payable(_amp));
if (_v3 != address(0))
V3 = Rover(payable(_v3));
if (_stables.length != _vaults.length) revert LengthMismatch();
spLastUpdate = block.timestamp; stables = _stables;
uint len = _vaults.length - 1; metrics.last = 1;
metrics.trackingStart = block.timestamp;
for (uint i; i <= len; i++) {
address stable = _stables[i];
address vault = _vaults[i];
toIndex[stable] = i + 1;
tokens[vault] = stable; vaults[stable] = vault;
stable.call(abi.encodeWithSelector(0x095ea7b3,
vault, type(uint).max));
}
} receive() external payable {}
function get_metrics(bool force)
public returns (uint, uint) {
BasketLib.Metrics memory stats = metrics;
uint elapsed = block.timestamp - stats.last;
if (force || elapsed > 10 minutes) {
uint[14] memory amounts = get_deposits();
uint raw = amounts[12] - amounts[13];
metrics = BasketLib.computeMetrics(stats,
elapsed, raw, amounts[0], amounts[12]);
} return (metrics.total, metrics.yield);
}
function getAverageYield()
public view returns (uint) {
return BasketLib.getAverageYield(metrics);
}
function getStables() external view
returns (address[] memory) { return stables;
} function setQuid(address _quid, address _jam)
external onlyOwner { QUID = Basket(_quid);
LINK = address(QUID.LINK()); JAM = _jam;
USDC.approve(v3Router, type(uint).max);
WETH.approve(v3Router, type(uint).max);
WETH.approve(address(V4), type(uint).max);
WETH.approve(address(POOL), type(uint).max);
USDC.approve(vaults[stables[stables.length - 1]], type(uint).max);
WETH.approve(address(AMP), type(uint).max);
USDC.approve(address(AMP), type(uint).max);
WETH.approve(address(V3), type(uint).max);
USDC.approve(address(V3), type(uint).max);
} //
function setV4(address _hub, address _spoke) external
onlyOwner { require(!AMP.hasOpenDebt());
uint i; uint[4] memory amounts;
uint reserveId; address stable;
uint weth = _withdrawAAVE(address(WETH),
_availableETH(), address(this));
for (i = 0; i < 4; i++) { stable = stables[i];
amounts[i] = _withdrawAAVE(
stable, 0, address(this));
}
SPOKE = AAVEv4(_spoke); HUB = IHub(_hub);
WETH.approve(_hub, type(uint).max);
for (i = 0; i < 4; i++) {
IERC20(stables[i]).approve(
_hub, type(uint).max);
_supplyAAVE(stables[i],
amounts[i], address(this));
} _supplyAAVE(address(WETH),
weth, address(this));
_lastTotalETH = _availableETH();
AMP.setV4(_hub, _spoke);
renounceOwnership();
}
function getTWAP(uint32 period)
public view returns (uint price) {
uint32[] memory secondsAgos = new uint32[](2);
int56[] memory tickCumulatives; bool token0isUSD;
if (period == 0) { secondsAgos[0] = 1800; secondsAgos[1] = 0;
(tickCumulatives, ) = v3PoolWETH.observe(secondsAgos);
period = 1800; token0isUSD = token1isWETH;
} else { secondsAgos[0] = period; secondsAgos[1] = 0;
tickCumulatives = CORE.observe(secondsAgos);
token0isUSD = V4.token1isETH();
} price = BasketLib.ticksToPrice(tickCumulatives[0],
tickCumulatives[1], period, token0isUSD);
} function v3Fair(uint twapPrice) internal view returns (bool) {
return !BasketLib.isV3Manipulated(address(v3PoolWETH),
token1isWETH, twapPrice);
}
/// @param token either token we are paying or want to get
/// @param forETH ^ for $ --> ETH, opposite for ETH --> $
/// @param amount Amount to swap (either ETH, QD, or $)
/// @param minOut Minimum output (slippage protection)
function swap(address token, bool forETH, uint amount,
uint minOut) public payable nonReentrant returns
(uint max) { bool stable; bool zeroForOne;
Types.AuxContext memory ctx = _buildContext();
(uint160 sqrtPriceX96,,,) = V4.repack();
stable = toIndex[token] > 0;
if (!forETH) {
if (token != address(QUID)) require(stable);
amount = _depositETH(msg.sender, amount);
zeroForOne = !V4.token1isETH();
max = CORE.POOLED_USD();
} else { max = CORE.POOLED_ETH();
zeroForOne = V4.token1isETH();
if (token == address(QUID)) {
(uint burned, uint seedBurned) = QUID.turn(
msg.sender, amount);
amount = burned;
if (seedBurned > 0) {
for (uint i = 0; i < stables.length; i++) {
uint share = FullMath.mulDiv(
untouchables[stables[i]],
seedBurned, burned);
_tip(share, stables[i], -1);
}
}
} else {
address vault = tokens[token];
uint index = toIndex[vault];
if (index > 4) {
if (token == vaults[stables[10]])
token = address(USDC);
amount = _withdraw(address(this),
index, amount);
} else require(stable);
amount = deposit(msg.sender,
token, amount);
} token = address(0);
}
_syncETH(); uint poolSupplied;
(max, poolSupplied) = BasketLib.routeSwap(ctx,
Types.RouteParams({ sqrtPriceX96: sqrtPriceX96,
zeroForOne: zeroForOne, token: token,
amount: amount, pooled: max,
v4Price: getTWAP(1800),
v3Price: getTWAP(0),
recipient: msg.sender
}));
if (poolSupplied > 0) {
vogueETH += poolSupplied;
_lastTotalETH = _availableETH();
} require(max >= minOut);
}
function _buildContext() internal view
returns (Types.AuxContext memory) {
address aave = address(SPOKE) != address(0) ? address(SPOKE) : address(POOL);
return Types.AuxContext({ v3Pool: address(v3PoolWETH), hub: address(HUB),
v3Router: v3Router, weth: address(WETH), usdc: address(USDC),
vault: aave, v4: address(V4), core: address(CORE),
rover: address(V3), v3Fee: v3Fee,
isAAVE: true, nativeWETH: true });
}
function arbETH(uint shortfall) public
onlyUs returns (uint got) { _syncETH();
(got,) = BasketLib.arbETH(_buildContext(),
shortfall, getTWAP(0));
if (got > 0) {
vogueETH += got;
_lastTotalETH = _availableETH();
}
}
/// @notice Proportional AAVE
/// yield attribution to Vogue
function _syncETH() internal {
if (_lastTotalETH > 0) {
uint avail = _availableETH();
if (avail > _lastTotalETH)
vogueETH += (avail - _lastTotalETH)
* vogueETH / _lastTotalETH;
_lastTotalETH = avail;
}
}
/// @notice Unified Vogue ETH operation
/// @param op 0=deposit, 1=take, 2=sync
function vogueETHOp(uint amount, uint8 op)
external returns (uint sent) {
require(msg.sender == address(V4));
if (op == 0) { // deposit
WETH.transferFrom(msg.sender,
address(this), amount);
_supplyAAVE(address(WETH), amount,
address(this)); vogueETH += amount;
}
else if (op == 1) { // take
amount = Math.min(amount, vogueETH);
sent = _withdrawAAVE(address(WETH),
amount, address(this));
vogueETH -= Math.min(sent, vogueETH);
WETH.transfer(msg.sender, sent);
} else { _syncETH(); sent = vogueETH; }
}
/// @notice leveraged long (borrow WETH against USDC)
/// @dev 70% LTV on AAVE, excess USDC as collateral
/// @param amount WETH amount to deposit in AAVE
function leverETH(uint amount) payable
external { uint twapPrice = getTWAP(0);
amount = _depositETH(msg.sender, amount);
uint usdcNeeded = BasketLib.convert(
amount, twapPrice, false);
uint took = _take(address(this),
usdcNeeded, address(USDC), 0);
if (took <= usdcNeeded) { require(v3Fair(twapPrice));
(uint more, uint used) = BasketLib.source(_buildContext(),
usdcNeeded - took, amount, twapPrice, true);
took += more;
amount -= used;
} require(took >= usdcNeeded * 99 / 100);
AMP.leverETH(msg.sender, amount, took / 1e12);
}
function leverUSD(uint amount, address token)
external returns (uint usdcAmount) {
usdcAmount = amount; uint160 sqrtPriceX96;
if (token == address(USDC)) {
USDC.transferFrom(msg.sender,
address(this), usdcAmount);
} else {
(sqrtPriceX96,,,) = V4.repack();
uint depositedAmount = deposit(
msg.sender, token, usdcAmount);
uint scale = IERC20(token).decimals() - 6;
depositedAmount /= scale > 0 ? 10 ** scale : 1;
// Swap stable → ETH → USDC through V4 pool
CORE.swap(sqrtPriceX96, address(this),
V4.token1isETH(), token, depositedAmount);
uint ethReceived = address(this).balance;
WETH.deposit{value: ethReceived}();
_supplyAAVE(address(WETH), ethReceived,
address(this));
uint usdcBefore = USDC.balanceOf(address(this));
CORE.swap(sqrtPriceX96, address(this), !V4.token1isETH(),
address(USDC), ethReceived); vogueETH += ethReceived;
usdcAmount = USDC.balanceOf(
address(this)) - usdcBefore;
} uint twapPrice = getTWAP(0);
require(v3Fair(twapPrice));
uint targetETH = BasketLib.convert(
usdcAmount, getTWAP(1800), true);
(uint inETH, uint spent) = BasketLib.source(
_buildContext(), targetETH,
usdcAmount, twapPrice, false);
usdcAmount -= spent;
require(inETH >= targetETH * 99 / 100);
USDC.approve(address(AMP), usdcAmount);
AMP.leverUSD(msg.sender, usdcAmount, inETH);
}
/// @notice Convert Basket tokens into dollars
/// @param amount of tokens to redeem, 1e18...
function redeem(uint amount) external {
(uint total,) = get_metrics(false);
uint price = getTWAP(1800); _syncETH();
uint ethAvailable = _availableETH();
uint pooledUSD = CORE.POOLED_USD() * 1e12;
uint usdAvailable = total > pooledUSD ?
total - pooledUSD : 0;
uint ethExcess = ethAvailable > vogueETH ?
ethAvailable - vogueETH : 0;
uint ethValue = FullMath.mulDiv(
ethExcess, price, WAD);
uint reserved = Math.min(amount,
usdAvailable + ethValue);
(uint burned, uint seedBurned) = QUID.turn(
msg.sender, Math.min(reserved, usdAvailable));
uint taken = _take(msg.sender, burned,
address(QUID), seedBurned);
if (taken < reserved) {
uint ethToUse = Math.min(FullMath.mulDiv(
reserved - taken, WAD, price), ethExcess);
if (ethToUse > 0) { price = getTWAP(0); require(v3Fair(price));
uint received = _withdrawAAVE(address(WETH), ethToUse,
address(this));
received = BasketLib.sourceExternalUSD(_buildContext(),
received, price);
if (received > 0) IERC20(stables[0]).transfer(
msg.sender, received);
}
}
}
function get_deposits() public
returns (uint[14] memory amounts) {
amounts = BasketLib.get_deposits(address(this), address(HUB)
!= address(0) ? address(SPOKE): address(POOL), address(HUB), stables);
address stable = stables[9]; address vault = vaults[stable];
(uint spTotal, uint spYieldWeighted) = BasketLib.calcSPValue(vault, address(this),
untouchables[stable], BasketLib.SPState(spValue,
spTotalYield, spPrincipalTime, spLastUpdate));
if (spTotal > 0) { amounts[12] += spTotal;
amounts[10] = spTotal;
amounts[0] += spYieldWeighted; }
uint b = QUID.l2Deposits();
amounts[12] += b; amounts[13] = b;
}
/// @notice Get USYC amount redeemable today
/// @dev msg.sender in BasketLib will be Aux
/// @return Redeemable amount scaled to 1e18
function getUSYCRedeemable() external view returns (uint) {
address teller = vaults[stables[stables.length - 1]];
return BasketLib.getUSYCRedeemable(teller);
}
// she let me into a conversation, conversation only kate could make
// breaking into my imagination: whatever's there, was hers to take
function _take(address who, uint amount, address token, uint seed)
internal returns (uint sent) { address vault; address skip;
uint index = toIndex[token];
uint[14] memory amounts = get_deposits();
if (token != address(QUID)) { skip = token;
require(index > 0 && index < 12);
uint needed = FeeLib.calcNeeded(token, amount,
amounts, stables, LINK);
if (seed > 0) { _tip(seed, token, -1);
sent = _withdraw(who, index, needed);
return sent;
}
sent = _withdraw(who, index, needed);
amount = needed > sent ? needed - sent : 0;
sent = BasketLib.scaleTokenAmount(sent, token, true);
amount = BasketLib.scaleTokenAmount(amount, token, true);
}
if (amounts[12] == 0 || amount == 0) return sent;
uint min = amounts[12]; amount = seed == 0 ?
Math.min(min, amount) : amount;
// amounts[12] excludes untouchable
for (uint i = 1; i <= stables.length; i++) {
token = stables[i - 1]; if (token == skip) continue;
amounts[i] = FullMath.mulDiv(amount, FullMath.mulDiv(WAD,
amounts[i], amounts[12]), WAD);
if (seed > 0) _tip(FullMath.mulDiv(amounts[i],
seed, amount), token, -1);
amounts[i] = FeeLib.applyFeeAndHaircut(token, i - 1,
amounts[i], amounts, stables, LINK);
if (amounts[i] > 0) {
// 6-dec tokens: USDT(i=1), USDC(2), PYUSD(3), USYC(11)
uint divisor = (i < 4 || i == 11) ? 1e12 : 1;
amounts[i] = _withdraw(who, i,
amounts[i] / divisor);
sent += amounts[i] * divisor;
}
}
sent += QUID.distributeL2(who, amount, amounts[12]);
}
// don't check sent == passed in...
function take(address who, uint amount,
address token, uint seed) public onlyUs
returns (uint) { address weth = address(WETH);
return (token == weth) ? _withdrawAAVE(weth,
amount, who):
_take(who, amount, token, seed);
}
function _withdraw(address to,
uint index, uint amount) internal
returns (uint sent) { address vault;
// sent is 1e16 for USDC & USDT...
if (amount == 0) return 0;
if (index == 10) { address bold = stables[9]; vault = vaults[bold];
BasketLib.SPWithdrawResult memory r = BasketLib.withdrawFromSP(vault,
bold, address(WETH), amount, getTWAP(0), BasketLib.SPState(spValue,
spTotalYield, spPrincipalTime, spLastUpdate));
if (r.boldReceived == 0) return 0;
spValue = r.newSpValue;
spTotalYield = r.newSpTotalYield;
spPrincipalTime = r.newSpPrincipalTime;
spLastUpdate = r.newSpLastUpdate; sent = r.sent;
if (r.wethGain > 0) { _supplyAAVE(address(WETH), r.wethGain,
address(this));
vogueETH += r.wethGain;
}
} else if (index < 5) { vault = stables[index - 1];
if (index == 1) { (sent,) = BasketLib.withdrawUSYC(
vaults[stables[10]], to, amount);
amount -= sent;
} if (amount > 0) sent += _withdrawAAVE(vault, amount,
to);
} else if (index != 11) { vault = vaults[stables[index - 1]];
(amount,) = BasketLib.calculateVaultWithdrawal(vault, amount);
if (amount == 0) return 0; // skip if no shares to redeem...
sent = IERC4626(vault).redeem(
amount, to, address(this));
} else (sent,) = BasketLib.withdrawUSYC(
vaults[stables[10]], to, amount);
} // there's never an incentive
// for EOAs to call this since
// mint() is the only way to
// get yield for a deposit...
// so it's assumed only our
// contracts will call this...
function deposit(address from,
address token, uint amount) public
returns (uint usd) { address vault;
if (tokens[token] != address(0)
&& token != address(POOL)) {
amount = Math.min(
IERC4626(token).convertToShares(amount),
IERC4626(token).allowance(from, address(this)));
usd = IERC4626(token).convertToAssets(amount);
require(usd > 0 && IERC4626(token).transferFrom(from,
address(this), amount));
token = tokens[token];
} else { uint index = toIndex[token];
require(index > 0 && index < 12);
usd = Math.min(amount, IERC20(token).allowance(
from, address(this)));
IERC20(token).transferFrom(from, address(this), usd);
// ^ no require() for this b/c USDT doesn't return true
require(usd > 0);
(usd, amount) = _supply(
token, index, usd);
} uint _target = QUID.target();
if (untouchable < _target // fee
&& msg.sender == address(QUID)) {
uint fee = BasketLib.seedFee(usd, untouchable,
_target, getAverageYield());
if (fee > 0) {
_tip(fee, token, 1);
if (token == address(USDC) && amount > 0
&& untouchable < _target) _tip(Math.min(
FullMath.mulDiv(amount, fee, usd),
_target - untouchable),
stables[stables.length - 1], 1);
}
}
} function _tip(uint cut, address token, int sign) internal {
cut = BasketLib.scaleTokenAmount(cut, token, true);
if (sign > 0) {
untouchables[token] += cut; untouchable += cut;
} else {
cut = Math.min(cut, untouchables[token]);
untouchables[token] -= cut;
untouchable -= Math.min(untouchable, cut);
}
}
function _availableETH() internal returns (uint) {
if (address(SPOKE) == address(0))
return BasketLib.aaveAvailableV3(
address(POOL), address(WETH));
else { uint reserveId = SPOKE.getReserveId(address(HUB),
HUB.getAssetId(address(WETH)));
return SPOKE.getUserSuppliedAssets(
reserveId, address(this));
}
}
function _supplyAAVE(address asset, uint amount,
address to) internal returns (uint deposited) {
if (asset == address(WETH)) _syncETH();
deposited = BasketLib.supplyAAVE(address(HUB) != address(0) ? address(SPOKE) : address(POOL),
asset, amount, to, address(HUB));
if (asset == address(WETH))
_lastTotalETH = _availableETH();
}
function _withdrawAAVE(address asset, uint amount,
address to) internal returns (uint drawn) {
if (asset == address(WETH)) _syncETH();
drawn = BasketLib.withdrawAAVE(address(HUB) != address(0) ? address(SPOKE) : address(POOL),
asset, amount, to, address(HUB));
if (asset == address(WETH))
_lastTotalETH = _availableETH();
}
function _depositETH(address sender,
uint amount) internal returns (uint sent) {
if (msg.value > 0) { sent = msg.value;
WETH.deposit{value: msg.value}();
}
if (amount > 0) { uint available = Math.min(
WETH.allowance(sender, address(this)),
WETH.balanceOf(sender));
uint took = Math.min(amount, available);
if (took > 0) { WETH.transferFrom(sender,
address(this), took);
sent += took;
}
} require(sent > 0);
} /// @param borrower receive tokens & callback
/// @param token Stable token being borrowed...
/// @param amount Token amount (native decimals)
/// @param shareBps LP Profit share signals
/// higher priority to builders/sequencers
/// commitment (100 = 1% min, 10000 = 100%)
/// @param data passed to borrower callback
function flashLoan(address borrower,
address token, uint amount, uint shareBps,
bytes calldata data) external nonReentrant
returns (bool) { require(msg.sender == JAM);
uint bal = IERC20(token).balanceOf(address(this));
uint sent = take(borrower, amount, token, 0);
bytes32 result = IFlashBorrower(borrower).onFlashLoan(
borrower, token, sent, shareBps, data);
uint repaid = IERC20(token).balanceOf(address(this)) - bal;
require(result == CALLBACK_SUCCESS &&
repaid >= (toIndex[token] > 0 &&
toIndex[token] < 4 ? sent / 1e12 : sent));
if (token == address(WETH)) { _supplyAAVE(address(WETH),
repaid, address(this)); vogueETH += repaid;
} else _supply(token, toIndex[token], repaid);
return true; // Block builders don't introspect
}
// they see priority fees, explicit bribes, not
// internal profit splits. Bebop's orchestrator
// could score solvers by committed shareBps,
// routing more flow to generous solvers...
function _supply(address token, uint index,
uint usd) internal returns (uint, uint) {
address vault = vaults[token]; uint amount;
if (index == 10) // BOLD -> Stability Pool
(spValue, spPrincipalTime, spLastUpdate) = BasketLib.depositToSP(
vault, usd, BasketLib.SPState(spValue, spTotalYield,
spPrincipalTime, spLastUpdate));
else if (index < 5) { // AAVE: USDC(1), USDT(2), PYUSD(3), GHO(4)
vault = address(SPOKE) != address(0) ? address(SPOKE) : address(POOL);
if (index == 1 && QUID.marketCreated())
(amount, usd) = BasketLib.depositUSYC(vaults[stables[10]],
vault, address(USDC), usd, address(HUB));
if (usd > 0) _supplyAAVE(token, usd,
address(this));
}
// DAI(5), USDS(6), FRAX(7), USDE(8), CRVUSD(9)
else if (index != 11) // 4626 returns shares...
usd = IERC4626(vault).convertToAssets(
IERC4626(vault).deposit(usd,
address(this)));
return (usd, amount);
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import {Types} from "./imports/Types.sol";
import {FeeLib} from "./imports/FeeLib.sol";
import {FullMath} from "v4-core/src/libraries/FullMath.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {OptimisticOracleV3Interface} from "./imports/OOV3Interface.sol";
interface ILink { // chainlink aggregator interface
function trigger(uint timestamp, uint8 winningSide, uint severityBps) external;
function onAssertionFiled() external;
function onDisputeStarted() external;
function onAssertionRejected() external;
function onDisputeEnded() external;
function paid() external view returns (bool);
function resetForNewRound() external;
function getRoundStartTime() external view returns (uint);
function getMarketCapital() external view returns (uint);
}
/// @dev STATICCALL.
/// LMSR internals (cost, exp) inline
/// from BasketLib into UMA's bytecode.
interface IUMA { function settleAssertion() external;
function getAssertionInfo() external view returns
(uint8 phase, uint8 claimedSide, uint round, uint8 rejections);
function buyTokens(int128[12] memory q, uint8 numSides, int128 b, uint8 side, uint netCap) external pure returns (uint tokens, int128 deltaQ);
function sellTokens(int128[12] memory q, uint8 numSides, int128 b, uint8 side, uint tokSell) external pure returns (uint returned, int128 deltaQ);
function computeWeight(uint[] memory capitals, uint[] memory timestamps, uint roundStart, uint resTs, uint lambda, uint floor, uint confidence, bool isWinner) external pure returns (uint);
function computePayout(uint capital, uint weight, uint totalWinWeight, uint totalLoseWeight, uint totalLoserCap, uint consolBps, bool isWinner) external pure returns (uint);
function reduceEntries(uint[] memory capitals, uint[] memory tokens, uint tokensToSell, uint totalTokens) external pure returns (uint[] memory, uint[] memory, uint);
}
/// @dev Court interface — arbitration for DVM/CRE conflicts.
interface ICourt {
function requestArbitration(
bytes32 assertionId,
uint8 numSides,
uint8 claimedSide,
uint8 recommendedSide,
uint8 confidence
) external;
}
/// @title UMA — OOV3 phase machine for single depeg market
/// @notice Weekly rounds. Asserter claims which side won.
/// Parallel assertions: multiple claimants can file simultaneously.
/// First confirmed assertion wins. Side 0 ("none depegged") is
/// decoupled from OOV3 — uses a permissionless timeout via resolveAsNone().
/// Caller funds the bond via transferFrom — returned on success,
/// lost on rejection. Escalating bond makes griefing exponentially expensive.
///
/// When CRE auto-disputed an assertion with high confidence but the DVM
/// overrules (confirms truthful), neither oracle is trusted at face value.
/// Market enters Arbitrating phase and routes to the opt-in jury Court
/// for final resolution.
contract UMA is Ownable {
uint public constant LIVENESS = 127 hours;
uint public constant REVEAL_WINDOW = 48 hours;
// How long to wait after a dispute before escalating to Court without CRE.
// Gives CRE time to submit forensic evidence; anyone can call escalateToCourt()
// once this window has elapsed with no evidence.
uint public constant CRE_TIMEOUT = 48 hours;
uint public constant BOND_FLOOR = 100e6; // $100 in USDC (6 dec)
uint public constant BOND_CEILING = 10_000e6; // $10k in USDC (6 dec)
IERC20 public immutable BOND_TOKEN;
uint public constant BOND_BPS = 1;
uint8 constant MAX_SIDES = 12;
uint constant WAD = 1e18;
ILink public LINK;
OptimisticOracleV3Interface public immutable OO;
address public FORWARDER; // CRE KeystoneForwarder
address public COURT; // arbitrates DVM/CRE clash
address public QUID; // Basket
bool public marketRegistered;
Types.Market internal market;
// ═══════════════════════════════════════════════════════════════
// Parallel assertion tracking
// Multiple asserters can file claims simultaneously.
// First confirmed assertion resolves the market.
// Rejected assertions decrement the counter; when all are
// rejected the market returns to Trading and LINK unfreezes.
// ═══════════════════════════════════════════════════════════════
struct AssertionContext {
address asserter; // who funded the bond
uint8 claimedSide; // 1..N (never 0 — that uses resolveAsNone)
uint bond; // amount transferred from asserter
uint requestTimestamp;
uint round; // which market round this belongs to
bool disputed; // set by assertionDisputedCallback
uint maxDeviationBps; // depeg severity passed to Link on resolution
uint disputedAt; // timestamp of dispute; 0 if not disputed
}
mapping(bytes32 => AssertionContext) public assertions;
bytes32[] public pendingAssertionIds;
uint public activeAssertionCount;
/// @dev round → side → assertionId. Prevents duplicate per side per round.
/// Cleared on rejection (re-opens the side). Scoped by round.
mapping(uint => mapping(uint8 => bytes32)) public sideAssertionId;
error WrongPhase(Types.Phase actual, Types.Phase expected);
mapping(bytes32 => Types.ForensicEvidence) public evidence;
mapping(bytes32 => bool) public evidenceSubmitted;
/// @dev The assertionId that triggered arbitration, so Court
/// and jurors can look up the on-chain ForensicEvidence.
bytes32 public arbitratingAssertionId;
/// @dev What the DVM confirmed (for Court/jury context).
uint8 internal _arbitratingDvmSide;
uint public watchdogDisputeCount;
uint8 public minConfidence = 80;
error NotQUID(); error NotOO();
error InvalidSide(uint8, uint8);
error NotForwarder();
error NotCourt();
event MarketRegistered(uint8 numSides);
event ResolutionRequested(bytes32 assertionId,
uint8 claimedSide, uint bond);
event AssertionDisputed();
event AssertionSettled(
uint8 winningSide, bool truthful);
event MarketRestarted(uint round);
event DisputeForensicsRequested(
bytes32 indexed assertionId,
uint8 claimedSide,
uint requestTimestamp
);
event ForensicEvidenceStored(
bytes32 indexed assertionId,
uint8 recommendedSide,
uint8 confidence
);
event WatchdogDispute(
bytes32 indexed assertionId,
uint8 claimedSide, uint8 recommendedSide,
uint maxDeviationBps, uint8 confidence, bytes32 evidenceHash
);
event WatchdogSkipped(bytes32 indexed assertionId, string reason);
/// @notice Emitted when DVM confirms an assertion that CRE disputed
/// with high confidence. Market enters Arbitrating phase pending
/// jury resolution via Court.
event ArbitrationRequested(
bytes32 indexed assertionId,
uint8 claimedSide,
uint8 recommendedSide,
uint8 confidence
);
/// @notice Emitted when Court delivers the jury's ruling.
event ArbitrationResolved(uint8 winningSide);
modifier onlyQUID() { if (msg.sender != QUID) revert NotQUID(); _; }
modifier onlyOO() { if (msg.sender != address(OO)) revert NotOO(); _; }
modifier onlyForwarder() {
if (msg.sender != FORWARDER)
revert NotForwarder(); _;
}
modifier onlyCourt() {
if (msg.sender != COURT)
revert NotCourt(); _;
}
modifier inPhase(Types.Phase p) {
if (market.phase != p)
revert WrongPhase(
market.phase, p); _;
}
constructor(address _oo,
address _bond) Ownable(msg.sender) {
OO = OptimisticOracleV3Interface(_oo);
BOND_TOKEN = IERC20(_bond);
}
/// @dev Set the CRE KeystoneForwarder address.
/// Callable once by owner. Deploy UMA first, then set
/// forwarder when Chainlink publishes the mainnet address.
function setForwarder(address _f) external onlyOwner {
require(FORWARDER == address(0), "already set");
require(_f != address(0), "zero address");
FORWARDER = _f;
}
/// @dev onlyOwner prevents frontrunning during deployment.
function setup(address _quid, address _link, address _court) external
onlyOwner { QUID = _quid; LINK = ILink(_link); COURT = _court; }
function registerMarket(address[] calldata stables) external onlyQUID {
require(!marketRegistered, "exists");
marketRegistered = true;
uint8 n = uint8(stables.length);
market.numSides = n + 1;
market.phase = Types.Phase.Trading;
market.roundNumber = 1;
emit MarketRegistered(market.numSides);
}
// ═══════════════════════════════════════════════════════════════
// Side 0 resolution — permissionless timeout, no OOV3
// "None depegged" is the default outcome when a full round
// passes without a depeg claim. Anyone can trigger after
// MONTH elapsed from round start. No bond needed.
// ═══════════════════════════════════════════════════════════════
/// @notice Resolve as "none depegged" — permissionless timeout.
/// Callable by anyone once MONTH has elapsed since round start.
/// Bypasses OOV3 entirely since side 0 is the default/null outcome.
function resolveAsNone() external inPhase(Types.Phase.Trading) {
require(block.timestamp >=
LINK.getRoundStartTime() + FeeLib.MONTH,
"round not mature");
market.phase = Types.Phase.Resolved;
market.winningSide = 0;
market.revealDeadline = block.timestamp + REVEAL_WINDOW;
market.consecutiveRejections = 0;
LINK.trigger(block.timestamp, 0, 0);
emit AssertionSettled(0, true);
}
// ═══════════════════════════════════════════════════════════════
// Depeg assertions (side > 0) — OOV3 backed, caller-funded
// Multiple assertions can coexist. First confirmed wins.
// Bond pulled from msg.sender → returned on success, lost on
// rejection. Escalating bond after consecutive rejections makes
// griefing exponentially expensive.
// ═══════════════════════════════════════════════════════════════
/// @notice Assert which side won this round.
/// Side 0 ("none depegged") must use resolveAsNone().
/// Caller funds the bond — returned on success, lost on rejection.
/// Multiple asserters may file in parallel; first confirmed wins.
function requestResolution(uint8 claimedSide, uint16 maxDeviationBps)
external returns (bytes32 assertionId) {
require(claimedSide > 0, "use resolveAsNone");
require(market.phase == Types.Phase.Trading
|| market.phase == Types.Phase.Asserting,
"market not accepting assertions");
require(activeAssertionCount < market.numSides, "too many pending assertions");
if (claimedSide >= market.numSides) revert InvalidSide(claimedSide, market.numSides);
require(sideAssertionId[market.roundNumber][claimedSide] == bytes32(0),
"side already asserted");
uint bond = getMinimumBond();
// Pull bond from caller — skin in the game
BOND_TOKEN.transferFrom(msg.sender, address(this), bond);
BOND_TOKEN.approve(address(OO), bond);
bytes memory claim = abi.encodePacked(
"Depeg market round ", _uint2str(market.roundNumber),
" outcome: side ", _uint2str(uint(claimedSide))
);
// asserter = address(this) → bond returns here on success
// callbackRecipient = address(this) → callbacks come here
assertionId = OO.assertTruth(
claim, address(this), address(this),
address(0), uint64(LIVENESS), BOND_TOKEN,
bond, OO.defaultIdentifier(), bytes32(0)
);
// Track this assertion
assertions[assertionId] = AssertionContext({
asserter: msg.sender,
claimedSide: claimedSide,
bond: bond,
requestTimestamp: block.timestamp,
round: market.roundNumber,
disputed: false,
maxDeviationBps: maxDeviationBps,
disputedAt: 0
});
sideAssertionId[market.roundNumber][claimedSide] = assertionId;
pendingAssertionIds.push(assertionId);
activeAssertionCount++;
// First assertion in this round freezes trading
if (market.phase == Types.Phase.Trading) {
market.phase = Types.Phase.Asserting;
LINK.onAssertionFiled();
}
emit ResolutionRequested(
assertionId, claimedSide, bond);
}
/// @notice Settle all pending assertions whose liveness has expired.
function settleAssertion() external {
require(market.phase == Types.Phase.Asserting
|| market.phase == Types.Phase.Disputed
|| market.phase == Types.Phase.Arbitrating, "wrong phase");
for (uint i; i < pendingAssertionIds.length; i++) {
bytes32 id = pendingAssertionIds[i];
if (assertions[id].asserter != address(0)) {
try OO.settleAssertion(id) {} catch {}
}
}
}
function assertionResolvedCallback(bytes32 assertionId,
bool truthful) external onlyOO {
AssertionContext memory ctx = assertions[assertionId];
if (ctx.asserter == address(0)) return; // unknown / already processed
// Stale assertion from previous round disputed late;
// must NOT touch LINK state for the current round...
// Without this guard, a $0 dispute on a stale assertion
// sets disputeFrozen=true permanently (no recovery path).
if (ctx.round != market.roundNumber) {
delete assertions[assertionId]; return;
}
delete assertions[assertionId];
if (activeAssertionCount > 0) activeAssertionCount--;
if (truthful) {
if (market.phase == Types.Phase.Resolved
|| market.phase == Types.Phase.Arbitrating) {
// Market already resolved or in arbitration — just refund
BOND_TOKEN.transfer(ctx.asserter, ctx.bond);
emit AssertionSettled(market.winningSide, true);
if (ctx.disputed) LINK.onDisputeEnded();
// Decrement pendingAssertions so trading unfreezes once
// the jury delivers its ruling and _resolveArbitration fires.
LINK.onAssertionRejected();
return;
}
// Dispute was already routed to Court in assertionDisputedCallback.
// DVM resolution of a disputed assertion is handled by the
// Arbitrating guard above (bond refund path). We only reach
// here for undisputed assertions resolving truthfully.
market.phase = Types.Phase.Resolved;
market.winningSide = ctx.claimedSide;
market.revealDeadline = block.timestamp + REVEAL_WINDOW;
market.consecutiveRejections = 0;
LINK.trigger(block.timestamp, ctx.claimedSide, ctx.maxDeviationBps);
// OOV3 returned the bond to us — refund the winning asserter
BOND_TOKEN.transfer(ctx.asserter, ctx.bond);
} else {
// Rejected — asserter loses bond (OOV3 slashed it)
if (market.consecutiveRejections < 255)
market.consecutiveRejections++;
// Re-open this side for a fresh assertion
if (ctx.round == market.roundNumber)
sideAssertionId[ctx.round][ctx.claimedSide] = bytes32(0);
// All assertions exhausted → unfreeze
if (activeAssertionCount == 0
&& market.phase != Types.Phase.Resolved) {
market.phase = Types.Phase.Trading;
// roundStartTime intentionally NOT reset — early entrants
// keep their time-decay advantage. Resetting would let a
// $100 false assertion wipe the entire decay curve.
LINK.onAssertionRejected();
}
}
emit AssertionSettled(market.winningSide, truthful);
if (ctx.disputed) LINK.onDisputeEnded();
}
// ═══════════════════════════════════════════════════════════════
// Court ruling — jury resolves DVM/CRE conflict
//
// winningSide is the jury's final answer to "which oracle was
// right?" — either the DVM's claimedSide or the CRE's
// recommendedSide (or theoretically a third option if the jury
// determines both were wrong).
//
// This does exactly what the normal truthful resolution path
// does: resolve the market, trigger LINK, unfreeze trading.
// ═══════════════════════════════════════════════════════════════
/// @notice Called by Court with the jury's ruling.
/// @param winningSide The jury's verdict on which side won.
/// @param severityBps Haircut severity in bps (e.g. 500 = 5%).
/// Jury determines this since CRE is not yet live.
/// Pass 0 if no depeg was found (winningSide == 0).
function receiveRuling(uint8 winningSide, uint16 severityBps)
external onlyCourt inPhase(Types.Phase.Arbitrating) {
require(winningSide < market.numSides, "invalid side");
require(winningSide == 0 || severityBps >= 100, "severity below 1%");
_resolveArbitration(winningSide, severityBps);
emit ArbitrationResolved(winningSide);
}
/// @dev Shared resolution logic for Court ruling.
function _resolveArbitration(uint8 winningSide, uint severityBps) internal {
market.phase = Types.Phase.Resolved;
market.winningSide = winningSide;
market.revealDeadline = block.timestamp + REVEAL_WINDOW;
market.consecutiveRejections = 0;
arbitratingAssertionId = bytes32(0);
_arbitratingDvmSide = 0;
LINK.trigger(block.timestamp, winningSide, severityBps);
LINK.onDisputeEnded();
// Decrement pendingAssertions so trading can resume after jury ruling.
// The assertion that triggered arbitration counted as a pending assertion;
// the jury's ruling is its final resolution.
LINK.onAssertionRejected();
}
function assertionDisputedCallback
(bytes32 assertionId) external onlyOO {
AssertionContext storage ctx = assertions[assertionId];
require(ctx.asserter != address(0), "unknown assertion");
ctx.disputed = true;
ctx.disputedAt = block.timestamp;
if (market.phase == Types.Phase.Asserting)
market.phase = Types.Phase.Disputed;
LINK.onDisputeStarted();
emit AssertionDisputed();
// Emit so CRE can pick this up and submit forensic evidence
// within the CRE_TIMEOUT window. If CRE is not live or misses
// the window, anyone can call escalateToCourt() after the deadline.
emit DisputeForensicsRequested(assertionId,
ctx.claimedSide, ctx.requestTimestamp);
}
/// @notice Escalate a disputed assertion to Court after the CRE timeout.
/// Callable by anyone once CRE_TIMEOUT has elapsed since the dispute
/// and no CRE forensic evidence has arrived. Routes the jury to decide
/// both the outcome and the haircut severity.
function escalateToCourt(bytes32 assertionId) external {
AssertionContext memory ctx = assertions[assertionId];
require(ctx.asserter != address(0), "unknown");
require(ctx.disputed, "not disputed");
require(ctx.claimedSide > 0, "side 0 uses resolveAsNone");
require(block.timestamp >= ctx.disputedAt + CRE_TIMEOUT,
"CRE window open");
require(!evidenceSubmitted[assertionId], "CRE responded");
require(market.phase == Types.Phase.Disputed, "wrong phase");
require(COURT != address(0), "no court");
ICourt(COURT).requestArbitration(
assertionId, market.numSides,
ctx.claimedSide,
ctx.claimedSide, // no CRE recommendation — jury decides
0); // no CRE confidence — jury is authoritative
market.phase = Types.Phase.Arbitrating;
arbitratingAssertionId = assertionId;
_arbitratingDvmSide = ctx.claimedSide;
emit ArbitrationRequested(
assertionId, ctx.claimedSide, ctx.claimedSide, 0);
}
function restartMarket() external inPhase(Types.Phase.Resolved) {
require(block.timestamp >= market.revealDeadline && LINK.paid());
market.winningSide = 0;
market.phase = Types.Phase.Trading;
market.consecutiveRejections = 0;
market.revealDeadline = 0;
market.roundNumber++;
activeAssertionCount = 0;
delete pendingAssertionIds;
LINK.resetForNewRound();
emit MarketRestarted(
market.roundNumber);
}
// ═══════════════════════════════════════════════════════════════
// Stores evidence for DVM voters. Advisory only — the DVM
// vote is the sole source of truth for market resolution
// UNLESS it conflicts with CRE, in which case Court arbitrates.
// onReport() is the sole CRE entry point — stores evidence
// AND auto-disputes when warranted. First write per
// assertionId wins (idempotent via evidenceSubmitted).
// ═══════════════════════════════════════════════════════════════
function _storeEvidence(bytes32 assertionId,
uint8 claimedSide, uint8 recommendedSide,
uint maxDeviationBps, uint8 confidence,
bytes32 evidenceHash) internal {
if (evidenceSubmitted[assertionId]) return;
evidenceSubmitted[assertionId] = true;
evidence[assertionId] = Types.ForensicEvidence({
claimedSide: claimedSide,
recommendedSide: recommendedSide,
maxDeviationBps: maxDeviationBps,
confidence: confidence,
evidenceHash: evidenceHash,
timestamp: block.timestamp
});
emit ForensicEvidenceStored(assertionId,
recommendedSide, confidence);
}
function getEvidence(bytes32 assertionId) external
view returns (Types.ForensicEvidence memory) {
return evidence[assertionId];
}
// ═══════════════════════════════════════════════════════════════
// Receives reports from Chainlink CRE via KeystoneForwarder.
// If evidence contradicts the assertion with high confidence,
// auto-disputes via OOV3 using USDC held by this contract.
// DVM vote remains the source of truth unless it conflicts
// with CRE, in which case Court arbitrates.
// ═══════════════════════════════════════════════════════════════
/// @notice Called by CRE KeystoneForwarder with forensic analysis.
/// Stores evidence AND auto-disputes if warranted.
/// Implements IReceiver.onReport(bytes metadata, bytes report).
function onReport(bytes calldata, bytes calldata report)
external onlyForwarder {
(bytes32 assertionId, uint8 claimedSide,
uint8 recommendedSide, uint maxDeviationBps,
uint8 confidence, bytes32 evidenceHash) = abi.decode(report,
(bytes32, uint8, uint8, uint, uint8, bytes32));
_storeEvidence(assertionId, claimedSide, recommendedSide,
maxDeviationBps, confidence, evidenceHash);
// Auto-dispute only for known pending undisputed assertions
AssertionContext memory ctx = assertions[assertionId];
if (ctx.asserter == address(0)) {
emit WatchdogSkipped(assertionId, "unknown assertion");
return;
}
if (ctx.disputed) {
emit WatchdogSkipped(assertionId, "already disputed");
return;
}
if (market.phase == Types.Phase.Resolved) {
emit WatchdogSkipped(assertionId, "already resolved");
return;
}
if (claimedSide == recommendedSide) {
emit WatchdogSkipped(assertionId, "evidence agrees");
return;
}
if (confidence < minConfidence) {
emit WatchdogSkipped(assertionId, "confidence below threshold");
return;
}
uint bond = ctx.bond; // match the asserter's bond
if (BOND_TOKEN.balanceOf(address(this)) < bond) {
emit WatchdogSkipped(assertionId, "insufficient bond balance");
return;
}
BOND_TOKEN.approve(address(OO), bond);
OO.disputeAssertion(assertionId, address(this));
// NOTE: assertionDisputedCallback fires synchronously above
watchdogDisputeCount++;
emit WatchdogDispute(
assertionId, claimedSide, recommendedSide,
maxDeviationBps, confidence, evidenceHash
);
}
/// @notice How many auto-disputes this contract can afford.
/// When assertions are active, uses the current escalated bond.
/// Otherwise falls back to OOV3's minimum bond.
function disputeCapacity() external view returns (uint) {
uint bond = activeAssertionCount > 0 ? getMinimumBond():
OO.getMinimumBond(address(BOND_TOKEN));
if (bond == 0) return type(uint).max;
return BOND_TOKEN.balanceOf(address(this)) / bond;
}
function setMinConfidence(uint8 _min) external onlyOwner {
require(_min <= 100, "invalid threshold");
minConfidence = _min;
}
function withdrawBond(uint amount,
address to) external onlyOwner {
BOND_TOKEN.transfer(to, amount);
}
/// @notice Bond = clamp(capital_usdc × BOND_BPS, FLOOR, CEILING) × 2^rejections
/// First assertion: normal bond. After 1 rejection: 2×. After 2: 4×.
/// Capped at BOND_CEILING to prevent overflow. Resets on success or new round.
function getMinimumBond() public view returns (uint) {
uint capital = LINK.getMarketCapital();
uint capitalUSDC = capital / 1e12;
// WAD (18 dec) → USDC (6 dec)
uint bp = (capitalUSDC * BOND_BPS) / 10000;
uint base = bp < BOND_FLOOR ? BOND_FLOOR :
bp > BOND_CEILING ? BOND_CEILING : bp;
uint8 rej = market.consecutiveRejections;
uint8 shift = rej > 7 ? 7 : rej; // cap at 128×
uint escalated = base << shift;
if (escalated > BOND_CEILING) escalated = BOND_CEILING;
uint ooMin = OO.getMinimumBond(address(BOND_TOKEN));
return escalated > ooMin ? escalated : ooMin;
}
function isTradingEnabled()
external view returns (bool) {
Types.Phase p = market.phase;
return p == Types.Phase.Trading
|| p == Types.Phase.Asserting;
}
function isRevealOpen()
external view returns (bool) {
return market.phase == Types.Phase.Resolved
&& block.timestamp < market.revealDeadline;
}
function getNumSides() external
view returns (uint8) {
return market.numSides;
}
function getAssertionInfo() external view
returns (uint8 phase, uint8 claimedSide,
uint round, uint8 rejections) {
return (uint8(market.phase), market.winningSide,
market.roundNumber, market.consecutiveRejections);
}
/// @notice Bond needed to dispute the first pending assertion,
/// or OOV3 minimum if none are active.
function getBondForDispute() external view returns (uint) {
if (pendingAssertionIds.length > 0) {
bytes32 id = pendingAssertionIds[0];
AssertionContext memory ctx = assertions[id];
if (ctx.asserter != address(0)) return ctx.bond;
}
return OO.getMinimumBond(address(BOND_TOKEN));
}
/// @notice Get details of a specific pending assertion.
function getAssertion(bytes32 assertionId) external view
returns (address asserter, uint bond, uint8 claimedSide,
uint round, uint requestTimestamp, bool disputed) {
AssertionContext memory ctx = assertions[assertionId];
return (ctx.asserter, ctx.bond, ctx.claimedSide,
ctx.round, ctx.requestTimestamp, ctx.disputed);
}
function getPendingAssertionCount() external
view returns (uint) {
return activeAssertionCount;
}
function _uint2str(uint v) internal
pure returns (bytes memory) {
if (v == 0) return "0";
uint t = v; uint d;
while (t != 0) {
d++; t /= 10;
}
bytes memory b = new bytes(d);
while (v != 0) {
b[--d] = bytes1(uint8(48 + v % 10));
v /= 10;
} return b;
}
/// @notice Binary search to buy tokens on LMSR curve.
/// All values in WAD (18-decimal). No scaling needed.
function buyTokens(int128[MAX_SIDES] memory q,
uint8 numSides, int128 b, uint8 side,
uint netCap) external pure
returns (uint tokens, int128 deltaQ) {
int128 capWAD = int128(int256(netCap));
// At minimum price (1/numSides), max tokens ≈ capWAD × numSides.
// 2× safety margin covers price movement during purchase.
// _expNorm clamps exp(-x) → 0 for x > 100 to avoid overflow.
int128 lo; int128 hi = capWAD * int128(int256(
uint256(numSides))) * 2;
for (uint i; i < 64; i++) {
int128 mid = (lo + hi) / 2;
uint c = FeeLib.cost(q,
numSides, b, side, mid);
if (c <= uint(uint128(capWAD))) lo = mid;
else hi = mid;
if (hi - lo <= 1) break; // converge to 1 wei
} tokens = uint(uint128(lo)); deltaQ = lo;
}
/// @notice All values in WAD (18-decimal)...
function sellTokens(int128[MAX_SIDES] memory q,
uint8 numSides, int128 b, uint8 side,
uint tokSell) external pure // LMSR
returns (uint returned, int128 deltaQ) {
int128 tokWAD = int128(int256(tokSell));
uint refund = FeeLib.cost(q,
numSides, b, side, -tokWAD);
returned = refund; deltaQ = tokWAD;
}
/// @notice Compute a single position's payout weight.
/// capitals[] are in WAD. decay is in bps (0-10000).
/// Result: weight = confFactor × Σ(capital × decay) / 10000
function computeWeight(uint[] memory capitals,
uint[] memory timestamps, uint roundStart,
uint resTs, uint lambda, uint floor,
uint confidence, bool isWinner)
external pure returns (uint weight) {
uint timeWeightedCap; uint decay;
for (uint j; j < capitals.length; j++) {
if (resTs <= roundStart) decay = 10000;
else { uint p; // participation in bps
{ // scope: mktD & posD die here (stack relief)
uint mktD = resTs - roundStart;
uint posD = timestamps[j] <= roundStart ? mktD :
(timestamps[j] >= resTs ? 0 : resTs - timestamps[j]);
p = Math.min((posD * 10000) / mktD, 10000);
} // ← mktD, posD freed from stack
if (lambda <= 100) decay = p;
else if (lambda <= 200) {
uint t = lambda - 100;
uint qd = (p * p) / 10000;
decay = (p * (100 - t) + qd * t) / 100;
} else {
uint qd = (p * p) / 10000;
decay = (qd * p) / 10000;
}
if (decay < floor) decay = floor;
} timeWeightedCap += capitals[j] * decay;
} uint twcWAD = timeWeightedCap / 10000;
// timeWeightedCap is WAD × bps. Divide by 10000 to get WAD...
// confidence is 100-10000 (1-100%). Normalize to WAD fraction.
uint confNorm = (confidence * WAD) / 10000;
if (isWinner)
weight = FullMath.mulDiv(
confNorm, twcWAD, WAD);
else { uint inv = WAD - confNorm;
weight = FullMath.mulDiv(inv > 0 ?
inv : 1, twcWAD, WAD);
}
}
/// @notice Compute a single position's payout amount (18-decimal)...
/// If no winners exist, entire loser capital goes to consolation pool
/// to prevent permanent capital lock.
function computePayout(uint capital, uint weight,
uint totalWinWeight, uint totalLoseWeight, // 20% goes back...
uint totalLoserCap, uint consolBps, bool isWinner) external pure
returns (uint payout) { uint winnerPool; uint consolPool;
if (totalWinWeight == 0) { // No winners: all loser capital
// → consolation (no one to receive winner pool)
winnerPool = 0; consolPool = totalLoserCap;
} else {
winnerPool = FullMath.mulDiv(totalLoserCap,
10000 - consolBps, 10000);
consolPool = totalLoserCap - winnerPool;
}
if (isWinner) {
uint bonus = totalWinWeight > 0 ? FullMath.mulDiv(
winnerPool, weight, totalWinWeight) : 0;
payout = capital + bonus;
} else {
payout = totalLoseWeight > 0 ?
FullMath.mulDiv(consolPool,
weight, totalLoseWeight): 0;
}
}
/// @notice Pro-rata reduce entries selling tokens
function reduceEntries(uint[] memory capitals,
uint[] memory tokens, uint tokensToSell,
uint totalTokens) external pure returns (
uint[] memory newCapitals,
uint[] memory newTokens,
uint totalCapReduced) {
uint len = capitals.length;
newCapitals = new uint[](len);
newTokens = new uint[](len);
for (uint i; i < len; i++) {
newCapitals[i] = capitals[i];
newTokens[i] = tokens[i];
}
uint tokensRemaining = tokensToSell;
for (uint i; i < len; i++) {
if (newTokens[i] == 0) continue;
// Last active entry absorbs rounding residual
uint tokFromEntry;
if (tokensRemaining >= newTokens[i]) {
tokFromEntry = newTokens[i];
} else if (i == len - 1 || _isLastActive(newTokens, i + 1, len)) {
tokFromEntry = tokensRemaining;
} else {
tokFromEntry = (newTokens[i] *
tokensToSell) / totalTokens;
if (tokFromEntry > tokensRemaining)
tokFromEntry = tokensRemaining;
}
if (tokFromEntry >= newTokens[i]) {
totalCapReduced += newCapitals[i];
newCapitals[i] = 0;
newTokens[i] = 0;
} else {
uint capFromEntry = (newCapitals[i]
* tokFromEntry) / newTokens[i];
newTokens[i] -= tokFromEntry;
newCapitals[i] -= capFromEntry;
totalCapReduced += capFromEntry;
}
tokensRemaining -= tokFromEntry;
if (tokensRemaining == 0) break;
}
}
/// @dev Check if all entries from `start` to `end` have zero tokens
function _isLastActive(uint[] memory toks,
uint start, uint end) internal pure returns (bool) {
for (uint j = start; j < end; j++)
if (toks[j] > 0) return false;
return true;
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import {IUMA} from "./UMA.sol";
import {Basket} from "./Basket.sol";
import {Types} from "./imports/Types.sol";
import {FeeLib} from "./imports/FeeLib.sol";
import {FullMath} from "v4-core/src/libraries/FullMath.sol";
/// @title N-outcome LSMR for "which stablecoin depegs this week"
/// @notice Side 0 = "none depegs". Sides 1..N = each stablecoin.
/// Single market, weekly rounds. Time decay rewards early signal.
/// Rollover positions recommit each round for fresh LMSR entry.
contract Link { Types.Market internal _market;
uint public constant FEE_BPS = 400; // 4% on fresh orders
uint public constant ROLLOVER_FEE_BPS = 200; // 2% on recommit
uint public constant MIN_ORDER = 1e18; // 1 QD (18 decimals)
uint public constant CONSOLATION_BPS = 2000; // 20% to losers
uint public constant REVEAL_WINDOW = 48 hours;
int128 public constant INITIAL_B = 10_000e18;
/// @dev decay lambda for 12-outcome weekly market.
/// Derived from: λ_base(5) × complexity_adj(0.774)
/// × duration_factor(0.327) = 1.265, scaled ×100 → 127.
/// Falls in the linear-quadratic blend zone (100 < λ ≤ 200):
/// t = 26 → 74% linear + 26% quadratic
/// Full week (100% participation) → 100% weight
/// Mid-week entry (50%) → ~43% weight
/// Day 5 entry (29%) → ~23% weight
/// Last minute (~0%) → 10% floor
struct RevealEntry { uint confidence; bytes32 salt; }
Basket public immutable QUID; IUMA public immutable UMA;
uint public constant DECAY_FLOOR = 1000; // 10% min. weight
uint constant NEUTRAL_CONFIDENCE = 5000; // fifty-fifty
uint public constant LAMBDA = 127; // same as liveness
mapping(address => uint8) public stablecoinToSide;
mapping(address => mapping(uint8 => Types.Position)) internal _positions;
/// @dev Parallel freeze counters — replace single-assertion bools.
/// Trading blocked when pendingAssertions > 0
/// Sells also blocked when pendingDisputes > 0
uint internal _depegSeverityBps;
uint public pendingAssertions;
uint public pendingDisputes;
uint public accumulatedFees;
uint constant WAD = 1e18;
/// @dev Σ(confidence × capital) per side
/// this round, accumulated during reveals
uint[12] internal _confCapAccum;
/// @dev Total revealed capital per side this round
uint[12] internal _revealedCapPerSide;
/// @dev Last round's average confidence
/// per side — used as Bayesian prior in calcRisk
uint[12] internal _lastRoundAvgConf;
mapping(address => mapping(uint8 => Types.PositionEntry[])) internal _entries;
event OrderPlaced(address indexed user, uint8 side, uint capital, uint tokens);
event PositionSold(address indexed user, uint8 side, uint tokens, uint returned);
event ConfidenceRevealed(address indexed user, uint8 side, uint confidence);
event PayoutPushed(address indexed user, uint8 side, uint amount);
event Recommitted(address indexed user, uint8 side, uint tokens);
event MarketCreated(uint8 numSides); event WeightsCalculated();
error OnlyUMA(); error InvalidSide(uint8 side, uint8 max);
error StalePosition(uint posRound, uint mktRound);
error OrderTooSmall(uint amount, uint minimum);
constructor(address _uma, address _quid) {
UMA = IUMA(_uma); QUID = Basket(_quid);
}
function createMarket(address[] calldata stables) external {
require(msg.sender == address(QUID)
|| msg.sender == address(UMA));
uint8 numSides = uint8(stables.length) + 1;
require(numSides <= 12, "too many sides");
require(_market.numSides == 0, "exists");
_market.numSides = numSides;
_market.startTime = block.timestamp;
_market.roundStartTime = block.timestamp;
_market.b = INITIAL_B; _market.roundNumber = 1;
for (uint8 i; i < stables.length; i++)
stablecoinToSide[stables[i]] = i + 1;
emit MarketCreated(numSides);
}
modifier onlyUMA() {
if (msg.sender != address(UMA))
revert OnlyUMA(); _; }
function trigger(uint timestamp,
uint8 winningSide, uint severityBps) external onlyUMA {
require(winningSide < _market.numSides, "invalid side");
_market.resolved = true; _market.winningSide = winningSide;
_market.resolutionTimestamp = timestamp;
_market.revealDeadline = block.timestamp + REVEAL_WINDOW;
_depegSeverityBps = severityBps;
if (pendingAssertions > 0) pendingAssertions--;
}
function onAssertionFiled() external onlyUMA {
pendingAssertions++;
}
function onDisputeStarted() external onlyUMA {
pendingDisputes++;
}
function onAssertionRejected() external onlyUMA {
if (pendingAssertions > 0) pendingAssertions--;
}
function onDisputeEnded() external onlyUMA {
if (pendingDisputes > 0) pendingDisputes--;
}
function paid() external view returns (bool) {
return _market.payoutsComplete;
}
/// @notice Resets market-level state for a new round.
/// Individual positions are NOT iterated. Stale positions
/// are gated by lastRound checks on every interaction.
function resetForNewRound() external onlyUMA {
for (uint8 side; side < _market.numSides; side++) {
uint revealedCapital = _revealedCapPerSide[side];
if (revealedCapital > 0)
_lastRoundAvgConf[side] = _confCapAccum[side] / revealedCapital;
else
_lastRoundAvgConf[side] = 0;
_confCapAccum[side] = 0;
_revealedCapPerSide[side] = 0;
}
_market.resolved = false;
_market.winningSide = 0;
_market.resolutionTimestamp = 0;
_market.totalCapital = 0;
_market.positionsTotal = 0;
_market.positionsRevealed = 0;
_market.positionsPaidOut = 0;
_market.positionsWeighed = 0;
_market.totalWinnerCapital = 0;
_market.totalLoserCapital = 0;
_market.totalWinnerWeight = 0;
_market.totalLoserWeight = 0;
_market.weightsComplete = false;
_market.payoutsComplete = false;
_market.revealDeadline = 0;
pendingAssertions = 0;
pendingDisputes = 0;
_depegSeverityBps = 0;
delete _market.q;
delete _market.capitalPerSide;
_market.roundStartTime = block.timestamp;
_market.roundNumber++;
}
function getDepegSeverityBps() external view returns (uint) {
return _depegSeverityBps;
}
function getMarketCapital()
external view returns (uint) {
return _market.totalCapital;
}
/// @dev Convert gas used to QD via ETH TWAP
function _gasToQD(uint gasUsed) internal
view returns (uint) { if (gasUsed == 0) return 0;
return FullMath.mulDiv(gasUsed * block.basefee,
QUID.AUX().getTWAP(0), WAD);
}
/// @dev Swap QD → ETH via Aux and forward to keeper.
/// Fallback: transfer QD directly if swap fails...
function _reimburseKeeper(uint qdAmount) internal {
if (qdAmount == 0) return;
uint ethBefore = address(this).balance;
try QUID.AUX().swap{value: 0}(address(QUID), true, qdAmount, 0) {
uint got = address(this).balance - ethBefore;
if (got > 0) {
(bool ok,) = msg.sender.call{value: got}("");
if (!ok) {} // swallowed — keeper missed ETH but protocol OK
}
} catch {
uint qdBal = QUID.balanceOf(address(this));
if (qdBal < qdAmount) qdAmount = qdBal;
if (qdAmount > 0) QUID.transfer(msg.sender, qdAmount);
}
} receive() external payable {}
function placeOrder(uint8 side, uint capital,
bool autoRollover, bytes32 commitHash, address delegate) external {
if (side >= _market.numSides) revert InvalidSide(side, _market.numSides);
if (capital < MIN_ORDER) revert OrderTooSmall(capital, MIN_ORDER);
require(commitHash != bytes32(0), "commit required");
require(!_market.resolved, "resolved");
require(pendingDisputes == 0, "dispute pending");
require(pendingAssertions == 0, "assertion pending");
QUID.transferFrom(msg.sender, address(this), capital);
uint fee = (capital * FEE_BPS) / 10000;
uint netCapital = capital - fee;
accumulatedFees += fee;
uint tokens = _buyTokens(_market, side, netCapital);
Types.Position storage position = _positions[msg.sender][side];
if (position.user == address(0)) {
position.user = msg.sender; position.side = side;
position.lastRound = _market.roundNumber;
_market.positionsTotal++;
} else if (position.lastRound < _market.roundNumber) {
uint staleCapital = position.totalCapital;
if (staleCapital > 0)
QUID.transfer(msg.sender, staleCapital);
position.totalCapital = 0;
position.totalTokens = 0;
position.lastRound = _market.roundNumber;
position.revealed = false;
position.revealedConfidence = 0;
position.weight = 0;
position.paidOut = false;
_market.positionsTotal++;
delete _entries[msg.sender][side];
}
position.delegate = delegate;
position.totalCapital += netCapital;
position.totalTokens += tokens;
position.autoRollover = autoRollover;
position.entryTimestamp = block.timestamp;
_entries[msg.sender][side].push(Types.PositionEntry({
capital: netCapital, tokens: tokens,
commitmentHash: commitHash,
timestamp: block.timestamp,
revealedConfidence: 0 }));
_market.totalCapital += netCapital;
_market.capitalPerSide[side] += netCapital;
emit OrderPlaced(msg.sender, side, netCapital, tokens);
}
/// @notice Sell tokens back to the LMSR curve.
/// returned = LMSR refund (can be > or < capitalReduced).
/// Any LMSR loss (capitalReduced > returned) goes to accumulatedFees
/// so no phantom QD accumulates in the contract.
function sellPosition(uint8 side,
uint tokensToSell) external {
require(!_market.resolved, "resolved");
require(pendingDisputes == 0, "dispute pending");
require(pendingAssertions == 0, "assertion pending");
Types.Position storage position = _positions[msg.sender][side];
require(position.totalTokens >= tokensToSell);
if (position.lastRound != _market.roundNumber)
revert StalePosition(position.lastRound, _market.roundNumber);
uint capitalReduced;
uint returned = _sellTokens(_market,
side, tokensToSell);
{
Types.PositionEntry[] storage entries = _entries[msg.sender][side];
uint length = entries.length;
uint[] memory oldCapitals = new uint[](length);
uint[] memory oldTokens = new uint[](length);
for (uint index; index < length; index++) {
oldCapitals[index] = entries[index].capital;
oldTokens[index] = entries[index].tokens;
}
uint[] memory newCapitals; uint[] memory newTokens;
(newCapitals, newTokens, capitalReduced) = UMA.reduceEntries(
oldCapitals, oldTokens, tokensToSell, position.totalTokens);
uint writeIndex;
for (uint index; index < length; index++) {
if (newTokens[index] == 0) continue;
if (writeIndex != index) entries[writeIndex] = entries[index];
entries[writeIndex].capital = newCapitals[index];
entries[writeIndex].tokens = newTokens[index];
writeIndex++;
}
while (entries.length > writeIndex) entries.pop();
}
position.totalTokens -= tokensToSell;
position.totalCapital -= capitalReduced;
_market.totalCapital -= capitalReduced;
_market.capitalPerSide[side] -= capitalReduced;
if (position.totalTokens == 0) _market.positionsTotal--;
// LMSR loss: user gets less than their entry cost removed.
// Difference stays in contract — route to fees so it's tracked.
if (capitalReduced > returned)
accumulatedFees += capitalReduced - returned;
// Solvency cap: never pay more than Hook actually holds
uint balance = QUID.balanceOf(address(this));
if (returned > balance) returned = balance;
QUID.transfer(msg.sender, returned);
emit PositionSold(msg.sender, side, tokensToSell, returned);
}
/// @notice Reveal + compute weight in one pass.
/// Stale autoRollover: enter LMSR, auto-reveal NEUTRAL.
/// Fresh commits: verify reveals from keeper (MongoDB).
/// Zero-hash entries: auto-reveal NEUTRAL.
/// Gas reimbursed from accumulatedFees.
/// @param reveals Flat array of {confidence, salt} for committed entries
/// @param revealCounts Per-user count — 0 for pure rollover / already revealed
function calculateWeights(address[] calldata users, uint8[] calldata sides,
RevealEntry[] calldata reveals, uint[] calldata revealCounts)
external { uint gasStart = gasleft();
require(_market.resolved);
require(users.length == sides.length, "length mismatch");
require(users.length == revealCounts.length, "length mismatch");
require(_market.positionsPaidOut == 0, "payouts started");
require(block.timestamp >= _market.revealDeadline, "reveal window open");
uint revealCursor;
for (uint index; index < users.length; index++) {
address user = users[index]; uint8 side = sides[index];
Types.Position storage position = _positions[user][side];
if (position.weight > 0 || position.paidOut) {
revealCursor += revealCounts[index]; continue; }
// ── Stale autoRollover: create entry for weight calculation ──
// Fee already deducted in pushPayouts when capital was retained.
// No _buyTokens: computeWeight uses capitals/timestamps, not tokens.
// _market.q is deleted on resetForNewRound anyway.
if (position.lastRound < _market.roundNumber) {
if (!position.autoRollover || position.totalCapital == 0) {
revealCursor += revealCounts[index]; continue; }
uint capital = position.totalCapital;
position.totalTokens = 0;
position.lastRound = _market.roundNumber;
position.revealed = false;
position.revealedConfidence = 0;
position.weight = 0; position.paidOut = false;
position.entryTimestamp = _market.roundStartTime;
_market.totalCapital += capital;
_market.capitalPerSide[side] += capital;
_market.positionsTotal++;
delete _entries[user][side];
_entries[user][side].push(Types.PositionEntry({
capital: capital, tokens: 0,
commitmentHash: bytes32(0),
timestamp: _market.roundStartTime,
revealedConfidence: 0 }));
emit Recommitted(user, side, capital);
}
if (position.lastRound != _market.roundNumber) {
revealCursor += revealCounts[index]; continue; }
if (!position.revealed) {
uint count = revealCounts[index];
uint start = revealCursor; revealCursor += count;
_revealPosition(user, side, reveals, start, count);
} else { revealCursor += revealCounts[index]; }
bool isWinner = side == _market.winningSide; uint weight;
{ Types.PositionEntry[] storage entries = _entries[user][side];
uint length = entries.length;
uint[] memory capitals = new uint[](length);
uint[] memory timestamps = new uint[](length);
for (uint entry; entry < length; entry++) {
capitals[entry] = entries[entry].capital;
timestamps[entry] = entries[entry].timestamp;
}
weight = UMA.computeWeight(capitals, timestamps,
_market.roundStartTime, _market.resolutionTimestamp,
LAMBDA, DECAY_FLOOR, position.revealedConfidence, isWinner);
}
if (weight == 0) {
position.paidOut = true;
_market.positionsPaidOut++;
_market.positionsWeighed++;
emit PayoutPushed(user, side, 0);
continue;
}
position.weight = weight;
if (isWinner) _market.totalWinnerWeight += weight;
else _market.totalLoserWeight += weight;
_market.positionsWeighed++;
}
if (_market.positionsWeighed >= _market.positionsRevealed)
_market.weightsComplete = true;
// Reimburse keeper from protocol fees
uint gasCost = _gasToQD(gasStart - gasleft() + 21000);
uint qdBal = QUID.balanceOf(address(this));
if (gasCost > qdBal) gasCost = qdBal;
if (gasCost > 0 && gasCost <= accumulatedFees) {
accumulatedFees -= gasCost;
_reimburseKeeper(gasCost);
}
emit WeightsCalculated();
}
/// @notice Push payouts in one pass. Rollover positions retain
/// capital (minus fee) for next round's LMSR entry.
/// Gas reimbursed from accumulatedFees.
function pushPayouts(address[] calldata users,
uint8[] calldata sides) external {
uint gasStart = gasleft();
require(_market.weightsComplete);
require(_market.positionsWeighed == _market.positionsRevealed,
"weights not finalized");
uint winnerWeight = _market.totalWinnerWeight;
uint loserWeight = _market.totalLoserWeight;
uint loserCapital = _market.totalLoserCapital;
uint8 winner = _market.winningSide;
uint roundNum = _market.roundNumber;
for (uint index; index < users.length; index++) {
address user = users[index]; uint8 side = sides[index];
Types.Position storage position = _positions[user][side];
if (position.paidOut) continue;
if (position.lastRound != roundNum) continue;
if (position.weight == 0) continue;
position.paidOut = true;
_market.positionsPaidOut++;
uint payout = UMA.computePayout(
position.totalCapital, position.weight,
winnerWeight, loserWeight,
loserCapital, CONSOLATION_BPS,
side == winner);
uint balance = QUID.balanceOf(address(this));
if (payout > balance) payout = balance;
if (position.autoRollover) {
// Retain for next round, deduct rollover fee on capital.
// Fee is on original stake, not on winnings.
// LMSR entry deferred to calculateWeights.
uint fee = (position.totalCapital * ROLLOVER_FEE_BPS) / 10000;
if (fee >= payout) fee = 0; // don't eat entire payout
accumulatedFees += fee;
position.totalCapital = payout - fee;
position.totalTokens = 0;
position.revealed = false;
position.revealedConfidence = 0;
position.weight = 0;
position.paidOut = false;
delete _entries[user][side];
} else {
QUID.transfer(user, payout);
}
emit PayoutPushed(user, side, payout);
}
if (_market.positionsPaidOut >= _market.positionsRevealed)
_market.payoutsComplete = true;
// Reimburse keeper from protocol fees
uint gasCost = _gasToQD(gasStart - gasleft() + 21000);
uint qdBal = QUID.balanceOf(address(this));
if (gasCost > qdBal) gasCost = qdBal;
if (gasCost > 0 && gasCost <= accumulatedFees) {
accumulatedFees -= gasCost;
_reimburseKeeper(gasCost);
}
}
function settleAssertion() external {
UMA.settleAssertion();
}
function depegPending() external view returns (bool) {
return _market.resolved && _market.winningSide > 0;
}
/// @notice Burn accumulated prediction market fees.
function burnAccumulatedFees() external {
require(!_market.resolved
|| _market.payoutsComplete);
uint fees = accumulatedFees;
require(fees > 0); accumulatedFees = 0;
QUID.turn(address(this), fees);
}
function getLMSRPrice(uint8 side)
external view returns (uint) {
return FeeLib.price(_market.q,
_market.numSides, _market.b, side);
}
function getLMSRCost(uint8 side, int128 delta)
external view returns (uint) {
return FeeLib.cost(_market.q, _market.numSides,
_market.b, side, delta);
}
/// @dev Reveal entries for a position. Extracted to avoid stack-too-deep.
function _revealPosition(address user, uint8 side,
RevealEntry[] calldata reveals, uint start, uint count) internal {
Types.Position storage position = _positions[user][side];
Types.PositionEntry[] storage entries = _entries[user][side];
uint cursor; uint weightedConfidenceSum;
for (uint entry; entry < entries.length; entry++) {
if (entries[entry].commitmentHash == bytes32(0)) {
entries[entry].revealedConfidence = NEUTRAL_CONFIDENCE;
weightedConfidenceSum += entries[entry].capital * NEUTRAL_CONFIDENCE;
} else {
require(cursor < count, "not enough reveals");
RevealEntry calldata reveal = reveals[start + cursor];
require(reveal.confidence >= 100 && reveal.confidence <= 10000
&& reveal.confidence % 100 == 0, "bad confidence");
require(keccak256(abi.encodePacked(reveal.confidence, reveal.salt))
== entries[entry].commitmentHash, "hash mismatch");
entries[entry].revealedConfidence = reveal.confidence;
weightedConfidenceSum += entries[entry].capital * reveal.confidence;
cursor++;
}
} require(cursor == count, "extra reveals");
uint averageConfidence = position.totalCapital > 0
? weightedConfidenceSum / position.totalCapital
: NEUTRAL_CONFIDENCE;
position.revealed = true;
position.revealedConfidence = averageConfidence;
_market.positionsRevealed++;
_confCapAccum[side] += position.totalCapital * averageConfidence;
_revealedCapPerSide[side] += position.totalCapital;
if (side == _market.winningSide)
_market.totalWinnerCapital += position.totalCapital;
else
_market.totalLoserCapital += position.totalCapital;
emit ConfidenceRevealed(user, side, averageConfidence);
}
function _buyTokens(Types.Market storage market,
uint8 side, uint netCapital) internal
returns (uint tokens) { int128 deltaQ;
(tokens, deltaQ) = UMA.buyTokens(market.q,
market.numSides, market.b, side, netCapital);
market.q[side] += deltaQ;
}
function _sellTokens(Types.Market storage market, uint8 side, uint tokensToSell)
internal returns (uint returned) { int128 deltaQ;
(returned, deltaQ) = UMA.sellTokens(market.q,
market.numSides, market.b, side, tokensToSell);
market.q[side] -= deltaQ;
}
function getMarket() external
view returns (Types.Market memory) { return _market; }
function getPosition(address user, uint8 side) external
view returns (Types.Position memory) {
return _positions[user][side];
}
function getPositionEntries(address user, uint8 side) external
view returns (Types.PositionEntry[] memory) {
return _entries[user][side];
}
function getDepegStats(address stablecoin) external
view returns (Types.DepegStats memory stats) {
uint8 side = stablecoinToSide[stablecoin];
if (side == 0) return stats;
if (_market.numSides == 0) return stats;
stats.capOnSide = _market.capitalPerSide[side];
stats.capNone = _market.capitalPerSide[0];
stats.capTotal = _market.totalCapital;
stats.depegged = _market.resolved && _market.winningSide == side;
stats.avgConf = _lastRoundAvgConf[side]; stats.side = side;
}
function getAllPrices() external view
returns (uint[] memory prices) {
prices = new uint[](_market.numSides);
for (uint8 i; i < _market.numSides; i++)
prices[i] = FeeLib.price(_market.q,
_market.numSides, _market.b, i);
}
function getCapitalPerSide() external
view returns (uint[12] memory) {
return _market.capitalPerSide;
}
function getRoundStartTime() external
view returns (uint) {
return _market.roundStartTime;
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {Basket} from "./Basket.sol";
import {RandaoLib} from "./imports/RandaoLib.sol";
import {MessageCodec} from "./imports/MessageCodec.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {ReentrancyGuard} from "solmate/src/utils/ReentrancyGuard.sol";
interface ICourt {
function getMarketConfig(uint64 marketId) external view returns (
uint8 numSides, uint8 numWinners, bool requiresUnanimous, uint appealCost);
function getRoundStartTime(uint64 marketId) external view returns (uint);
function getCurrentRound(uint64 marketId) external view returns (uint8);
function getVerdictTimestamp(uint64 marketId) external view returns (uint);
function getFinalVerdict(uint64 marketId) external view returns (uint8[] memory);
function owner() external view returns (address);
}
contract Jury is Ownable, ReentrancyGuard {
address public court;
address public immutable basket;
uint constant REVEAL_SIZE = 12;
uint constant FULL_JURY = 21;
uint constant COMMIT_PERIOD = 4 days;
uint constant REVEAL_WINDOW = 12 hours;
uint constant APPEAL_WINDOW = 7 days;
uint constant BASE_COMP_TIMEOUT = 7 days;
struct Round {
uint8 numSides;
uint8 numWinners;
bool requiresUnanimous;
address appellant;
address[] jurors;
uint[] revealedIndices;
bool finalized;
uint8[] verdict;
bool unanimous;
bool meetsThreshold;
}
struct Compensation {
uint baseFromSolana;
uint slashedOnEth;
bool baseReceived;
bool distributed;
}
mapping(uint64 => mapping(uint8 => Round)) public rounds;
mapping(uint64 => mapping(address => bool)) public hasServed;
mapping(uint64 => mapping(address => uint)) public lockedStake;
mapping(uint64 => mapping(uint8 => mapping(address => bool))) public revealed;
mapping(uint64 => mapping(uint8 => mapping(address => uint8[]))) public votes;
mapping(uint64 => mapping(uint8 => mapping(address => bytes32))) public commits;
mapping(uint64 => mapping(uint8 => mapping(address => address))) public delegates;
mapping(uint64 => Compensation) public compensation;
event JuryFulfilled(uint64 indexed marketId, uint8 round);
event InsufficientStakers(uint64 indexed marketId, uint8 round, uint current, uint needed);
event VoteCommitted(uint64 indexed marketId, uint8 round, address juror);
event VoteRevealed(uint64 indexed marketId, uint8 round, address juror);
event RoundFinalized(uint64 indexed marketId, uint8 round);
event JurorSlashed(address juror, uint amount);
event JurorCompensated(address juror, uint amount);
event JuryCompensated(uint64 indexed marketId, uint total);
error OnlyCourt();
error OnlyBasket();
error AlreadyCommitted();
error AlreadyFulfilled();
error AlreadyFinalized();
error DoubleSpend();
modifier onlyCourt() {
if (court == address(0) || msg.sender != court) revert OnlyCourt();
_;
}
modifier onlyBasket() {
if (msg.sender != basket) revert OnlyBasket();
_;
}
constructor(address _basket) Ownable(msg.sender) {
basket = _basket;
}
function setup(address _court) external onlyOwner {
court = _court; renounceOwnership();
}
function receiveJuryFunds(uint64 marketId, uint amount)
external nonReentrant onlyBasket {
Compensation storage comp = compensation[marketId];
(uint8 numSides,,,) = ICourt(court).getMarketConfig(marketId);
uint verdictTs = ICourt(court).getVerdictTimestamp(marketId);
// Late compensation: no active resolution AND no pending verdict → treasury
if (numSides == 0 && verdictTs == 0) {
Basket(basket).transfer(ICourt(court).owner(), amount);
return;
}
if (comp.distributed) {
Basket(basket).transfer(ICourt(court).owner(), amount);
return;
}
if (comp.baseReceived) revert DoubleSpend();
comp.baseFromSolana = amount;
comp.baseReceived = true;
_tryDistribute(marketId);
}
function addSlashedEth(uint64 marketId, uint amount) external onlyCourt {
compensation[marketId].slashedOnEth += amount;
}
function refundAppellant(uint64 marketId, uint amount, address appellant) external onlyCourt {
compensation[marketId].slashedOnEth -= amount;
Basket(basket).transfer(appellant, amount);
}
function markCompensationTimedOut(uint64 marketId) external onlyCourt {
Compensation storage comp = compensation[marketId];
comp.baseFromSolana = 0; comp.baseReceived = true;
}
function tryDistribute(uint64 marketId) external nonReentrant {
_tryDistribute(marketId);
}
function _tryDistribute(uint64 marketId) internal {
Compensation storage comp = compensation[marketId];
uint verdictTs = ICourt(court).getVerdictTimestamp(marketId);
if (verdictTs == 0 || block.timestamp <= verdictTs + APPEAL_WINDOW ||
!comp.baseReceived || comp.distributed) return;
comp.distributed = true;
uint ethSlashed = comp.slashedOnEth;
uint8[] memory finalVerdict = ICourt(court).getFinalVerdict(marketId);
uint8 finalRound = ICourt(court).getCurrentRound(marketId);
uint distributed = _distributeCompensation(marketId, finalRound,
finalVerdict, comp.baseFromSolana, ethSlashed);
emit JuryCompensated(marketId, distributed);
comp.baseFromSolana = 0;
comp.slashedOnEth = 0;
}
function _distributeCompensation(uint64 marketId, uint8 finalRound,
uint8[] memory finalVerdict, uint baseComp, uint ethSlashed)
internal returns (uint distributed) { uint correctCount = 0;
address[] memory correctJurors = new address[]((finalRound + 1) * FULL_JURY);
uint pool = baseComp + ethSlashed;
for (uint8 r = 0; r <= finalRound; r++) {
(uint slashed, uint correct) = _processRoundJurors(
marketId, r, finalVerdict, correctJurors, correctCount);
pool += slashed;
correctCount = correct;
}
if (correctCount > 0) {
uint perJuror = pool / correctCount;
for (uint i = 0; i < correctCount; i++) {
Basket(basket).transfer(correctJurors[i], perJuror);
distributed += perJuror;
}
} else {
Basket(basket).transfer(owner(), pool);
distributed = pool;
} return distributed;
}
function voirDire(uint64 marketId, uint8 round,
bytes[] calldata headers) external onlyCourt returns (bool) {
Round storage r = rounds[marketId][round];
// Round 0 with existing data: clear ALL old rounds for re-resolution
if (round == 0 && (r.finalized || r.jurors.length > 0)) {
for (uint8 oldRound = 0; oldRound < 10; oldRound++) {
Round storage oldR = rounds[marketId][oldRound];
if (oldR.jurors.length == 0) continue;
for (uint i = 0; i < oldR.jurors.length; i++) {
address juror = oldR.jurors[i];
uint stake = lockedStake[marketId][juror];
if (stake > 0) {
Basket(basket).unlockFromJury(juror, stake);
lockedStake[marketId][juror] = 0;
}
hasServed[marketId][juror] = false;
delete commits[marketId][oldRound][juror];
delete revealed[marketId][oldRound][juror];
delete votes[marketId][oldRound][juror];
delete delegates[marketId][oldRound][juror];
}
delete rounds[marketId][oldRound];
}
} else if (r.finalized || r.jurors.length > 0) {
// RETRY within same resolution: clear only this round
for (uint i = 0; i < r.jurors.length; i++) {
address juror = r.jurors[i];
uint stake = lockedStake[marketId][juror];
if (stake > 0) {
Basket(basket).unlockFromJury(juror, stake);
lockedStake[marketId][juror] = 0;
}
hasServed[marketId][juror] = false;
delete commits[marketId][round][juror];
delete revealed[marketId][round][juror];
delete votes[marketId][round][juror];
delete delegates[marketId][round][juror];
}
delete rounds[marketId][round];
}
r = rounds[marketId][round];
if (r.numSides == 0) {
(r.numSides, r.numWinners,
r.requiresUnanimous,) = _getMarketConfig(marketId);
}
require(ICourt(court).getRoundStartTime(marketId) > 0, "No active resolution");
require(round == ICourt(court).getCurrentRound(marketId), "Wrong round");
require(headers.length <= 10);
require(headers.length >= 3, "need 3 headers");
bytes32 seed = RandaoLib.getHistoricalRandaoValue(block.number - 1, headers[0]);
seed = keccak256(abi.encodePacked(seed,
RandaoLib.getHistoricalRandaoValue(block.number - 2, headers[1]),
RandaoLib.getHistoricalRandaoValue(block.number - 3, headers[2])));
for (uint i = 3; i < headers.length; i++) {
seed = keccak256(abi.encodePacked(seed,
RandaoLib.getHistoricalRandaoValue(block.number - (i + 1), headers[i])));
}
uint poolSize = Basket(basket).juryPoolSize();
if (poolSize < FULL_JURY) {
emit InsufficientStakers(marketId,
round, poolSize, FULL_JURY); return false;
}
uint selected = 0;
uint maxAttempts = poolSize * 3;
if (maxAttempts > 100) maxAttempts = 100;
if (maxAttempts < FULL_JURY * 2) maxAttempts = FULL_JURY * 2;
for (uint i = 0; i < maxAttempts && selected < FULL_JURY; i++) {
seed = keccak256(abi.encodePacked(seed, i));
uint idx = uint(seed) % Basket(basket).juryPoolSize();
address candidate = Basket(basket).juryPoolMember(idx);
if (_selectAndLockJuror(marketId, candidate, r)) selected++;
}
if (selected != FULL_JURY) return false;
return true;
}
function commitVote(uint64 marketId, uint8 round,
bytes32 commitment, address delegate) external {
Round storage r = rounds[marketId][round];
require(!r.finalized, "finalized");
require(r.jurors.length > 0, "inactive");
uint roundStart = ICourt(court).getRoundStartTime(marketId);
require(roundStart > 0, "round not started");
require(block.timestamp <= roundStart + COMMIT_PERIOD, "commit period ended");
bool found = false;
for (uint i = 0; i < r.jurors.length; i++) {
if (r.jurors[i] == msg.sender) { found = true; break; }
}
require(found, "not juror");
if (commits[marketId][round][msg.sender] != bytes32(0)) revert AlreadyCommitted();
if (delegate != address(0)) delegates[marketId][round][msg.sender] = delegate;
commits[marketId][round][msg.sender] = commitment;
emit VoteCommitted(marketId, round, msg.sender);
}
function revealVote(uint64 marketId, uint8 round,
uint8[] calldata sides, bytes32 salt, address juror) external {
Round storage r = rounds[marketId][round];
require(!r.finalized, "finalized");
require(msg.sender == delegates[marketId][round][juror] || msg.sender == juror, "403");
uint roundStart = ICourt(court).getRoundStartTime(marketId);
require(roundStart > 0, "round not started");
require(block.timestamp >= roundStart + COMMIT_PERIOD, "early");
require(block.timestamp <= roundStart + COMMIT_PERIOD + REVEAL_WINDOW, "late");
require(!revealed[marketId][round][juror], "revealed");
require(commits[marketId][round][juror] == keccak256(abi.encode(sides, salt)), "invalid");
revealed[marketId][round][juror] = true;
votes[marketId][round][juror] = sides;
emit VoteRevealed(marketId, round, juror);
}
function finalizeRound(uint64 marketId, uint8 round,
uint roundStart, bytes[] calldata headers) external onlyCourt {
Round storage r = rounds[marketId][round];
if (r.finalized) revert AlreadyFinalized();
require(roundStart == ICourt(court).getRoundStartTime(marketId), "roundStart mismatch");
if (r.revealedIndices.length == 0) {
require(block.timestamp > roundStart + COMMIT_PERIOD, "commit active");
uint commitCount = 0;
for (uint i = 0; i < r.jurors.length; i++) {
if (commits[marketId][round][r.jurors[i]] != bytes32(0)) commitCount++;
}
require(commitCount >= REVEAL_SIZE, "commits");
require(headers.length >= 2, "headers");
bytes32 seed = RandaoLib.getHistoricalRandaoValue(block.number - 1, headers[0]);
seed = keccak256(abi.encodePacked(seed, RandaoLib.getHistoricalRandaoValue(
block.number - 2, headers[1])));
for (uint i = 2; i < headers.length; i++) {
seed = keccak256(abi.encodePacked(seed,
RandaoLib.getHistoricalRandaoValue(
block.number - (i + 1), headers[i])));
}
address[] memory committed = new address[](commitCount);
uint idx = 0;
for (uint i = 0; i < r.jurors.length; i++) {
if (commits[marketId][round][r.jurors[i]] != bytes32(0)) {
committed[idx++] = r.jurors[i];
}
} bool[] memory selected = new bool[](commitCount);
for (uint i = 0; i < REVEAL_SIZE; i++) {
uint index;
do {
seed = keccak256(abi.encodePacked(seed, i));
index = uint(seed) % commitCount;
} while (selected[index]);
selected[index] = true;
for (uint j = 0; j < r.jurors.length; j++) {
if (r.jurors[j] == committed[index]) {
r.revealedIndices.push(j);
break;
}
}
}
} uint revealCount = 0;
for (uint i = 0; i < r.revealedIndices.length; i++) {
address juror = r.jurors[r.revealedIndices[i]];
if (revealed[marketId][round][juror]) revealCount++;
}
if (r.numWinners > 1) {
(r.verdict,
r.unanimous,
r.meetsThreshold) = _getMultiWinner(marketId, round, r);
} else {
uint8[] memory voteCounts = new uint8[](r.numSides);
for (uint i = 0; i < r.revealedIndices.length; i++) {
address juror = r.jurors[r.revealedIndices[i]];
if (revealed[marketId][round][juror]) {
uint8[] memory jurorVotes = votes[marketId][round][juror];
if (jurorVotes.length > 0 && jurorVotes[0] < r.numSides) {
voteCounts[jurorVotes[0]]++;
}
}
} uint8 maxVotes = 0;
uint8 winningSide = 0;
for (uint8 side = 0; side < r.numSides; side++) {
if (voteCounts[side] > maxVotes) {
maxVotes = voteCounts[side];
winningSide = side;
}
}
r.unanimous = (revealCount > 0 && maxVotes == revealCount);
r.meetsThreshold = (revealCount > 0 && maxVotes * 3 >= revealCount * 2);
r.verdict = new uint8[](1);
r.verdict[0] = winningSide;
} r.finalized = true;
emit RoundFinalized(marketId, round);
}
function getStoredVerdict(uint64 marketId, uint8 round)
external view returns (uint8[] memory verdict,
bool unanimous, bool meetsThreshold) {
Round storage r = rounds[marketId][round];
return (r.verdict, r.unanimous, r.meetsThreshold);
}
function _selectAndLockJuror(uint64 marketId,
address candidate, Round storage r) internal returns (bool) {
if (hasServed[marketId][candidate]) return false;
uint balance = Basket(basket).balanceOf(candidate);
if (balance > 500e18) return false;
uint stake = (balance * 2000) / 10000;
r.jurors.push(candidate);
hasServed[marketId][candidate] = true;
lockedStake[marketId][candidate] = stake;
Basket(basket).lockForJury(candidate, stake);
return true;
}
function _getMultiWinner(uint64 marketId, uint8 round, Round storage r)
internal view returns (uint8[] memory, bool, bool) {
uint8[][] memory positionVotes = new uint8[][](r.numWinners);
for (uint8 pos = 0; pos < r.numWinners; pos++) {
positionVotes[pos] = new uint8[](r.numSides);
}
uint revealCount = 0;
for (uint i = 0; i < r.revealedIndices.length; i++) {
address juror = r.jurors[r.revealedIndices[i]];
if (revealed[marketId][round][juror]) revealCount++;
}
for (uint i = 0; i < revealCount; i++) {
address juror = r.jurors[r.revealedIndices[i]];
if (revealed[marketId][round][juror]) {
uint8[] memory ranking = votes[marketId][round][juror];
for (uint8 pos = 0; pos < r.numWinners && pos < ranking.length; pos++) {
if (ranking[pos] < r.numSides) {
positionVotes[pos][ranking[pos]]++;
}
}
}
} uint8[] memory winners = new uint8[](r.numWinners);
uint8[] memory winnerVotes = new uint8[](r.numWinners);
for (uint8 pos = 0; pos < r.numWinners; pos++) {
uint8 maxVotes = 0;
for (uint8 side = 0; side < r.numSides; side++) {
if (positionVotes[pos][side] > maxVotes ||
(positionVotes[pos][side] == maxVotes &&
uint(keccak256(abi.encodePacked(
blockhash(block.number - 1),
pos, side))) % 2 == 0)) {
maxVotes = positionVotes[pos][side];
winners[pos] = side;
}
} winnerVotes[pos] = maxVotes;
}
if (revealCount == 0) {
return (winners, false, false);
}
bool meetsThreshold = true;
uint threshold = (revealCount * 2) / 3;
for (uint8 pos = 0; pos < r.numWinners; pos++) {
if (winnerVotes[pos] < threshold) {
meetsThreshold = false;
break;
}
}
bool unanimous = true;
for (uint8 pos = 0; pos < r.numWinners; pos++) {
if (winnerVotes[pos] != revealCount) {
unanimous = false;
break;
}
} return (winners, unanimous, meetsThreshold);
}
function getCorrectJurors(uint64 marketId,
uint8 round) external view returns (address[] memory) {
Round storage r = rounds[marketId][round];
uint8[] memory finalVerdict = r.verdict;
uint correct = 0;
for (uint i = 0; i < r.revealedIndices.length; i++) {
address juror = r.jurors[r.revealedIndices[i]];
if (revealed[marketId][round][juror] &&
_verdictMatches(votes[marketId][round][juror], finalVerdict)) {
correct++;
}
} address[] memory correctJurors = new address[](correct);
uint idx = 0;
for (uint i = 0; i < r.revealedIndices.length; i++) {
address juror = r.jurors[r.revealedIndices[i]];
if (revealed[marketId][round][juror] &&
_verdictMatches(votes[marketId][round][juror], finalVerdict)) {
correctJurors[idx++] = juror;
}
} return correctJurors;
}
function isJuror(uint64 marketId, uint8 round,
address addr) external view returns (bool) {
Round storage r = rounds[marketId][round];
for (uint i = 0; i < r.jurors.length; i++) {
if (r.jurors[i] == addr) return true;
}
return false;
}
function _processRoundJurors(uint64 marketId,
uint8 r, uint8[] memory finalVerdict,
address[] memory correctJurors, uint correctCount)
internal returns (uint slashed, uint newCorrectCount) {
Round storage round = rounds[marketId][r];
newCorrectCount = correctCount;
for (uint i = 0; i < round.revealedIndices.length; i++) {
uint jurorIndex = round.revealedIndices[i];
address juror = round.jurors[jurorIndex];
uint stake = lockedStake[marketId][juror];
if (stake == 0) continue;
if (!revealed[marketId][r][juror]) {
Basket(basket).unlockFromJury(juror, stake);
Basket(basket).turn(juror, stake);
slashed += stake;
emit JurorSlashed(juror, stake);
} else if (_verdictMatches(votes[marketId][r][juror], finalVerdict)) {
Basket(basket).unlockFromJury(juror, stake);
correctJurors[newCorrectCount++] = juror;
} else {
Basket(basket).unlockFromJury(juror, stake);
}
lockedStake[marketId][juror] = 0;
}
for (uint i = 0; i < round.jurors.length; i++) {
address juror = round.jurors[i];
uint stake = lockedStake[marketId][juror];
if (stake > 0) {
Basket(basket).unlockFromJury(juror, stake);
lockedStake[marketId][juror] = 0;
}
}
}
function _verdictMatches(uint8[] memory a,
uint8[] memory b) internal pure returns (bool) {
if (a.length != b.length) return false;
for (uint i = 0; i < a.length; i++) {
if (a[i] != b[i]) return false;
}
return true;
}
function setAppellant(uint64 marketId,
uint8 round, address appellant) external onlyCourt {
rounds[marketId][round].appellant = appellant;
}
function getAppellant(uint64 marketId,
uint8 round) external view returns (address) {
return rounds[marketId][round].appellant;
}
function getJurors(uint64 marketId,
uint8 round) external view
returns (address[] memory) {
return rounds[marketId][round].jurors;
}
function _getMarketConfig(uint64 marketId) internal
view returns (uint8 numSides, uint8 numWinners,
bool requiresUnanimous, uint appealCost) {
return ICourt(court).getMarketConfig(marketId);
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {Jury} from "./Jury.sol";
import {Basket} from "./Basket.sol";
import {MessageCodec} from "./imports/MessageCodec.sol";
import {ReentrancyGuard} from "solmate/src/utils/ReentrancyGuard.sol";
/// @dev UMA callback — delivers jury ruling for DVM/CRE conflicts.
interface IUMA {
function receiveRuling(uint8 winningSide, uint16 severityBps) external;
function getEvidence(bytes32 assertionId) external view
returns (uint8, uint8, uint maxDeviationBps, uint8, bytes32, uint);
}
contract Court is ReentrancyGuard {
Jury public immutable jury;
Basket public immutable QUID;
/// @dev UMA oracle — authorized to request arbitration.
address public umaOracle;
/// @dev Markets originated from UMA conflict detection.
/// Ruling routed to UMA.receiveRuling instead of Basket.sendToSolana.
mapping(uint64 => bool) public isArbitration;
/// @dev Auto-incrementing counter for UMA arbitration IDs.
/// Starts at 2^63 to avoid collision with Solana marketIds (small sequential).
uint64 public arbitrationCounter = 2**63;
uint constant APPEAL_WINDOW = 7 days;
uint constant MAX_APPEALS = 3;
uint constant MAX_HUNG_JURIES = 3;
uint constant MAX_TOTAL_ROUNDS = 10;
uint constant COMMIT_PERIOD = 4 days;
uint constant REVEAL_WINDOW = 12 hours;
uint constant FINALIZE_BLOCK_WINDOW = 50;
/// @dev Default appeal cost for UMA arbitrations (100 QD).
uint constant ARBITRATION_APPEAL_COST = 100e18;
enum AppealGround {
HUNG_JURY, INCORRECT_VERDICT,
NEW_EVIDENCE, FABRICATION,
BIAS, EXCLUSIONARY
}
struct Appeal {
address appellant;
AppealGround ground;
string reasoning;
uint timestamp;
bool sustained;
uint cost;
}
struct Resolution {
uint8 numSides;
uint8 numWinners;
bool requiresUnanimous;
uint8 currentRound;
uint8 hungJuryCount;
uint8 appealCount;
uint appealCost;
uint8[] verdict;
bytes32 resolutionRequester;
}
mapping(uint64 => bytes) public rulingData;
mapping(uint64 => Resolution) public resolutions;
mapping(uint64 => uint) public roundStartTime;
mapping(uint64 => uint) public verdictTimestamp;
mapping(uint64 => uint) public finalizeEligibleBlock;
mapping(uint64 => mapping(uint8 => Appeal)) public appeals;
/// @dev assertionId associated with each UMA arbitration marketId.
mapping(uint64 => bytes32) public arbitrationAssertionId;
event VerdictReached(uint64 indexed marketId, uint8 round, uint8[] verdict);
event HungJury(uint64 indexed marketId, uint8 round, uint8 count);
event JurySelectionFailed(uint64 indexed marketId, uint8 round);
event JurySelectionComplete(uint64 indexed marketId, uint8 round);
event AppealFiled(uint64 indexed marketId, uint8 round, address appellant,
AppealGround ground, uint8 appealCount);
event MarketDataEvicted(uint64 indexed marketId, uint gasRefund);
event AppealResult(uint64 indexed marketId, uint8 round, bool sustained);
event ResolutionFinalized(uint64 indexed marketId, uint8[] verdict);
event ForceMajeure(uint64 indexed marketId);
/// @notice Emitted when UMA requests arbitration for a DVM/CRE conflict.
event ArbitrationReceived(uint64 indexed marketId, bytes32 assertionId,
uint8 claimedSide, uint8 recommendedSide);
/// @notice Emitted when a Solana market requests Court resolution.
event ResolutionReceived(uint64 indexed marketId, uint8 numSides);
error ResolutionActive();
error NoVerdictToAppeal();
error AlreadyFinalized();
error MaxRoundsExceeded();
error MaxAppealsReached();
error Unauthorized();
error OutsideFinalizeWindow();
error AppealWindowActive();
error NoVerdictToExecute();
error AlreadyExecuted();
error ZeroAddress();
error WrongRound();
error RoundNotStarted();
error JuryNotSelected();
error JuryAlreadySelected();
error NoVerdictStored();
error AppealLate();
error TooEarly();
error NotUMA();
constructor(address _basket,
address _jury, address _uma) {
umaOracle = _uma;
jury = Jury(_jury);
QUID = Basket(_basket);
}
receive() external payable {}
function receiveResolutionRequest(bytes calldata lzMessage) external {
if (msg.sender != address(QUID)) revert Unauthorized();
MessageCodec.ResolutionRequestData memory req = MessageCodec.decodeResolutionRequest(lzMessage);
Resolution storage res = resolutions[req.marketId];
if (res.numSides != 0) revert ResolutionActive();
res.requiresUnanimous = req.requiresUnanimous;
res.numWinners = req.numWinners;
res.resolutionRequester = req.requester;
res.appealCost = req.appealCost;
res.numSides = req.numSides;
roundStartTime[req.marketId] = block.timestamp;
emit ResolutionReceived(req.marketId, req.numSides);
}
// ═══════════════════════════════════════════════════════════════
// UMA arbitration entry point
//
// Called by UMA.assertionResolvedCallback when DVM and CRE
// disagree. Creates a Resolution for the jury to decide
// which oracle malfunctioned. Single-winner, 2/3 majority.
//
// No affidavits (evidence is on-chain as ForensicEvidence).
// Ruling delivered directly to UMA.receiveRuling().
// Court's MAX_APPEALS guarantees termination.
//
// Jury compensation: no Solana funds expected. After appeal
// window + timeout, jurors are compensated from slashed appeal
// costs only. If no appeals, jurors serve for stake return
// (no bonus). Acceptable for a rare conflict edge case.
// ═══════════════════════════════════════════════════════════════
/// @notice Called by UMA when DVM/CRE conflict detected.
/// @param assertionId For jurors to look up ForensicEvidence on UMA
/// @param numSides From UMA's market (stables + 1)
/// @param claimedSide What the DVM confirmed
/// @param recommendedSide What CRE's evidence recommends
/// @param confidence CRE's confidence score (0-100)
function requestArbitration(bytes32 assertionId, uint8 numSides,
uint8 claimedSide, uint8 recommendedSide, uint8 confidence) external {
if (msg.sender != umaOracle) revert NotUMA();
uint64 marketId = arbitrationCounter++;
Resolution storage res = resolutions[marketId];
res.numSides = numSides; res.numWinners = 1;
res.requiresUnanimous = false;
res.appealCost = ARBITRATION_APPEAL_COST;
res.resolutionRequester = bytes32(uint256(uint160(umaOracle)));
isArbitration[marketId] = true;
arbitrationAssertionId[marketId] = assertionId;
roundStartTime[marketId] = block.timestamp;
emit ArbitrationReceived(
marketId, assertionId, claimedSide, recommendedSide);
}
function progressToJurySelection(uint64 marketId, uint8 round,
bytes[] calldata headers) external nonReentrant {
Resolution storage res = resolutions[marketId];
if (res.currentRound != round) revert WrongRound();
if (roundStartTime[marketId] == 0) revert RoundNotStarted();
// Prevent re-selection if jury already selected for this round
if (finalizeEligibleBlock[marketId] != 0) revert JuryAlreadySelected();
bool success = jury.voirDire(marketId, round, headers);
if (!success) { res.hungJuryCount++;
if (res.hungJuryCount >= MAX_HUNG_JURIES) {
res.verdict = new uint8[](0);
_sendRuling(marketId);
emit ForceMajeure(marketId);
return;
}
emit JurySelectionFailed(marketId, round);
return;
}
uint blocksUntilRevealEnd = (COMMIT_PERIOD + REVEAL_WINDOW) / 12;
finalizeEligibleBlock[marketId] = block.number + blocksUntilRevealEnd;
emit JurySelectionComplete(marketId, round);
}
function finalizeRound(uint64 marketId,
bytes[] calldata headers) external nonReentrant {
uint eligible = finalizeEligibleBlock[marketId];
if (eligible == 0) revert JuryNotSelected();
if (block.number < eligible || block.number > eligible + FINALIZE_BLOCK_WINDOW) {
revert OutsideFinalizeWindow();
}
Resolution storage res = resolutions[marketId];
if (verdictTimestamp[marketId] != 0 &&
block.timestamp <= verdictTimestamp[marketId] + APPEAL_WINDOW)
revert AlreadyFinalized();
jury.finalizeRound(marketId, res.currentRound,
roundStartTime[marketId], headers);
(uint8[] memory verdict, bool unanimous, bool meetsThreshold) =
jury.getStoredVerdict(marketId, res.currentRound);
if (!meetsThreshold || (res.requiresUnanimous && !unanimous)) {
_handleHungJury(marketId);
return;
}
address appellant = jury.getAppellant(marketId, res.currentRound);
if (appellant != address(0)) {
bool sustained = !_verdictMatches(res.verdict, verdict);
appeals[marketId][res.currentRound].sustained = sustained;
if (sustained) {
uint cost = appeals[marketId][res.currentRound].cost;
jury.refundAppellant(marketId, cost, appellant);
} else if (unanimous) {
// Failed frivolous appeal with unanimous verdict - finalize immediately
// Appellant penalty: appeal cost already burned (not refunded)
_finalize(marketId);
emit AppealResult(marketId, res.currentRound, false);
return;
}
emit AppealResult(marketId, res.currentRound, sustained);
}
res.verdict = verdict;
verdictTimestamp[marketId] = block.timestamp;
emit VerdictReached(marketId, res.currentRound, verdict);
}
error AppealRoundInProgress();
function executeVerdict(uint64 marketId) external nonReentrant {
Resolution storage res = resolutions[marketId];
if (verdictTimestamp[marketId] == 0) revert NoVerdictToExecute();
if (block.timestamp <= verdictTimestamp[marketId] + APPEAL_WINDOW) revert AppealWindowActive();
// Prevent execution during active appeal round
// If roundStartTime > verdictTimestamp, an appeal was filed and new round started
if (roundStartTime[marketId] > verdictTimestamp[marketId]) revert AppealRoundInProgress();
if (rulingData[marketId].length > 0) revert AlreadyExecuted();
if (res.verdict.length == 0) revert NoVerdictStored();
jury.tryDistribute(marketId);
_finalize(marketId);
}
function fileAppeal(uint64 marketId, AppealGround ground,
string calldata reasoning)
external nonReentrant returns (uint8) {
Resolution storage res = resolutions[marketId];
if (verdictTimestamp[marketId] == 0) revert NoVerdictToAppeal();
if (block.timestamp > verdictTimestamp[marketId] + APPEAL_WINDOW) revert AppealLate();
if (rulingData[marketId].length > 0) revert AlreadyExecuted();
if (roundStartTime[marketId] > verdictTimestamp[marketId]) revert AppealRoundInProgress();
if (res.appealCount >= MAX_APPEALS) revert MaxAppealsReached();
uint cost = _appealCost(marketId, res.appealCount);
QUID.transferFrom(msg.sender, address(jury), cost);
jury.addSlashedEth(marketId, cost);
res.appealCount++;
res.currentRound++;
if (res.currentRound >= MAX_TOTAL_ROUNDS) revert MaxRoundsExceeded();
appeals[marketId][res.currentRound] = Appeal({
appellant: msg.sender,
ground: ground,
reasoning: reasoning,
timestamp: block.timestamp,
cost: cost,
sustained: false
});
delete finalizeEligibleBlock[marketId];
roundStartTime[marketId] = block.timestamp;
jury.setAppellant(marketId, res.currentRound, msg.sender);
emit AppealFiled(marketId, res.currentRound,
msg.sender, ground, res.appealCount);
return res.currentRound;
}
function _finalize(uint64 marketId) internal {
_sendRuling(marketId); _evictMarketData(marketId);
emit ResolutionFinalized(marketId, resolutions[marketId].verdict);
}
function _evictMarketData(uint64 marketId) internal {
Resolution storage res = resolutions[marketId];
uint8 maxRound = res.currentRound;
res.numWinners = 0;
res.requiresUnanimous = false;
res.hungJuryCount = 0;
res.appealCost = 0;
res.appealCount = 0;
res.resolutionRequester = bytes32(0);
for (uint8 i = 0; i <= maxRound; i++) {
delete appeals[marketId][i].reasoning;
}
delete roundStartTime[marketId];
delete finalizeEligibleBlock[marketId];
uint slotsCleared = 10 + maxRound * 5;
emit MarketDataEvicted(marketId, slotsCleared * 15000);
}
function _handleHungJury(uint64 marketId) internal {
Resolution storage res = resolutions[marketId]; res.hungJuryCount++;
emit HungJury(marketId, res.currentRound, res.hungJuryCount);
if (res.hungJuryCount < MAX_HUNG_JURIES
&& res.currentRound + 1 < MAX_TOTAL_ROUNDS) {
// Copy appellant to next round if this was an appeal round
address currentAppellant = jury.getAppellant(marketId, res.currentRound);
uint currentAppealCost = appeals[marketId][res.currentRound].cost;
res.currentRound++;
if (currentAppellant != address(0)) {
// Carry forward appellant tracking for hung jury retry
jury.setAppellant(marketId, res.currentRound, currentAppellant);
appeals[marketId][res.currentRound].appellant = currentAppellant;
appeals[marketId][res.currentRound].cost = currentAppealCost;
}
delete finalizeEligibleBlock[marketId];
roundStartTime[marketId] = block.timestamp;
} else {
// ── Hung juries exhausted ──
// Arbitration: resolve as side 0 via UMA.receiveRuling().
// Non-arbitration: force majeure (cancel + pro-rata refund).
if (isArbitration[marketId]) {
res.verdict = new uint8[](1);
res.verdict[0] = 0; // conservative default
_sendRuling(marketId);
emit ForceMajeure(marketId);
} else {
delete res.verdict;
_sendRuling(marketId);
emit ForceMajeure(marketId);
}
}
}
function _sendRuling(uint64 marketId) internal {
// Set verdictTimestamp if not already set (for force majeure paths)
// This ensures _tryDistribute in Jury can proceed after appeal window
if (verdictTimestamp[marketId] == 0) {
verdictTimestamp[marketId] = block.timestamp;
}
Resolution storage res = resolutions[marketId];
// ── Arbitration: deliver ruling directly to UMA ──
// Same chain, no cross-chain messaging needed.
// Jury compensation uses the timeout path (no Solana funds).
if (isArbitration[marketId]) {
uint8 winningSide = (res.verdict.length > 0)
? res.verdict[0] : 0;
// Read CRE evidence severity — zero if CRE was unavailable
// (escalateToCourt path) or if jury found no depeg.
uint16 severityBps = 0;
bytes32 aId = arbitrationAssertionId[marketId];
if (aId != bytes32(0) && winningSide > 0) {
(,, uint maxDev,,,) = IUMA(umaOracle).getEvidence(aId);
severityBps = uint16(maxDev);
}
// Call UMA first, then mark as sent.
// If receiveRuling reverts the ruling is retryable via _sendRuling.
// reentrancy is safe: rulingData written after external call,
// and nonReentrant guards all Court entry points.
IUMA(umaOracle).receiveRuling(winningSide, severityBps);
rulingData[marketId] = hex"01";
return;
}
// ── Standard: encode and send cross-chain to Solana
bytes memory message = MessageCodec.encodeFinalRuling(
marketId, res.verdict);
rulingData[marketId] = message;
QUID.sendToSolana{value: 0.05 ether}(message);
}
function _verdictMatches(uint8[] memory a,
uint8[] memory b) internal pure returns (bool) {
if (a.length != b.length) return false;
for (uint i = 0; i < a.length; i++) {
if (a[i] != b[i]) return false;
}
return true;
}
function _appealCost(uint64 marketId,
uint8 appealIndex) internal view returns (uint) {
uint base = resolutions[marketId].appealCost;
for (uint i = 0; i < appealIndex; i++) {
base = (base * 15000) / 10000;
}
return base;
}
error RulingNotSent();
/// @notice Timeout if jury compensation never arrives from Solana
/// @param marketId The market ID
function timeoutJuryCompensation(uint64 marketId) external nonReentrant {
if (verdictTimestamp[marketId] == 0) revert NoVerdictToExecute();
if (block.timestamp <= verdictTimestamp[marketId] + APPEAL_WINDOW) revert TooEarly();
// Ruling must have been sent before we can timeout waiting for compensation
if (rulingData[marketId].length == 0) revert RulingNotSent();
// If ruling not sent, prevent timeout during active appeal round
// (But if rulingData exists, resolution is final regardless of roundStartTime)
jury.markCompensationTimedOut(marketId);
jury.tryDistribute(marketId);
}
function isInResolutionPhase(uint64 marketId) external view returns (bool) {
return roundStartTime[marketId] > 0 && verdictTimestamp[marketId] == 0;
}
function getMarketConfig(uint64 marketId) external
view returns (uint8 numSides, uint8 numWinners,
bool requiresUnanimous, uint appealCost) {
Resolution storage res = resolutions[marketId];
return (res.numSides, res.numWinners,
res.requiresUnanimous,
res.appealCost);
}
function getRoundStartTime(uint64 marketId)
external view returns (uint) {
return roundStartTime[marketId];
}
function getCurrentRound(uint64 marketId)
external view returns (uint8) {
return resolutions[marketId].currentRound;
}
function getVerdictTimestamp(uint64 marketId)
external view returns (uint) {
return verdictTimestamp[marketId];
}
function getFinalVerdict(uint64 marketId)
external view returns (uint8[] memory) {
return resolutions[marketId].verdict;
}
function getAppeal(uint64 marketId, uint8 round) external view returns (
address appellant, AppealGround ground,
string memory reasoning, uint timestamp, uint cost, bool sustained) {
Appeal storage appeal = appeals[marketId][round];
return (appeal.appellant, appeal.ground,
appeal.reasoning, appeal.timestamp, appeal.cost, appeal.sustained);
}
function getFinalizeWindow(uint64 marketId) external
view returns (uint eligibleBlock, uint windowEnd) {
eligibleBlock = finalizeEligibleBlock[marketId];
windowEnd = eligibleBlock + FINALIZE_BLOCK_WINDOW;
}
function isReadyForExecution(uint64 marketId)
external view returns (bool ready, string memory reason) {
if (verdictTimestamp[marketId] == 0) return (false, "No verdict yet");
if (block.timestamp <= verdictTimestamp[marketId] + APPEAL_WINDOW) return (false, "Appeal window active");
if (rulingData[marketId].length > 0) return (false, "Already executed");
if (resolutions[marketId].verdict.length == 0) return (false, "No verdict stored");
return (true, "");
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { IOFT, OFTCore } from "./OFTCore.sol";
/**
* @title OFT Contract
* @dev OFT is an ERC-20 token that extends the functionality of the OFTCore contract.
*/
abstract contract OFT is OFTCore, ERC20 {
/**
* @dev Constructor for the OFT contract.
* @param _name The name of the OFT.
* @param _symbol The symbol of the OFT.
* @param _lzEndpoint The LayerZero endpoint address.
* @param _delegate The delegate capable of making OApp configurations inside of the endpoint.
*/
constructor(
string memory _name,
string memory _symbol,
address _lzEndpoint,
address _delegate
) ERC20(_name, _symbol) OFTCore(decimals(), _lzEndpoint, _delegate) {}
/**
* @dev Retrieves the address of the underlying ERC20 implementation.
* @return The address of the OFT token.
*
* @dev In the case of OFT, address(this) and erc20 are the same contract.
*/
function token() external view returns (address) {
return address(this);
}
/**
* @notice Indicates whether the OFT contract requires approval of the 'token()' to send.
* @return requiresApproval Needs approval of the underlying token implementation.
*
* @dev In the case of OFT where the contract IS the token, approval is NOT required.
*/
function approvalRequired() external pure virtual returns (bool) {
return false;
}
/**
* @dev Burns tokens from the sender's specified balance.
* @param _amountLD The amount of tokens to send in local decimals.
* @param _minAmountLD The minimum amount to send in local decimals.
* @param _dstEid The destination chain ID.
* @return amountSentLD The amount sent in local decimals.
* @return amountReceivedLD The amount received in local decimals on the remote.
*/
function _debit(
uint256 _amountLD,
uint256 _minAmountLD,
uint32 _dstEid
) internal virtual override returns (uint256 amountSentLD, uint256 amountReceivedLD) {
(amountSentLD, amountReceivedLD) = _debitView(_amountLD, _minAmountLD, _dstEid);
// @dev In NON-default OFT, amountSentLD could be 100, with a 10% fee, the amountReceivedLD amount is 90,
// therefore amountSentLD CAN differ from amountReceivedLD.
// @dev Default OFT burns on src.
_burn(msg.sender, amountSentLD);
}
/**
* @dev Credits tokens to the specified address.
* @param _to The address to credit the tokens to.
* @param _amountLD The amount of tokens to credit in local decimals.
* @dev _srcEid The source chain ID.
* @return amountReceivedLD The amount of tokens ACTUALLY received in local decimals.
*/
function _credit(
address _to,
uint256 _amountLD,
uint32 _srcEid
) internal virtual override returns (uint256 amountReceivedLD) {
// @dev Default OFT mints on dst.
_mint(_to, _amountLD);
// @dev In the case of NON-default OFT, the _amountLD MIGHT not be == amountReceivedLD.
return _amountLD;
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
library Types {
/// @notice Vogue
/// self-managed LP
struct SelfManaged {
uint created;
address owner;
int24 lower;
int24 upper;
int liq;
}
/// @notice Amp AAVE
/// leveraged position
struct viaAAVE {
uint breakeven;
uint supplied;
uint borrowed;
uint buffer;
int price;
}
/// @notice Vogue LP deposit...
/// MasterChef-style fee tracking
struct Deposit { uint pooled_eth;
uint usd_owed;
uint fees_eth;
uint fees_usd;
}
/// @notice routing
struct AuxContext {
address v3Pool;
address v3Router;
address weth;
address usdc;
address vault;
address v4;
address core;
address rover;
uint24 v3Fee;
address hub;
bool isAAVE;
bool nativeWETH;
}
struct PositionEntry {
uint capital;
uint tokens;
bytes32 commitmentHash;
uint timestamp;
uint revealedConfidence; // max 10000 bps, 0 = not yet revealed
}
struct Position {
address user;
uint8 side;
uint totalCapital;
uint totalTokens;
bytes32 commitmentHash;
bool revealed;
uint revealedConfidence; // max 10000 bps
bool autoRollover;
uint weight; // final weight (confidence × time decay)
bool paidOut;
uint entryTimestamp; // when capital entered this round
uint lastRound; // round this position is active in
address delegate; // our keeper so user doesnt need to manually reveal their commit
}
/// @dev Forensic evidence submitted by CRE workflow.
/// Advisory only — DVM vote is sole source of truth.
struct ForensicEvidence {
uint8 claimedSide;
uint8 recommendedSide;
uint maxDeviationBps;
uint8 confidence; // 0-100
bytes32 evidenceHash;
// keccak256 of data
uint timestamp;
}
enum Phase { Trading, Asserting,
Disputed, Resolved, Arbitrating }
struct Market {
uint8 numSides; // stables.length + 1
uint startTime; // initial creation
uint roundStartTime; // beginning of current round
int128 b; // LSMR liquidity parameter
Phase phase;
bool resolved;
uint resolutionTimestamp;
uint8 claimedSide; // what the asserter claims
uint8 winningSide; // confirmed outcome
uint8 consecutiveRejections; // escalates bond after griefing
bytes32 assertionId;
address asserter;
uint revealDeadline;
uint requestTimestamp; // when requestResolution was called
int128[12] q;
// LSMR quantities per side
uint[12] capitalPerSide;
uint totalCapital;
uint positionsTotal;
uint positionsRevealed;
uint totalWinnerCapital;
uint totalLoserCapital;
uint totalWinnerWeight;
uint totalLoserWeight;
bool weightsComplete;
bool payoutsComplete;
bool assertionPending; // blocks new buys during OOV3 liveness
uint positionsPaidOut; // tracks payout progress for safe restart
uint positionsWeighed; // tracks weight computation for safe weightsComplete
uint roundNumber;
}
struct RouteParams {
uint160 sqrtPriceX96;
bool zeroForOne;
address token;
uint amount;
uint pooled;
uint v4Price;
uint v3Price;
address recipient;
}
struct DepegStats {
uint capOnSide;
uint capNone;
uint capTotal;
bool depegged;
uint8 side;
uint avgConf; // Bayesian prior: last round's avg confidence on this side
}
/// @dev Every field must be present for ABI compatibility with the
/// on-chain JamSettlement contract, even if our solver doesn't
/// use all of them.
/// For ERC20-only arb, pass:
/// sellNFTIds: new uint256[](0)
/// buyNFTIds: new uint256[](0)
/// sellTokenTransfers: "" (0x)
/// buyTokenTransfers: "" (0x)
struct JamOrder {
address taker; // order creator (EOA that signed)
address receiver; // where buy tokens are sent
uint256 expiry; // block.timestamp deadline
uint256 nonce; // unique per taker, prevents replay
address executor; // solver address, or address(0) for open
uint16 minFillPercent; // 10000 = 100%, minimum acceptable fill (Bebop ABI)
bytes32 hooksHash; // keccak256 of hooks, or EMPTY_HOOKS_HASH
address[] sellTokens; // tokens taker is selling
address[] buyTokens; // tokens taker wants to receive
uint256[] sellAmounts; // amounts of each sell token
uint256[] buyAmounts; // minimum amounts of each buy token
uint256[] sellNFTIds; // NFT token IDs to sell (empty for ERC20)
uint256[] buyNFTIds; // NFT token IDs to buy (empty for ERC20)
bytes sellTokenTransfers; // per-token transfer cmds (empty for ERC20)
bytes buyTokenTransfers; // per-token transfer cmds (empty for ERC20)
bool usingPermit2; // true if taker approved via Permit2
uint256 partnerInfo; // encoded partner + fee data (0 = none)
}
/// @notice A single external call for JamSettlement to execute.
/// @dev Matches Bebop's JamInteraction.Data exactly.
/// Used for settlement interactions (executed by JamSettlement)
/// and for repay swaps (executed directly by the Solver contract).
struct Interaction {
bool result; // if true, runInteractions checks call returns true
address to; // target contract to call
uint256 value; // ETH value to forward
bytes data; // calldata for the external call
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
// @dev Import the 'MessagingFee' and 'MessagingReceipt' so it's exposed to OApp implementers
// solhint-disable-next-line no-unused-import
import { OAppSender, MessagingFee, MessagingReceipt } from "./OAppSender.sol";
// @dev Import the 'Origin' so it's exposed to OApp implementers
// solhint-disable-next-line no-unused-import
import { OAppReceiver, Origin } from "./OAppReceiver.sol";
import { OAppCore } from "./OAppCore.sol";
/**
* @title OApp
* @dev Abstract contract serving as the base for OApp implementation, combining OAppSender and OAppReceiver functionality.
*/
abstract contract OApp is OAppSender, OAppReceiver {
/**
* @dev Constructor to initialize the OApp with the provided endpoint and owner.
* @param _endpoint The address of the LOCAL LayerZero endpoint.
* @param _delegate The delegate capable of making OApp configurations inside of the endpoint.
*/
constructor(address _endpoint, address _delegate) OAppCore(_endpoint, _delegate) {}
/**
* @notice Retrieves the OApp version information.
* @return senderVersion The version of the OAppSender.sol implementation.
* @return receiverVersion The version of the OAppReceiver.sol implementation.
*/
function oAppVersion()
public
pure
virtual
override(OAppSender, OAppReceiver)
returns (uint64 senderVersion, uint64 receiverVersion)
{
return (SENDER_VERSION, RECEIVER_VERSION);
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import {IV3SwapRouter as ISwapRouter} from "./v3/IV3SwapRouter.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {FullMath} from "v4-core/src/libraries/FullMath.sol";
import {TickMath} from "v4-core/src/libraries/TickMath.sol";
import {IERC4626} from "forge-std/interfaces/IERC4626.sol";
import {WETH as WETH9} from "solmate/src/tokens/WETH.sol";
import {IERC20} from "forge-std/interfaces/IERC20.sol";
import {IUniswapV3Pool} from "./v3/IUniswapV3Pool.sol";
import {Types} from "./Types.sol";
interface IAux {
function vaults(address) external returns (address);
function untouchables(address) external returns (uint);
function take(address who,
uint amount, address token,
uint seed) external returns (uint);
}
interface IRover {
function take(uint amount) external returns (uint);
function withdrawUSDC(uint amount) external returns (uint);
function deposit(uint amount) external;
function depositUSDC(uint amount, uint price) external;
}
interface AAVEv3 {
function getReserveAToken(address asset)
external view returns (address);
function getReserveNormalizedIncome(address asset)
external view returns (uint);
function supply(address asset, uint amount,
address onBehalfOf, uint16 referralCode) external;
function withdraw(address asset, uint amount,
address to) external returns (uint);
}
interface IHub {
function getAssetId(address underlying)
external view returns (uint256);
}
interface AAVEv4 {
struct UserAccountData {
uint totalCollateralValue;
uint totalDebtValue;
uint avgCollateralFactor;
}
struct Reserve { uint8 decimals; }
function getReserveId(address hub,
uint assetId) external returns (uint);
function withdraw(uint reserveId,
uint amount, address onBehalfOf)
external returns (uint, uint);
function supply(uint reserveId,
uint amount, address onBehalfOf)
external returns (uint256, uint256);
function borrow(uint reserveId,
uint amount, address onBehalfOf)
external returns (uint256);
function repay(uint reserveId,
uint amount, address onBehalfOf)
external returns (uint256);
function getUserSuppliedAssets(uint reserveId,
address user) external view returns (uint);
function getUserSuppliedShares(uint reserveId,
address user) external view returns (uint);
function getUserAccountData(address user)
external view returns (UserAccountData memory);
function getReserve(uint reserveId)
external view returns (Reserve memory);
function ORACLE() external view returns (address);
}
interface IAaveOracle {
function getReservePrice(uint reserveId)
external view returns (uint);
}
interface IVogueCore {
function POOLED_ETH() external view returns (uint);
function POOLED_USD() external view returns (uint);
function MAX_POOLED_USD() external view returns (uint);
function token1isETH() external view returns (bool);
function swap(uint160 sqrtPriceX96, address sender,
bool forOne, address token, uint amount) external returns (uint);
}
/// @notice Minimal interface for Liquity V2 StabilityPool
/// @dev 0x5721cbbd64fc7Ae3Ef44A0A3F9a790A9264Cf9BF (WETH)
interface IStabilityPool {
function provideToSP(uint _topUp, bool _doClaim) external;
function withdrawFromSP(uint _amount, bool _doClaim) external;
function getCompoundedBoldDeposit(address _depositor) external view returns (uint);
function getDepositorYieldGainWithPending(address _depositor) external view returns (uint);
}
interface ITeller { // RWA vault for >10% APY on USDC
function share() external view returns (address);
function asset() external view returns (address);
function todayTimestamp() external view returns (uint);
function convertToAssets(uint shares) external view returns (uint);
function convertToShares(uint assets) external view returns (uint);
function deposit(uint assets, address receiver) external returns (uint shares);
function redeem(uint shares, address receiver, address owner) external returns (uint assets);
function redemptionLimitRemaining(address account, uint day) external view returns (uint);
function limit(address) external view returns (uint56 depositLimit, uint56 redeemLimit);
}
library BasketLib {
uint public constant RAY = 1e27;
uint public constant WAD = 1e18;
uint public constant WEEK = 604800;
uint public constant MONTH = 2420000;
uint128 public constant GAS_FINAL_RULING = 200_000;
struct Metrics {
uint total;
uint last;
uint yield;
uint trackingStart;
uint yieldAccum;
}
function computeMetrics(Metrics memory stats,
uint elapsed, uint raw, uint yieldWeighted,
uint tvl) internal view returns (Metrics memory) {
if (stats.trackingStart > 0 && stats.last > 0)
stats.yieldAccum += stats.yield * elapsed;
else if (stats.trackingStart == 0)
stats.trackingStart = block.timestamp;
stats.yield = (raw > 0 && yieldWeighted >= raw) ?
FullMath.mulDiv(WAD, yieldWeighted, raw) - WAD
: stats.yield;
stats.total = tvl; stats.last = block.timestamp;
return stats;
}
/// @notice Extract composeMsg from send() calldata
/// @dev Uses assembly to efficiently parse SendParam struct
/// @return payload The extracted compose message bytes
/* ---------------------------------------------------------------*
* extracts SendParam.composeMsg from calldata...
* assumes send(SendParam, MessagingFee, address)
* [0] = dstEid(uint32), [1] = to(bytes32), [2] = amountLD(uint),
* [3] = minAmountLD(uint), [4] = extraOptions(bytes),
* [5] = composeMsg(bytes), [6] = oftCmd(bytes)
* ---------------------------------------------------------------*/
function extract(bytes calldata original)
external pure returns (bytes memory payload) {
assembly { let base := original.offset
let off0 := calldataload(add(base, 4))
let structStart := add(add(base, 4), off0)
let composeHeadPos := add(structStart, 0xA0)
let composeOffset := calldataload(composeHeadPos)
let composePos := add(structStart, composeOffset)
let len := calldataload(composePos)
let ptr := mload(0x40)
mstore(ptr, len)
calldatacopy(add(ptr, 0x20), add(composePos, 0x20), len)
let size := add(0x20, and(add(len, 0x1f), not(0x1f)))
mstore(0x40, add(ptr, size))
payload := ptr
}
}
/// @notice Build LayerZero Type 3 options for cross-chain messages
/// @param msgType Message type (FINAL_RULING)
/// @return options Encoded LZ options bytes
function buildOptions(uint8 msgType) external
pure returns (bytes memory) { uint128 gas;
if (msgType == 6) gas = GAS_FINAL_RULING;
else revert("Unknown message type");
// LayerZero V2 Type 3 Options Format:
// [type(uint16)][workerID(uint8)][optionLength(uint16)][optionType(uint8)][gas(uint128)][value(uint128)]
// See: https://docs.layerzero.network/v2/developers/evm/configuration/options
// OPTION_TYPE_LZRECEIVE contains (uint128 _gas, uint128 _value)
uint128 value = 10_000_000; // 0.01 SOL in lamports
// Total size: 2 + 1 + 2 + 1 + 16 + 16 = 38 bytes
bytes memory options = new bytes(38);
// Type 3 header (uint16 big-endian)
options[0] = 0x00;
options[1] = 0x03;
// Worker ID: Executor (uint8)
options[2] = 0x01;
// Option length: 33 bytes = 1 (option type) + 16 (gas) + 16 (value)
options[3] = 0x00;
options[4] = 0x21; // 0x21 = 33
// Option type: LZRECEIVE (uint8)
options[5] = 0x01;
// Gas (uint128 = 16 bytes, big-endian)
for (uint i = 0; i < 16; i++) {
options[6 + i] = bytes1(uint8(gas >> (120 - i * 8)));
}
// Value (uint128 = 16 bytes, big-endian)
for (uint i = 0; i < 16; i++) {
options[22 + i] = bytes1(uint8(value >> (120 - i * 8)));
}
return options;
}
function get_deposits(address aux, address pool,
address hub, address[] memory stables) external
returns (uint[14] memory amounts) { uint i;
// amounts[0] = yield-weighted sum across ALL sources
// amounts[1..11] = per-token deposit values (18 dec)
// amounts[12] = raw TVL total (all sources, + L2 QD)
// amounts[13] = L2 basket deposits (set by Aux, not BasketLib)
address vault; uint balance; address stable;
for (i = 0; i < 4; i++) { stable = stables[i];
uint yieldWeighted;
if (hub == address(0)) {
vault = AAVEv3(pool).getReserveAToken(stable);
// Some aTokens (e.g. USDT) can overflow in getReserveNormalizedIncome
// at certain fork blocks. Wrap in try/catch so one bad token
// doesn't brick the whole get_deposits call.
try IERC20(vault).balanceOf(address(this))
returns (uint bal) { balance = bal; }
catch { balance = 0; }
uint yieldFactor;
try AAVEv3(pool).getReserveNormalizedIncome(stable)
returns (uint yf) { yieldFactor = yf; }
catch { yieldFactor = RAY; } // 1:1 fallback
yieldWeighted = FullMath.mulDiv(balance, yieldFactor, RAY);
} else { uint reserveId = AAVEv4(pool).getReserveId(hub,
IHub(hub).getAssetId(stable));
balance = AAVEv4(pool).getUserSuppliedAssets(
reserveId, address(this));
uint shares = AAVEv4(pool).getUserSuppliedShares(
reserveId, address(this));
yieldWeighted = (shares > 0) ? FullMath.mulDiv(
balance, balance, shares) : balance;
}
if (balance > 0) { balance *= i < 3 ? 1e12 : 1;
yieldWeighted *= i < 3 ? 1e12 : 1;
uint reserved = IAux(aux).untouchables(stable);
if (reserved > 0) { uint cap = Math.min(balance, reserved);
balance -= cap; yieldWeighted -= Math.min(yieldWeighted, cap);
} amounts[i + 1] = balance; amounts[12] += balance;
amounts[0] += yieldWeighted;
}
} for (i = 4; i < 9; i++) {
stable = stables[i]; vault = IAux(aux).vaults(stable);
uint shares = IERC4626(vault).balanceOf(address(this));
uint reserved = IAux(aux).untouchables(stable);
if (reserved > 0) shares -= Math.min(shares,
IERC4626(vault).convertToShares(reserved));
if (shares > 0) {
balance = IERC4626(vault).convertToAssets(shares);
amounts[i + 1] = balance; // raw position value
amounts[12] += balance; // raw TVL total
// yield-weighted: balance × sharePrice
uint supply = IERC4626(vault).totalSupply();
if (supply > 0) {
amounts[0] += FullMath.mulDiv(balance,
IERC4626(vault).totalAssets(), supply);
} else amounts[0] += balance;
}
} stable = stables[10];
vault = IAux(aux).vaults(stable);
(uint usycValue, ) = getUSYCValue(
vault, address(this));
if (usycValue > 0) {
uint usycReserved = IAux(aux).untouchables(stable);
usycValue -= Math.min(usycValue, usycReserved);
amounts[11] = usycValue; amounts[12] += usycValue;
// Yield-weighted: usycValue × teller growth rate.
// growth = convertToAssets(shares) / shares (teller-wide, pre-reserve).
// So yieldWeighted = usycValue × totalValue / parValue.
address usyc = ITeller(vault).share();
uint shares = IERC20(usyc).balanceOf(address(this));
if (shares > 0) {
uint totalValue = ITeller(vault).convertToAssets(shares) * 1e12;
uint parValue = shares * 1e12;
amounts[0] += FullMath.mulDiv(usycValue, totalValue, parValue);
} else amounts[0] += usycValue;
}
}
function getAverageYield(Metrics memory stats)
external view returns (uint) {
if (stats.trackingStart == 0) return 0;
uint totalTime = block.timestamp - stats.trackingStart;
uint timeSinceUpdate = block.timestamp - stats.last;
uint currentAccum = stats.yieldAccum
+ stats.yield * timeSinceUpdate;
return currentAccum / (totalTime + 1);
}
// sqrt(deficit) × avgYield / 4
function seedFee(uint usd,
uint untouchable, uint target,
uint avgYield) internal pure returns (uint) {
if (target == 0 || untouchable >= target) return 0;
uint deficit = FullMath.mulDiv(
target - untouchable, WAD, target);
uint sqrtDef = Math.sqrt(deficit * WAD);
if (sqrtDef == 0 || avgYield == 0) return 0;
return Math.min(FullMath.mulDiv(FullMath.mulDiv(
usd, sqrtDef, WAD), avgYield, WAD * 4),
target - untouchable);
}
/// @notice Calculate SP position
// & yield contribution in get_deposits
/// @param sp StabilityPool address...
/// @param depositor Address to check
/// @param reserved Untouchable amount
/// @param state Current SP tracking state
/// @return totalValue Position value (compounded + yield - reserved)
/// @return yieldContrib Contribution to amounts[12] for weighted average
function calcSPValue(address sp, address depositor,
uint reserved, SPState memory state) external view returns
(uint totalValue, uint yieldContrib) { if (sp == address(0)) return (0, 0);
uint compounded = IStabilityPool(sp).getCompoundedBoldDeposit(depositor);
uint yieldGain = IStabilityPool(sp).getDepositorYieldGainWithPending(depositor);
totalValue = compounded + yieldGain; totalValue -= Math.min(totalValue, reserved);
if (totalValue == 0) return (0, 0);
// Calculate time-weighted APY for yield contribution
uint currentPrincipalTime = state.spPrincipalTime +
state.spValue * (block.timestamp - state.spLastUpdate);
if (currentPrincipalTime > 0 && state.spTotalYield > 0) {
uint rate = FullMath.mulDiv(state.spTotalYield,
WAD * 365 days, currentPrincipalTime);
yieldContrib = FullMath.mulDiv(
totalValue, WAD + rate, WAD);
} else
yieldContrib = totalValue;
// no yield history, 1:1
}
struct SPState {
uint spValue; // original principal tracking (not compounded yield)
uint spTotalYield; // cumulative harvested yield (USD value, WAD)
uint spPrincipalTime; // cumulative (principal * seconds)
uint spLastUpdate; // last update timestamp
}
/// @notice Result from SP withdraw operation
struct SPWithdrawResult {
uint sent; // BOLD amount being sent out
uint toRedeposit; // BOLD amount to redeposit (already done)
uint wethGain; // ETH collateral gained (caller handles swap)
uint boldReceived; // Total BOLD received from SP
// Updated state values - caller must write these
uint newSpValue;
uint newSpTotalYield;
uint newSpPrincipalTime;
uint newSpLastUpdate;
}
/// @notice Execute SP withdrawal and return results with updated state
/// @dev Performs withdrawFromSP and provideToSP, caller handles WETH swap
/// @param sp StabilityPool address
/// @param bold BOLD token address
/// @param weth WETH token address
/// @param amount Requested withdrawal amount
/// @param ethPrice Current ETH price (WAD) for yield calculation
/// @param state Current SP tracking state
/// @return r Result with amounts and updated state values
function withdrawFromSP(address sp, address bold,
address weth, uint amount, uint ethPrice,
SPState memory state) external returns (SPWithdrawResult memory r) {
uint compounded = IStabilityPool(sp).getCompoundedBoldDeposit(address(this));
uint yieldGain = IStabilityPool(sp).getDepositorYieldGainWithPending(address(this));
uint totalBold = compounded + yieldGain;
if (totalBold == 0) return r;
// Only from principal, not yield...
amount = Math.min(amount, compounded);
uint wethBefore = WETH9(payable(weth)).balanceOf(address(this));
uint boldBefore = IERC20(bold).balanceOf(address(this));
IStabilityPool(sp).withdrawFromSP(compounded, true);
r.boldReceived = IERC20(bold).balanceOf(address(this)) - boldBefore;
r.wethGain = WETH9(payable(weth)).balanceOf(address(this)) - wethBefore;
// Send only from principal
r.sent = Math.min(amount, r.boldReceived);
// Redeposit everything else (includes all yield)
r.toRedeposit = r.boldReceived - r.sent;
r.newSpPrincipalTime = state.spPrincipalTime +
state.spValue * (block.timestamp - state.spLastUpdate);
r.newSpLastUpdate = block.timestamp;
// Only ETH gains count as harvested (BOLD yield compounds)
uint wethValueUSD = FullMath.mulDiv(r.wethGain, ethPrice, WAD);
r.newSpTotalYield = state.spTotalYield + wethValueUSD;
// Sent is all principal
r.newSpValue = state.spValue > r.sent ? state.spValue - r.sent : 0;
if (r.toRedeposit > 0) IStabilityPool(sp).provideToSP(r.toRedeposit, false);
}
/// @notice Deposit BOLD to StabilityPool and return updated state
/// @param sp StabilityPool address
/// @param amount Amount to deposit
/// @param state Current SP tracking state
/// @return newSpValue Updated spValue
/// @return newSpPrincipalTime Updated spPrincipalTime
/// @return newSpLastUpdate Updated spLastUpdate
function depositToSP(address sp,
uint amount, SPState memory state) external
returns (uint newSpValue, uint newSpPrincipalTime, uint newSpLastUpdate) {
newSpPrincipalTime = state.spPrincipalTime + state.spValue * (
block.timestamp - state.spLastUpdate);
newSpValue = state.spValue + amount;
newSpLastUpdate = block.timestamp;
IStabilityPool(sp).provideToSP(amount, false);
}
/// @param amount Amount being deposited
/// @return cut Fee amount to deduct from deposit
/// @notice Deposit fee driven by weighted median vote (K).
/// Symmetric with withdrawal: stressed (high haircut)
/// → higher fee → reserves build faster.
/// K=0 → 900bps (9%), K=32 → 100bps (1%)
/// @notice ETH price from sqrtPriceX96
/// @param sqrtPriceX96 Square root price
/// @param token0isUSD Whether token0 is USD
/// @return price ETH price in USD 1e18
function getPrice(uint160 sqrtPriceX96, bool token0isUSD)
public pure returns (uint price) {
uint casted = uint(sqrtPriceX96);
uint ratioX128 = FullMath.mulDiv(
casted, casted, 1 << 64);
if (token0isUSD) {
price = FullMath.mulDiv(1 << 128,
WAD * 1e12, ratioX128);
} else {
price = FullMath.mulDiv(ratioX128,
WAD * 1e12, 1 << 128);
}
}
function getUSYCRedeemable(address teller) public view returns
(uint) { address usycToken = ITeller(teller).share();
uint usycBalance = ITeller(teller).convertToAssets(
IERC20(usycToken).balanceOf(msg.sender));
uint dailyLimit = ITeller(teller).redemptionLimitRemaining(
msg.sender, ITeller(teller).todayTimestamp());
return Math.min(usycBalance, dailyLimit) * 1e12; // Scale to 1e18
}
function supplyAAVE(address aave,
address asset, uint amount,
address to, address hub) // 6909
public returns (uint deposited) {
if (hub == address(0)) { deposited = amount;
AAVEv3(aave).supply(asset,
amount, to, 0);
} else { uint reserveId = AAVEv4(aave).getReserveId(hub,
IHub(hub).getAssetId(asset));
(, deposited) = AAVEv4(aave).supply(reserveId,
amount, address(this));
}
}
function withdrawAAVE(address aave, address asset,
uint amount, address to, address hub)
public returns (uint drawn) {
if (hub == address(0)) {
amount = Math.min(amount, aaveAvailableV3(
aave, asset));
if (amount == 0) return 0;
drawn = AAVEv3(aave).withdraw(asset, amount, to);
} else {
uint reserveId = AAVEv4(aave).getReserveId(hub,
IHub(hub).getAssetId(asset));
uint max = AAVEv4(aave).getUserSuppliedAssets(
reserveId, address(this));
amount = amount > 0 ? Math.min(amount, max) : max;
if (amount == 0) return 0;
(, drawn) = AAVEv4(aave).withdraw(reserveId,
amount, address(this));
if (to != address(this))
IERC20(asset).transfer(to, drawn);
}
}
function getUSYCValue(// time value of money
address teller, address holder) public
view returns (uint value, uint yield) {
if (teller == address(0)) return (0, 0);
address usyc = ITeller(teller).share();
uint shares = IERC20(usyc).balanceOf(holder);
uint assets = ITeller(teller).convertToAssets(shares); // USDC 6 dec
value = assets * 1e12; // Scale to 18 dec
// Yield = value above par (1 USYC started at $1)
uint parValue = shares * 1e12; // par = 1:1 with USDC
yield = value > parValue ? value - parValue : 0;
}
function withdrawUSYC(address teller,
address recipient, uint amount)
external returns (uint sent,
uint sharesUsed) { address usyc = ITeller(teller).share();
uint shares = IERC20(usyc).balanceOf(address(this));
shares = Math.min(ITeller(teller).convertToShares(amount), shares);
uint today = ITeller(teller).todayTimestamp();
uint remaining = ITeller(teller).redemptionLimitRemaining(
address(this), today);
sharesUsed = Math.min(shares,
ITeller(teller).convertToShares(remaining));
if (sharesUsed > 0) sent = ITeller(teller).redeem(sharesUsed,
recipient, address(this));
}
function depositUSYC(address teller, address aave,
address usdc, uint amount, address hub) public returns
(uint pulled, uint deposited) { if (amount == 0) return (0, 0);
(uint56 depositLimit,) = ITeller(teller).limit(address(this));
if (depositLimit == 0) return (0, amount);
// reuse `deposited` as maxToUSYC, `pulled` as capacity
try ITeller(teller).redemptionLimitRemaining(
address(this), ITeller(teller).todayTimestamp())
returns (uint redeemable) {
address usycToken = ITeller(teller).share();
deposited = ITeller(teller).convertToAssets(
IERC20(usycToken).balanceOf(address(this)));
deposited = redeemable > deposited ?
redeemable - deposited : 0;
} catch { return (0, amount); }
pulled = Math.min(uint(depositLimit), deposited);
// pulled = capacity, deposited = 0 from here
deposited = Math.min(amount, pulled);
uint fromAAVE; // ^ fromIncoming
if (pulled > deposited) {
uint aaveBal = hub == address(0)
? aaveAvailableV3(aave, usdc)
: AAVEv4(aave).getUserSuppliedAssets(
AAVEv4(aave).getReserveId(hub,
IHub(hub).getAssetId(usdc)),
address(this));
fromAAVE = Math.min(pulled - deposited, aaveBal);
if (fromAAVE > 0)
withdrawAAVE(aave, usdc, fromAAVE,
address(this), hub);
}
// toTeller = deposited + fromAAVE (reuse `pulled`)
pulled = deposited + fromAAVE;
if (pulled > 0) {
deposited = IERC20(usdc).balanceOf(address(this));
try ITeller(teller).deposit(pulled, address(this))
returns (uint) { pulled = deposited -
IERC20(usdc).balanceOf(address(this));
} catch {
if (fromAAVE > 0) {
supplyAAVE(aave, usdc, fromAAVE,
address(this), hub);
fromAAVE = 0;
} pulled = 0;
}
} else pulled = 0;
// pulled = actual USDC consumed by teller
// deposited = remainder for caller to send to AAVE
deposited = amount - (pulled > fromAAVE
? pulled - fromAAVE : 0);
}
function ticksToPrice(int56 tickCum0,
int56 tickCum1, uint32 period, bool token0isUSD) external
pure returns (uint price) { int56 delta = tickCum1 - tickCum0;
int24 averageTick = int24(delta / int56(uint56(period)));
uint160 sqrtPriceX96 = TickMath.getSqrtPriceAtTick(averageTick);
price = getPrice(sqrtPriceX96, token0isUSD);
}
/// @notice Find index of last mature batch
function matureBatches(uint[] memory batches,
uint currentTimestamp, uint deployedTime)
external pure returns (int i) {
uint currentMonth = (currentTimestamp - deployedTime) / MONTH;
int start = int(batches.length - 1);
for (i = start; i >= 0; i--)
if (batches[uint(i)] <= currentMonth) return i;
return -1;
}
/// @param thresholdPercent Max deviation
function isManipulated(uint spot, uint twap,
uint thresholdPercent) public pure returns (bool) {
uint dev = spot > twap ? spot - twap : twap - spot;
return dev * 100 > twap * thresholdPercent;
}
/// fetch V3 spot, check against twap
function isV3Manipulated(address pool,
bool token1isWETH, uint twapPrice)
public view returns (bool) {
(uint160 sqrtPriceX96,,,,,,) = IUniswapV3Pool(pool).slot0();
return isManipulated(getPrice(sqrtPriceX96, token1isWETH), twapPrice, 2);
}
function calculateVaultWithdrawal(address vault, uint amount)
external view returns (uint sharesNeeded, uint assetsReceived) {
uint vaultBalance = IERC4626(vault).balanceOf(address(this));
sharesNeeded = IERC4626(vault).convertToShares(amount);
sharesNeeded = Math.min(vaultBalance, sharesNeeded);
assetsReceived = IERC4626(vault).convertToAssets(sharesNeeded);
return (sharesNeeded, assetsReceived);
}
/// @notice Scale token amounts between precisions...
function scaleTokenAmount(uint amount, address token,
bool scaleUp) external view returns (uint scaled) {
uint decimals = IERC20(token).decimals();
uint scale = decimals < 18 ? 18 - decimals : 0;
scaled = scale > 0 ? (scaleUp ? amount * (10 ** scale):
amount / (10 ** scale)) : amount; return scaled;
}
function arbETH(Types.AuxContext memory ctx, uint shortfall,
uint price) external returns (uint got, bool failed) {
uint usdNeeded = convert(shortfall, price, false);
uint took = IAux(address(this)).take(address(this),
usdNeeded, ctx.usdc, 0);
if (took == 0)
return (0, false);
got = swapUSDCtoWETH(ctx,
took / 1e12, price);
if (got == 0) {
// V3 failed — USDC was pulled but never converted.
// Re-deposit so it earns yield and stays visible.
uint stranded = IERC20(ctx.usdc).balanceOf(address(this));
if (stranded > 0) {
if (ctx.isAAVE) supplyAAVE(ctx.vault, ctx.usdc,
stranded, address(this), ctx.hub);
// !isAAVE: leave USDC in contract (no USDC vault in ctx)
}
return (0, false);
}
if (ctx.isAAVE) supplyAAVE(ctx.vault, ctx.weth,
got, address(this), ctx.hub);
else got = IERC4626(ctx.vault).convertToAssets(
IERC4626(ctx.vault).deposit(got, ctx.v4));
}
function swapWETHtoUSDC(Types.AuxContext memory ctx,
uint amountIn, uint price) public returns (uint amountOut) {
uint poolUSDC = IERC20(ctx.usdc).balanceOf(ctx.v3Pool);
uint max = convert(poolUSDC, price, true);
if (amountIn > max) amountIn = max;
if (amountIn > 0) {
uint minOut = convert(amountIn, price, false) * 99500 / 100000;
(bool ok, bytes memory ret) = ctx.v3Router.call(abi.encodeWithSelector(
ISwapRouter.exactInput.selector, ISwapRouter.ExactInputParams(
abi.encodePacked(ctx.weth, ctx.v3Fee, ctx.usdc),
address(this), amountIn, minOut)));
if (ok && ret.length >= 32) amountOut = abi.decode(ret, (uint));
}
}
function sourceExternalUSD(Types.AuxContext memory ctx,
uint wethIn, uint price) public returns (uint usdcOut) {
uint wethBefore = IERC20(ctx.weth).balanceOf(address(this));
if (ctx.rover != address(0)) {
uint targetUSDC = convert(wethIn, price, false);
uint fromRover = IRover(ctx.rover).withdrawUSDC(targetUSDC);
if (fromRover > 0) {
uint wethForRover = convert(
fromRover, price, true);
IRover(ctx.rover).deposit(wethForRover);
usdcOut = fromRover;
wethIn = wethIn > wethForRover ?
wethIn - wethForRover : 0;
}
} if (wethIn > 0) usdcOut += swapWETHtoUSDC(ctx, wethIn, price);
// Only sweep WETH that accumulated during THIS call
// (i.e., was pulled for swapping but V3 failed)...
uint wethAfter = IERC20(ctx.weth).balanceOf(address(this));
if (wethAfter > wethBefore) {
uint stranded = wethAfter - wethBefore;
if (ctx.isAAVE) supplyAAVE(ctx.vault, ctx.weth,
stranded, address(this), ctx.hub);
else IERC4626(ctx.vault).deposit(stranded, address(this));
}
}
function sourceExternalWETH(Types.AuxContext memory ctx,
uint usdcIn, uint price)
public returns (uint wethOut) {
uint usdcBefore = IERC20(ctx.usdc).balanceOf(address(this));
if (ctx.rover != address(0)) {
uint targetWETH = convert(usdcIn, price, true);
uint fromRover = IRover(ctx.rover).take(targetWETH);
if (fromRover > 0) {
uint usdcForRover = convert(fromRover, price, false);
// Cap to available: price divergence can make usdcForRover > usdcIn
usdcForRover = Math.min(usdcForRover, usdcIn);
IRover(ctx.rover).depositUSDC(usdcForRover, price);
wethOut = fromRover;
usdcIn = usdcIn > usdcForRover ?
usdcIn - usdcForRover : 0;
}
} if (usdcIn > 0) wethOut += swapUSDCtoWETH(ctx, usdcIn, price);
// Sweep USDC that arrived during this call but wasn't consumed
uint usdcAfter = IERC20(ctx.usdc).balanceOf(address(this));
if (usdcAfter > usdcBefore) {
uint stranded = usdcAfter - usdcBefore;
if (ctx.isAAVE) supplyAAVE(ctx.vault, ctx.usdc,
stranded, address(this), ctx.hub);
// !isAAVE: USDC stays in contract, visible via get_deposits
}
}
/// @notice Unified token sourcing via Rover → V3
/// @param target Amount of output token needed
/// @param input Available input token to swap
/// @param price Current price for external swaps
/// @param forUSD true = WETH→USDC, false = USDC→WETH
function source(Types.AuxContext memory ctx,
uint target, uint input, uint price,
bool forUSD) external returns
(uint got, uint used) {
if (forUSD) {
// Want USDC from WETH...
if (target > 0 && input > 0) {
uint selling = Math.min(convert(target,
price, true), input);
if (selling > 0) {
got = sourceExternalUSD(
ctx, selling, price);
used = selling;
}
}
} else { // Want WETH from USDC...
if (target > 0 && input > 0) {
used = Math.min(convert(target,
price, false), input);
if (used > 0)
got = sourceExternalWETH(
ctx, used, price);
}
}
}
function routeSwap(Types.AuxContext memory ctx,
Types.RouteParams memory p) external returns
(uint out, uint poolSupplied) { uint remainder;
if (!isManipulated(getPrice(p.sqrtPriceX96,
IVogueCore(ctx.core).token1isETH()), p.v4Price, 2)) {
uint pooled = Math.min(p.amount, convert(p.pooled,
p.v4Price, p.token != address(0)));
if (pooled > 0) {
if (p.token != address(0)) {
if (ctx.isAAVE) supplyAAVE(ctx.vault, ctx.weth,
pooled, address(this), ctx.hub);
else pooled = IERC4626(ctx.vault).convertToAssets(
IERC4626(ctx.vault).deposit(pooled, ctx.v4));
poolSupplied = pooled;
} out = IVogueCore(ctx.core).swap(p.sqrtPriceX96,
p.recipient, p.zeroForOne, p.token, pooled);
}
remainder = p.amount - pooled;
} else remainder = p.amount;
if (remainder > 0) {
require(!isV3Manipulated(ctx.v3Pool,
IVogueCore(ctx.core).token1isETH(), p.v3Price));
if (p.token == address(0)) {
remainder = IAux(address(this)).take(address(this),
remainder, ctx.usdc, 0);
if (remainder > 0) {
remainder = sourceExternalWETH(ctx,
remainder, p.v3Price);
if (ctx.nativeWETH) {
WETH9(payable(ctx.weth)).withdraw(remainder);
{ (bool ok,) = p.recipient.call{
value: remainder}("");
require(ok); }
} else {
IERC20(ctx.weth).transfer(
p.recipient, remainder);
} out += remainder;
}
} else { remainder = sourceExternalUSD(ctx,
remainder, p.v3Price);
IERC20(ctx.usdc).transfer(
p.recipient, remainder);
out += remainder;
}
}
}
/// @notice Swap USDC→WETH via V3, capped at pool liquidity
function swapUSDCtoWETH(Types.AuxContext memory ctx,
uint amountIn, uint price) public returns (uint amountOut) {
uint poolWETH = IERC20(ctx.weth).balanceOf(ctx.v3Pool);
uint max = convert(poolWETH, price, false);
if (amountIn > max) amountIn = max;
if (amountIn > 0) {
uint minOut = convert(amountIn, price, true) * 99500 / 100000; // TODO slippage?
(bool ok, bytes memory ret) = ctx.v3Router.call(abi.encodeWithSelector(
ISwapRouter.exactInput.selector, ISwapRouter.ExactInputParams(
abi.encodePacked(ctx.usdc, ctx.v3Fee, ctx.weth),
address(this), amountIn, minOut)));
if (ok && ret.length >= 32) amountOut = abi.decode(ret, (uint));
}
}
/// @notice Get available AAVE liquidity
/// (min of aToken balance and reserve)
function aaveAvailableV3(address aave,
address asset) public view returns (uint) {
address aToken = AAVEv3(aave).getReserveAToken(asset);
uint balance = IERC20(aToken).balanceOf(address(this));
uint reserve = IERC20(asset).balanceOf(aToken);
return Math.min(balance, reserve);
}
/// @notice Convert amount between
// ETH (18 dec) & USD (6 dec) using price
function convert(uint amount, uint price,
bool toETH) public pure returns (uint) {
return toETH ? FullMath.mulDiv(amount * 1e12, WAD, price) // USD to ETH
: FullMath.mulDiv(amount, price, WAD) / 1e12; // ETH to USD
}
/// @notice Distribute proportional L2 basket tokens.
/// @dev Called via DELEGATECALL — transfers from caller's balance.
function distributeL2(address[] memory baskets,
address to, uint burned, uint total)
external returns (uint totalOut) {
if (total == 0) return 0;
for (uint i; i < baskets.length; ++i) {
uint bal = IERC20(baskets[i]).balanceOf(
address(this));
if (bal == 0) continue;
uint share = FullMath.mulDiv(burned, bal, total);
if (share > 0) {
IERC20(baskets[i]).transfer(to, share);
totalOut += share;
}
}
}
/// @notice Yield-enhanced mint amount calculation.
function calcMintYield(uint deposited, uint decimals,
uint when, uint nextMonth, uint currentMonth_,
uint seeded, uint avgYield, bool isSeed)
external pure returns (uint normalized, uint month) {
normalized = decimals < 18 ? deposited
* (10 ** (18 - decimals)) : deposited;
month = when > nextMonth ? when : nextMonth;
uint yield = isSeed ? avgYield * 2 : avgYield;
if (isSeed) month -= 1;
normalized += FullMath.mulDiv(normalized * yield,
month - currentMonth_, WAD * 12);
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
library SortedSetLib {
struct Set {
uint[] sortedArray;
mapping(uint => bool) exists;
}
/// @notice Inserts a value while maintaining sorting order.
function insert(Set storage self, uint value) internal {
if (self.exists[value]) return; // Ignore duplicates
self.exists[value] = true;
(uint index, ) = binarySearch(self, value);
self.sortedArray.push(0); // Expand array
// Shift elements right to insert in the correct position
for (uint i = self.sortedArray.length - 1; i > index; i--) {
self.sortedArray[i] = self.sortedArray[i - 1];
}
self.sortedArray[index] = value;
}
/// @notice Removes a value and triggers automatic cleanup.
function remove(Set storage self, uint value) internal {
require(self.exists[value], "Value does not exist");
(uint index, ) = binarySearch(self, value);
require(index < self.sortedArray.length
&& self.sortedArray[index] == value, "Value not found");
self.sortedArray[index] = type(uint).max; // Mark as deleted
delete self.exists[value]; // ^ max is just a sentinel value
compactArray(self); // Cleanup on every removal
}
/// @notice Binary search to find index for insertion or lookup.
function binarySearch(Set storage self,
uint value) internal view returns (uint, bool) {
uint left = 0; uint right = self.sortedArray.length;
while (left < right) {
uint mid = left + (right - left) / 2;
if (self.sortedArray[mid] == value) {
return (mid, true); // Value found
}
else if (self.sortedArray[mid] < value) {
left = mid + 1;
}
else {
right = mid;
}
}
return (left, false); // Value not found, return insertion index
}
/// @notice Performs automatic cleanup by removing all `0`s.
function compactArray(Set storage self)
internal { uint newLength = 0;
uint[] memory newArray = new uint[](self.sortedArray.length);
for (uint i = 0; i < self.sortedArray.length; i++) {
if (self.sortedArray[i] != type(uint).max) {
newArray[newLength] = self.sortedArray[i];
newLength++;
}
}
// Resize the array
self.sortedArray = new uint[](newLength);
for (uint i = 0; i < newLength; i++) {
self.sortedArray[i] = newArray[i];
}
}
/// @notice Returns the sorted set.
function getSortedSet(Set storage self)
internal view returns (uint[] memory) {
return self.sortedArray;
}
}// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
/// @notice Minimalist and gas efficient standard ERC6909 implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC6909.sol)
abstract contract ERC6909 {
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event OperatorSet(address indexed owner, address indexed operator, bool approved);
event Approval(address indexed owner, address indexed spender, uint256 indexed id, uint256 amount);
event Transfer(address caller, address indexed from, address indexed to, uint256 indexed id, uint256 amount);
/*//////////////////////////////////////////////////////////////
ERC6909 STORAGE
//////////////////////////////////////////////////////////////*/
mapping(address => mapping(address => bool)) public isOperator;
mapping(address => mapping(uint256 => uint256)) public balanceOf;
mapping(address => mapping(address => mapping(uint256 => uint256))) public allowance;
/*//////////////////////////////////////////////////////////////
ERC6909 LOGIC
//////////////////////////////////////////////////////////////*/
function transfer(
address receiver,
uint256 id,
uint256 amount
) public virtual returns (bool) {
balanceOf[msg.sender][id] -= amount;
balanceOf[receiver][id] += amount;
emit Transfer(msg.sender, msg.sender, receiver, id, amount);
return true;
}
function transferFrom(
address sender,
address receiver,
uint256 id,
uint256 amount
) public virtual returns (bool) {
if (msg.sender != sender && !isOperator[sender][msg.sender]) {
uint256 allowed = allowance[sender][msg.sender][id];
if (allowed != type(uint256).max) allowance[sender][msg.sender][id] = allowed - amount;
}
balanceOf[sender][id] -= amount;
balanceOf[receiver][id] += amount;
emit Transfer(msg.sender, sender, receiver, id, amount);
return true;
}
function approve(
address spender,
uint256 id,
uint256 amount
) public virtual returns (bool) {
allowance[msg.sender][spender][id] = amount;
emit Approval(msg.sender, spender, id, amount);
return true;
}
function setOperator(address operator, bool approved) public virtual returns (bool) {
isOperator[msg.sender][operator] = approved;
emit OperatorSet(msg.sender, operator, approved);
return true;
}
/*//////////////////////////////////////////////////////////////
ERC165 LOGIC
//////////////////////////////////////////////////////////////*/
function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
return
interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165
interfaceId == 0x0f632fb3; // ERC165 Interface ID for ERC6909
}
/*//////////////////////////////////////////////////////////////
INTERNAL MINT/BURN LOGIC
//////////////////////////////////////////////////////////////*/
function _mint(
address receiver,
uint256 id,
uint256 amount
) internal virtual {
balanceOf[receiver][id] += amount;
emit Transfer(msg.sender, address(0), receiver, id, amount);
}
function _burn(
address sender,
uint256 id,
uint256 amount
) internal virtual {
balanceOf[sender][id] -= amount;
emit Transfer(msg.sender, sender, address(0), id, amount);
}
}// SPDX-License-Identifier: MIT
pragma solidity >=0.6.2;
/// @dev Interface of the ERC20 standard as defined in the EIP.
/// @dev This includes the optional name, symbol, and decimals metadata.
interface IERC20 {
/// @dev Emitted when `value` tokens are moved from one account (`from`) to another (`to`).
event Transfer(address indexed from, address indexed to, uint256 value);
/// @dev Emitted when the allowance of a `spender` for an `owner` is set, where `value`
/// is the new allowance.
event Approval(address indexed owner, address indexed spender, uint256 value);
/// @notice Returns the amount of tokens in existence.
function totalSupply() external view returns (uint256);
/// @notice Returns the amount of tokens owned by `account`.
function balanceOf(address account) external view returns (uint256);
/// @notice Moves `amount` tokens from the caller's account to `to`.
function transfer(address to, uint256 amount) external returns (bool);
/// @notice Returns the remaining number of tokens that `spender` is allowed
/// to spend on behalf of `owner`
function allowance(address owner, address spender) external view returns (uint256);
/// @notice Sets `amount` as the allowance of `spender` over the caller's tokens.
/// @dev Be aware of front-running risks: https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
function approve(address spender, uint256 amount) external returns (bool);
/// @notice Moves `amount` tokens from `from` to `to` using the allowance mechanism.
/// `amount` is then deducted from the caller's allowance.
function transferFrom(address from, address to, uint256 amount) external returns (bool);
/// @notice Returns the name of the token.
function name() external view returns (string memory);
/// @notice Returns the symbol of the token.
function symbol() external view returns (string memory);
/// @notice Returns the decimals places of the token.
function decimals() external view returns (uint8);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
library MessageCodec { // always plead the 5th...
uint8 public constant RESOLUTION_REQUEST = 5;
// A life is like a book. A book is like
uint8 public constant FINAL_RULING = 6;
// a box...a box has six sides. Inspite &
// outside, so, how do you get to what's
// insight? do you get what's inside, out?
uint8 public constant JURY_COMPENSATION = 7;
error InvalidMessageType();
error InvalidMessageLength();
error InvalidMarketId();
error InvalidSideCount();
error InvalidAmount();
error ResolutionTimeInPast();
error InvalidWinnerCount();
error InvalidSplits();
/// @notice Resolution request data from Solana
/// @dev Using struct to avoid stack too deep errors
struct ResolutionRequestData { uint64 marketId;
uint8 numSides; uint8 numWinners;
bool requiresUnanimous;
uint256 appealCost; bytes32 requester;
}
function encodeUint64LE(uint64 value) internal pure returns (bytes memory) {
bytes memory result = new bytes(8);
result[0] = bytes1(uint8(value));
result[1] = bytes1(uint8(value >> 8));
result[2] = bytes1(uint8(value >> 16));
result[3] = bytes1(uint8(value >> 24));
result[4] = bytes1(uint8(value >> 32));
result[5] = bytes1(uint8(value >> 40));
result[6] = bytes1(uint8(value >> 48));
result[7] = bytes1(uint8(value >> 56));
return result;
}
function decodeUint64LE(bytes memory data, uint256 offset)
internal pure returns (uint64 result) {
require(data.length >= offset + 8,
"Insufficient data for uint64");
result = uint64(uint8(data[offset]))
| (uint64(uint8(data[offset + 1])) << 8)
| (uint64(uint8(data[offset + 2])) << 16)
| (uint64(uint8(data[offset + 3])) << 24)
| (uint64(uint8(data[offset + 4])) << 32)
| (uint64(uint8(data[offset + 5])) << 40)
| (uint64(uint8(data[offset + 6])) << 48)
| (uint64(uint8(data[offset + 7])) << 56);
}
function decodeUint64LECalldata(bytes calldata data, uint256 offset)
internal pure returns (uint64) {
require(data.length >= offset + 8,
"Insufficient data for uint64");
return uint64(uint8(data[offset]))
| (uint64(uint8(data[offset + 1])) << 8)
| (uint64(uint8(data[offset + 2])) << 16)
| (uint64(uint8(data[offset + 3])) << 24)
| (uint64(uint8(data[offset + 4])) << 32)
| (uint64(uint8(data[offset + 5])) << 40)
| (uint64(uint8(data[offset + 6])) << 48)
| (uint64(uint8(data[offset + 7])) << 56);
}
// ============================================================================
// ENCODING FUNCTIONS - Ethereum → Better call Sol...ana
// ============================================================================
/**
* @notice Encode final ruling message with multi-winner support
* @dev Message format (variable length):
* [0] = FINAL_RULING
* [1-8] = marketId (LE)
* [9] = numWinners (0 = force majeure, 1+ = winners)
* For each winner:
* [offset] = winningSide (1 byte)
*
* INTERPRETATION:
* - Empty winners (length=0) = Force majeure (market cancelled)
* - Otherwise = Normal resolution, Solana calculates equal splits
*/
function encodeFinalRuling(uint64 marketId,
uint8[] memory winningSides) internal pure returns (bytes memory) {
if (marketId == 0) revert InvalidMarketId();
bytes memory message = abi.encodePacked(FINAL_RULING,
encodeUint64LE(marketId), uint8(winningSides.length));
for (uint i = 0; i < winningSides.length; i++) {
message = abi.encodePacked(message, winningSides[i]);
}
return message;
}
/**
* @notice Decode resolution request from Solana
* @dev Message format (52 bytes):
* [0] = RESOLUTION_REQUEST (5)
* [1-8] = marketId (uint64, little-endian)
* [9] = numSides (uint8)
* [10] = numWinners (uint8)
* [11] = requiresUnanimous (0 or 1)
* [12-19] = appealCost (uint64, little-endian)
* [20-51] = requester (32 bytes, Solana pubkey)
*/
function decodeResolutionRequest(bytes calldata message) internal pure
returns (ResolutionRequestData memory data) {
uint256 offset = 1;
data.marketId = decodeUint64LECalldata(message, offset);
offset += 8;
data.numSides = uint8(message[offset]);
offset += 1;
data.numWinners = uint8(message[offset]);
offset += 1;
data.requiresUnanimous = uint8(message[offset]) == 1;
offset += 1;
data.appealCost = uint256(decodeUint64LECalldata(message, offset));
offset += 8;
data.requester = bytes32(message[offset:offset+32]);
}
/**
* @notice Decode jury compensation from Solana
* @dev Format (17 bytes):
* [0] = JURY_COMPENSATION (7)
* [1-8] = marketId (LE)
* [9-16] = amount (LE, in Solana decimals - 6)
*/
function decodeJuryCompensation(bytes memory data)
internal pure returns (uint64 marketId, uint64 amount) {
if (data.length < 17) revert InvalidMessageLength();
if (uint8(data[0]) != JURY_COMPENSATION) revert InvalidMessageType();
marketId = decodeUint64LE(data, 1);
amount = decodeUint64LE(data, 9);
if (marketId == 0) revert InvalidMarketId();
if (amount == 0) revert InvalidAmount();
require(amount <= 1_000_000 * 1e6,
"Compensation exceeds maximum");
}
function getMessageType(bytes memory data) internal pure returns (uint8) {
if (data.length == 0) revert InvalidMessageLength();
return uint8(data[0]);
}
function toEthereumAmount(uint64 solanaAmount) internal pure returns (uint256) {
return uint256(solanaAmount) * 1e12;
}
function isForceMajeure(uint8[] memory verdict) internal pure returns (bool) {
return verdict.length == 0;
}
}// SPDX-License-Identifier: MIT
pragma solidity >=0.6.2;
import "./IERC20.sol";
/// @dev Interface of the ERC4626 "Tokenized Vault Standard", as defined in
/// https://eips.ethereum.org/EIPS/eip-4626
interface IERC4626 is IERC20 {
event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares);
event Withdraw(
address indexed sender, address indexed receiver, address indexed owner, uint256 assets, uint256 shares
);
/// @notice Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing.
/// @dev
/// - MUST be an ERC-20 token contract.
/// - MUST NOT revert.
function asset() external view returns (address assetTokenAddress);
/// @notice Returns the total amount of the underlying asset that is “managed” by Vault.
/// @dev
/// - SHOULD include any compounding that occurs from yield.
/// - MUST be inclusive of any fees that are charged against assets in the Vault.
/// - MUST NOT revert.
function totalAssets() external view returns (uint256 totalManagedAssets);
/// @notice Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal
/// scenario where all the conditions are met.
/// @dev
/// - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
/// - MUST NOT show any variations depending on the caller.
/// - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
/// - MUST NOT revert.
///
/// NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
/// “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
/// from.
function convertToShares(uint256 assets) external view returns (uint256 shares);
/// @notice Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal
/// scenario where all the conditions are met.
/// @dev
/// - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
/// - MUST NOT show any variations depending on the caller.
/// - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
/// - MUST NOT revert.
///
/// NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
/// “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
/// from.
function convertToAssets(uint256 shares) external view returns (uint256 assets);
/// @notice Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver,
/// through a deposit call.
/// @dev
/// - MUST return a limited value if receiver is subject to some deposit limit.
/// - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited.
/// - MUST NOT revert.
function maxDeposit(address receiver) external view returns (uint256 maxAssets);
/// @notice Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given
/// current on-chain conditions.
/// @dev
/// - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit
/// call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called
/// in the same transaction.
/// - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the
/// deposit would be accepted, regardless if the user has enough tokens approved, etc.
/// - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
/// - MUST NOT revert.
///
/// NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in
/// share price or some other type of condition, meaning the depositor will lose assets by depositing.
function previewDeposit(uint256 assets) external view returns (uint256 shares);
/// @notice Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens.
/// @dev
/// - MUST emit the Deposit event.
/// - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
/// deposit execution, and are accounted for during deposit.
/// - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not
/// approving enough underlying tokens to the Vault contract, etc).
///
/// NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.
function deposit(uint256 assets, address receiver) external returns (uint256 shares);
/// @notice Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call.
/// @dev
/// - MUST return a limited value if receiver is subject to some mint limit.
/// - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted.
/// - MUST NOT revert.
function maxMint(address receiver) external view returns (uint256 maxShares);
/// @notice Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given
/// current on-chain conditions.
/// @dev
/// - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call
/// in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the
/// same transaction.
/// - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint
/// would be accepted, regardless if the user has enough tokens approved, etc.
/// - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
/// - MUST NOT revert.
///
/// NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in
/// share price or some other type of condition, meaning the depositor will lose assets by minting.
function previewMint(uint256 shares) external view returns (uint256 assets);
/// @notice Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens.
/// @dev
/// - MUST emit the Deposit event.
/// - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint
/// execution, and are accounted for during mint.
/// - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not
/// approving enough underlying tokens to the Vault contract, etc).
///
/// NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.
function mint(uint256 shares, address receiver) external returns (uint256 assets);
/// @notice Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the
/// Vault, through a withdrawal call.
/// @dev
/// - MUST return a limited value if owner is subject to some withdrawal limit or timelock.
/// - MUST NOT revert.
function maxWithdraw(address owner) external view returns (uint256 maxAssets);
/// @notice Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block,
/// given current on-chain conditions.
/// @dev
/// - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw
/// call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if
/// called
/// in the same transaction.
/// - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though
/// the withdrawal would be accepted, regardless if the user has enough shares, etc.
/// - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
/// - MUST NOT revert.
///
/// NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in
/// share price or some other type of condition, meaning the depositor will lose assets by depositing.
function previewWithdraw(uint256 assets) external view returns (uint256 shares);
/// @notice Burns shares from owner and sends exactly assets of underlying tokens to receiver.
/// @dev
/// - MUST emit the Withdraw event.
/// - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
/// withdraw execution, and are accounted for during withdrawal.
/// - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner
/// not having enough shares, etc).
///
/// Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed.
/// Those methods should be performed separately.
function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares);
/// @notice Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault,
/// through a redeem call.
/// @dev
/// - MUST return a limited value if owner is subject to some withdrawal limit or timelock.
/// - MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock.
/// - MUST NOT revert.
function maxRedeem(address owner) external view returns (uint256 maxShares);
/// @notice Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block,
/// given current on-chain conditions.
/// @dev
/// - MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call
/// in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the
/// same transaction.
/// - MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the
/// redemption would be accepted, regardless if the user has enough shares, etc.
/// - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
/// - MUST NOT revert.
///
/// NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in
/// share price or some other type of condition, meaning the depositor will lose assets by redeeming.
function previewRedeem(uint256 shares) external view returns (uint256 assets);
/// @notice Burns exactly shares from owner and sends assets of underlying tokens to receiver.
/// @dev
/// - MUST emit the Withdraw event.
/// - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
/// redeem execution, and are accounted for during redeem.
/// - MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner
/// not having enough shares, etc).
///
/// NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed.
/// Those methods should be performed separately.
function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @title Contains 512-bit math functions
/// @notice Facilitates multiplication and division that can have overflow of an intermediate value without any loss of precision
/// @dev Handles "phantom overflow" i.e., allows multiplication and division where an intermediate value overflows 256 bits
library FullMath {
/// @notice Calculates floor(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
/// @param a The multiplicand
/// @param b The multiplier
/// @param denominator The divisor
/// @return result The 256-bit result
/// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv
function mulDiv(uint256 a, uint256 b, uint256 denominator) internal pure returns (uint256 result) {
unchecked {
// 512-bit multiply [prod1 prod0] = a * b
// 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 = prod1 * 2**256 + prod0
uint256 prod0 = a * b; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly ("memory-safe") {
let mm := mulmod(a, b, not(0))
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
// Make sure the result is less than 2**256.
// Also prevents denominator == 0
require(denominator > prod1);
// Handle non-overflow cases, 256 by 256 division
if (prod1 == 0) {
assembly ("memory-safe") {
result := div(prod0, denominator)
}
return result;
}
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from [prod1 prod0]
// Compute remainder using mulmod
uint256 remainder;
assembly ("memory-safe") {
remainder := mulmod(a, b, denominator)
}
// Subtract 256 bit number from 512 bit number
assembly ("memory-safe") {
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
// Factor powers of two out of denominator
// Compute largest power of two divisor of denominator.
// Always >= 1.
uint256 twos = (0 - denominator) & denominator;
// Divide denominator by power of two
assembly ("memory-safe") {
denominator := div(denominator, twos)
}
// Divide [prod1 prod0] by the factors of two
assembly ("memory-safe") {
prod0 := div(prod0, twos)
}
// Shift in bits from prod1 into prod0. For this we need
// to flip `twos` such that it is 2**256 / twos.
// If twos is zero, then it becomes one
assembly ("memory-safe") {
twos := add(div(sub(0, twos), twos), 1)
}
prod0 |= prod1 * twos;
// Invert denominator mod 2**256
// Now that denominator is an odd number, it has an inverse
// modulo 2**256 such that denominator * inv = 1 mod 2**256.
// Compute the inverse by starting with a seed that is correct
// correct for four bits. That is, denominator * inv = 1 mod 2**4
uint256 inv = (3 * denominator) ^ 2;
// 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 *= 2 - denominator * inv; // inverse mod 2**8
inv *= 2 - denominator * inv; // inverse mod 2**16
inv *= 2 - denominator * inv; // inverse mod 2**32
inv *= 2 - denominator * inv; // inverse mod 2**64
inv *= 2 - denominator * inv; // inverse mod 2**128
inv *= 2 - denominator * inv; // inverse mod 2**256
// Because the division is now exact we can divide by multiplying
// with the modular inverse of denominator. This will give us the
// correct result modulo 2**256. Since the preconditions guarantee
// that the outcome is less than 2**256, this is the final result.
// We don't need to compute the high bits of the result and prod1
// is no longer required.
result = prod0 * inv;
return result;
}
}
/// @notice Calculates ceil(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
/// @param a The multiplicand
/// @param b The multiplier
/// @param denominator The divisor
/// @return result The 256-bit result
function mulDivRoundingUp(uint256 a, uint256 b, uint256 denominator) internal pure returns (uint256 result) {
unchecked {
result = mulDiv(a, b, denominator);
if (mulmod(a, b, denominator) != 0) {
require(++result > 0);
}
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import { MessagingReceipt, MessagingFee } from "../OAppSender.sol";
/**
* @dev Struct representing token parameters for the OFT send() operation.
*/
struct SendParam {
uint32 dstEid; // Destination endpoint ID.
bytes32 to; // Recipient address.
uint256 amountLD; // Amount to send in local decimals.
uint256 minAmountLD; // Minimum amount to send in local decimals.
bytes extraOptions; // Additional options supplied by the caller to be used in the LayerZero message.
bytes composeMsg; // The composed message for the send() operation.
bytes oftCmd; // The OFT command to be executed, unused in default OFT implementations.
}
/**
* @dev Struct representing OFT limit information.
* @dev These amounts can change dynamically and are up the the specific oft implementation.
*/
struct OFTLimit {
uint256 minAmountLD; // Minimum amount in local decimals that can be sent to the recipient.
uint256 maxAmountLD; // Maximum amount in local decimals that can be sent to the recipient.
}
/**
* @dev Struct representing OFT receipt information.
*/
struct OFTReceipt {
uint256 amountSentLD; // Amount of tokens ACTUALLY debited from the sender in local decimals.
// @dev In non-default implementations, the amountReceivedLD COULD differ from this value.
uint256 amountReceivedLD; // Amount of tokens to be received on the remote side.
}
/**
* @dev Struct representing OFT fee details.
* @dev Future proof mechanism to provide a standardized way to communicate fees to things like a UI.
*/
struct OFTFeeDetail {
int256 feeAmountLD; // Amount of the fee in local decimals.
string description; // Description of the fee.
}
/**
* @title IOFT
* @dev Interface for the OftChain (OFT) token.
* @dev Does not inherit ERC20 to accommodate usage by OFTAdapter as well.
* @dev This specific interface ID is '0x02e49c2c'.
*/
interface IOFT {
// Custom error messages
error InvalidLocalDecimals();
error SlippageExceeded(uint256 amountLD, uint256 minAmountLD);
// Events
event OFTSent(
bytes32 indexed guid, // GUID of the OFT message.
uint32 dstEid, // Destination Endpoint ID.
address indexed fromAddress, // Address of the sender on the src chain.
uint256 amountSentLD, // Amount of tokens sent in local decimals.
uint256 amountReceivedLD // Amount of tokens received in local decimals.
);
event OFTReceived(
bytes32 indexed guid, // GUID of the OFT message.
uint32 srcEid, // Source Endpoint ID.
address indexed toAddress, // Address of the recipient on the dst chain.
uint256 amountReceivedLD // Amount of tokens received in local decimals.
);
/**
* @notice Retrieves interfaceID and the version of the OFT.
* @return interfaceId The interface ID.
* @return version The version.
*
* @dev interfaceId: This specific interface ID is '0x02e49c2c'.
* @dev version: Indicates a cross-chain compatible msg encoding with other OFTs.
* @dev If a new feature is added to the OFT cross-chain msg encoding, the version will be incremented.
* ie. localOFT version(x,1) CAN send messages to remoteOFT version(x,1)
*/
function oftVersion() external view returns (bytes4 interfaceId, uint64 version);
/**
* @notice Retrieves the address of the token associated with the OFT.
* @return token The address of the ERC20 token implementation.
*/
function token() external view returns (address);
/**
* @notice Indicates whether the OFT contract requires approval of the 'token()' to send.
* @return requiresApproval Needs approval of the underlying token implementation.
*
* @dev Allows things like wallet implementers to determine integration requirements,
* without understanding the underlying token implementation.
*/
function approvalRequired() external view returns (bool);
/**
* @notice Retrieves the shared decimals of the OFT.
* @return sharedDecimals The shared decimals of the OFT.
*/
function sharedDecimals() external view returns (uint8);
/**
* @notice Provides a quote for OFT-related operations.
* @param _sendParam The parameters for the send operation.
* @return limit The OFT limit information.
* @return oftFeeDetails The details of OFT fees.
* @return receipt The OFT receipt information.
*/
function quoteOFT(
SendParam calldata _sendParam
) external view returns (OFTLimit memory, OFTFeeDetail[] memory oftFeeDetails, OFTReceipt memory);
/**
* @notice Provides a quote for the send() operation.
* @param _sendParam The parameters for the send() operation.
* @param _payInLzToken Flag indicating whether the caller is paying in the LZ token.
* @return fee The calculated LayerZero messaging fee from the send() operation.
*
* @dev MessagingFee: LayerZero msg fee
* - nativeFee: The native fee.
* - lzTokenFee: The lzToken fee.
*/
function quoteSend(SendParam calldata _sendParam, bool _payInLzToken) external view returns (MessagingFee memory);
/**
* @notice Executes the send() operation.
* @param _sendParam The parameters for the send operation.
* @param _fee The fee information supplied by the caller.
* - nativeFee: The native fee.
* - lzTokenFee: The lzToken fee.
* @param _refundAddress The address to receive any excess funds from fees etc. on the src.
* @return receipt The LayerZero messaging receipt from the send() operation.
* @return oftReceipt The OFT receipt information.
*
* @dev MessagingReceipt: LayerZero msg receipt
* - guid: The unique identifier for the sent message.
* - nonce: The nonce of the sent message.
* - fee: The LayerZero fee incurred for the message.
*/
function send(
SendParam calldata _sendParam,
MessagingFee calldata _fee,
address _refundAddress
) external payable returns (MessagingReceipt memory, OFTReceipt memory);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
library OFTMsgCodec {
// Offset constants for encoding and decoding OFT messages
uint8 private constant SEND_TO_OFFSET = 32;
uint8 private constant SEND_AMOUNT_SD_OFFSET = 40;
/**
* @dev Encodes an OFT LayerZero message.
* @param _sendTo The recipient address.
* @param _amountShared The amount in shared decimals.
* @param _composeMsg The composed message.
* @return _msg The encoded message.
* @return hasCompose A boolean indicating whether the message has a composed payload.
*/
function encode(
bytes32 _sendTo,
uint64 _amountShared,
bytes memory _composeMsg
) internal view returns (bytes memory _msg, bool hasCompose) {
hasCompose = _composeMsg.length > 0;
// @dev Remote chains will want to know the composed function caller ie. msg.sender on the src.
_msg = hasCompose
? abi.encodePacked(_sendTo, _amountShared, addressToBytes32(msg.sender), _composeMsg)
: abi.encodePacked(_sendTo, _amountShared);
}
/**
* @dev Checks if the OFT message is composed.
* @param _msg The OFT message.
* @return A boolean indicating whether the message is composed.
*/
function isComposed(bytes calldata _msg) internal pure returns (bool) {
return _msg.length > SEND_AMOUNT_SD_OFFSET;
}
/**
* @dev Retrieves the recipient address from the OFT message.
* @param _msg The OFT message.
* @return The recipient address.
*/
function sendTo(bytes calldata _msg) internal pure returns (bytes32) {
return bytes32(_msg[:SEND_TO_OFFSET]);
}
/**
* @dev Retrieves the amount in shared decimals from the OFT message.
* @param _msg The OFT message.
* @return The amount in shared decimals.
*/
function amountSD(bytes calldata _msg) internal pure returns (uint64) {
return uint64(bytes8(_msg[SEND_TO_OFFSET:SEND_AMOUNT_SD_OFFSET]));
}
/**
* @dev Retrieves the composed message from the OFT message.
* @param _msg The OFT message.
* @return The composed message.
*/
function composeMsg(bytes calldata _msg) internal pure returns (bytes memory) {
return _msg[SEND_AMOUNT_SD_OFFSET:];
}
/**
* @dev Converts an address to bytes32.
* @param _addr The address to convert.
* @return The bytes32 representation of the address.
*/
function addressToBytes32(address _addr) internal pure returns (bytes32) {
return bytes32(uint256(uint160(_addr)));
}
/**
* @dev Converts bytes32 to an address.
* @param _b The bytes32 value to convert.
* @return The address representation of bytes32.
*/
function bytes32ToAddress(bytes32 _b) internal pure returns (address) {
return address(uint160(uint256(_b)));
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/Math.sol)
pragma solidity ^0.8.20;
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library Math {
/**
* @dev Muldiv operation overflow.
*/
error MathOverflowedMulDiv();
enum Rounding {
Floor, // Toward negative infinity
Ceil, // Toward positive infinity
Trunc, // Toward zero
Expand // Away from zero
}
/**
* @dev Returns the addition of two unsigned integers, with an overflow flag.
*/
function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
uint256 c = a + b;
if (c < a) return (false, 0);
return (true, c);
}
}
/**
* @dev Returns the subtraction of two unsigned integers, with an overflow flag.
*/
function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b > a) return (false, 0);
return (true, a - b);
}
}
/**
* @dev Returns the multiplication of two unsigned integers, with an overflow flag.
*/
function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
if (a == 0) return (true, 0);
uint256 c = a * b;
if (c / a != b) return (false, 0);
return (true, c);
}
}
/**
* @dev Returns the division of two unsigned integers, with a division by zero flag.
*/
function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b == 0) return (false, 0);
return (true, a / b);
}
}
/**
* @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
*/
function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b == 0) return (false, 0);
return (true, a % b);
}
}
/**
* @dev Returns the largest of two numbers.
*/
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two numbers. The result is rounded towards
* zero.
*/
function average(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b) / 2 can overflow.
return (a & b) + (a ^ b) / 2;
}
/**
* @dev Returns the ceiling of the division of two numbers.
*
* This differs from standard division with `/` in that it rounds towards infinity instead
* of rounding towards zero.
*/
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
if (b == 0) {
// Guarantee the same behavior as in a regular Solidity division.
return a / b;
}
// (a + b - 1) / b can overflow on addition, so we distribute.
return a == 0 ? 0 : (a - 1) / b + 1;
}
/**
* @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or
* denominator == 0.
* @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by
* Uniswap Labs also under MIT license.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
unchecked {
// 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
// use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
// variables such that product = prod1 * 2^256 + prod0.
uint256 prod0 = x * y; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly {
let mm := mulmod(x, y, not(0))
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
// Handle non-overflow cases, 256 by 256 division.
if (prod1 == 0) {
// Solidity will revert if denominator == 0, unlike the div opcode on its own.
// The surrounding unchecked block does not change this fact.
// See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
return prod0 / denominator;
}
// Make sure the result is less than 2^256. Also prevents denominator == 0.
if (denominator <= prod1) {
revert MathOverflowedMulDiv();
}
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from [prod1 prod0].
uint256 remainder;
assembly {
// Compute remainder using mulmod.
remainder := mulmod(x, y, denominator)
// Subtract 256 bit number from 512 bit number.
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
// Factor powers of two out of denominator and compute largest power of two divisor of denominator.
// Always >= 1. See https://cs.stackexchange.com/q/138556/92363.
uint256 twos = denominator & (0 - denominator);
assembly {
// Divide denominator by twos.
denominator := div(denominator, twos)
// Divide [prod1 prod0] by twos.
prod0 := div(prod0, twos)
// Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
twos := add(div(sub(0, twos), twos), 1)
}
// Shift in bits from prod1 into prod0.
prod0 |= prod1 * twos;
// Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
// that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
// four bits. That is, denominator * inv = 1 mod 2^4.
uint256 inverse = (3 * denominator) ^ 2;
// Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also
// works in modular arithmetic, doubling the correct bits in each step.
inverse *= 2 - denominator * inverse; // inverse mod 2^8
inverse *= 2 - denominator * inverse; // inverse mod 2^16
inverse *= 2 - denominator * inverse; // inverse mod 2^32
inverse *= 2 - denominator * inverse; // inverse mod 2^64
inverse *= 2 - denominator * inverse; // inverse mod 2^128
inverse *= 2 - denominator * inverse; // inverse mod 2^256
// Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
// This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
// less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
// is no longer required.
result = prod0 * inverse;
return result;
}
}
/**
* @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
uint256 result = mulDiv(x, y, denominator);
if (unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0) {
result += 1;
}
return result;
}
/**
* @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded
* towards zero.
*
* Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
*/
function sqrt(uint256 a) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
// For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
//
// We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
// `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
//
// This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
// → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
// → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
//
// Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
uint256 result = 1 << (log2(a) >> 1);
// At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
// since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
// every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
// into the expected uint128 result.
unchecked {
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
return min(result, a / result);
}
}
/**
* @notice Calculates sqrt(a), following the selected rounding direction.
*/
function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = sqrt(a);
return result + (unsignedRoundsUp(rounding) && result * result < a ? 1 : 0);
}
}
/**
* @dev Return the log in base 2 of a positive value rounded towards zero.
* Returns 0 if given 0.
*/
function log2(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 128;
}
if (value >> 64 > 0) {
value >>= 64;
result += 64;
}
if (value >> 32 > 0) {
value >>= 32;
result += 32;
}
if (value >> 16 > 0) {
value >>= 16;
result += 16;
}
if (value >> 8 > 0) {
value >>= 8;
result += 8;
}
if (value >> 4 > 0) {
value >>= 4;
result += 4;
}
if (value >> 2 > 0) {
value >>= 2;
result += 2;
}
if (value >> 1 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 2, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log2(value);
return result + (unsignedRoundsUp(rounding) && 1 << result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 10 of a positive value rounded towards zero.
* Returns 0 if given 0.
*/
function log10(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >= 10 ** 64) {
value /= 10 ** 64;
result += 64;
}
if (value >= 10 ** 32) {
value /= 10 ** 32;
result += 32;
}
if (value >= 10 ** 16) {
value /= 10 ** 16;
result += 16;
}
if (value >= 10 ** 8) {
value /= 10 ** 8;
result += 8;
}
if (value >= 10 ** 4) {
value /= 10 ** 4;
result += 4;
}
if (value >= 10 ** 2) {
value /= 10 ** 2;
result += 2;
}
if (value >= 10 ** 1) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 10, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log10(value);
return result + (unsignedRoundsUp(rounding) && 10 ** result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 256 of a positive value rounded towards zero.
* Returns 0 if given 0.
*
* Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
*/
function log256(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 16;
}
if (value >> 64 > 0) {
value >>= 64;
result += 8;
}
if (value >> 32 > 0) {
value >>= 32;
result += 4;
}
if (value >> 16 > 0) {
value >>= 16;
result += 2;
}
if (value >> 8 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 256, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log256(value);
return result + (unsignedRoundsUp(rounding) && 1 << (result << 3) < value ? 1 : 0);
}
}
/**
* @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers.
*/
function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) {
return uint8(rounding) % 2 == 1;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is set to the address provided by the deployer. This can
* later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
/**
* @dev The caller account is not authorized to perform an operation.
*/
error OwnableUnauthorizedAccount(address account);
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/
constructor(address initialOwner) {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Gas optimized reentrancy protection for smart contracts.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/ReentrancyGuard.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/security/ReentrancyGuard.sol)
abstract contract ReentrancyGuard {
uint256 private locked = 1;
modifier nonReentrant() virtual {
require(locked == 1, "REENTRANCY");
locked = 2;
_;
locked = 1;
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import { SafeERC20, IERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { MessagingParams, MessagingFee, MessagingReceipt } from "./interfaces/ILayerZeroEndpointV2.sol";
import { OAppCore } from "./OAppCore.sol";
/**
* @title OAppSender
* @dev Abstract contract implementing the OAppSender functionality for sending messages to a LayerZero endpoint.
*/
abstract contract OAppSender is OAppCore {
using SafeERC20 for IERC20;
// Custom error messages
error NotEnoughNative(uint256 msgValue);
error LzTokenUnavailable();
// @dev The version of the OAppSender implementation.
// @dev Version is bumped when changes are made to this contract.
uint64 internal constant SENDER_VERSION = 1;
/**
* @notice Retrieves the OApp version information.
* @return senderVersion The version of the OAppSender.sol contract.
* @return receiverVersion The version of the OAppReceiver.sol contract.
*
* @dev Providing 0 as the default for OAppReceiver version. Indicates that the OAppReceiver is not implemented.
* ie. this is a SEND only OApp.
* @dev If the OApp uses both OAppSender and OAppReceiver, then this needs to be override returning the correct versions
*/
function oAppVersion() public view virtual returns (uint64 senderVersion, uint64 receiverVersion) {
return (SENDER_VERSION, 0);
}
/**
* @dev Internal function to interact with the LayerZero EndpointV2.quote() for fee calculation.
* @param _dstEid The destination endpoint ID.
* @param _message The message payload.
* @param _options Additional options for the message.
* @param _payInLzToken Flag indicating whether to pay the fee in LZ tokens.
* @return fee The calculated MessagingFee for the message.
* - nativeFee: The native fee for the message.
* - lzTokenFee: The LZ token fee for the message.
*/
function _quote(
uint32 _dstEid,
bytes memory _message,
bytes memory _options,
bool _payInLzToken
) internal view virtual returns (MessagingFee memory fee) {
return
endpoint.quote(
MessagingParams(_dstEid, _getPeerOrRevert(_dstEid), _message, _options, _payInLzToken),
address(this)
);
}
/**
* @dev Internal function to interact with the LayerZero EndpointV2.send() for sending a message.
* @param _dstEid The destination endpoint ID.
* @param _message The message payload.
* @param _options Additional options for the message.
* @param _fee The calculated LayerZero fee for the message.
* - nativeFee: The native fee.
* - lzTokenFee: The lzToken fee.
* @param _refundAddress The address to receive any excess fee values sent to the endpoint.
* @return receipt The receipt for the sent message.
* - guid: The unique identifier for the sent message.
* - nonce: The nonce of the sent message.
* - fee: The LayerZero fee incurred for the message.
*/
function _lzSend(
uint32 _dstEid,
bytes memory _message,
bytes memory _options,
MessagingFee memory _fee,
address _refundAddress
) internal virtual returns (MessagingReceipt memory receipt) {
// @dev Push corresponding fees to the endpoint, any excess is sent back to the _refundAddress from the endpoint.
uint256 messageValue = _payNative(_fee.nativeFee);
if (_fee.lzTokenFee > 0) _payLzToken(_fee.lzTokenFee);
return
// solhint-disable-next-line check-send-result
endpoint.send{ value: messageValue }(
MessagingParams(_dstEid, _getPeerOrRevert(_dstEid), _message, _options, _fee.lzTokenFee > 0),
_refundAddress
);
}
/**
* @dev Internal function to pay the native fee associated with the message.
* @param _nativeFee The native fee to be paid.
* @return nativeFee The amount of native currency paid.
*
* @dev If the OApp needs to initiate MULTIPLE LayerZero messages in a single transaction,
* this will need to be overridden because msg.value would contain multiple lzFees.
* @dev Should be overridden in the event the LayerZero endpoint requires a different native currency.
* @dev Some EVMs use an ERC20 as a method for paying transactions/gasFees.
* @dev The endpoint is EITHER/OR, ie. it will NOT support both types of native payment at a time.
*/
function _payNative(uint256 _nativeFee) internal virtual returns (uint256 nativeFee) {
if (msg.value != _nativeFee) revert NotEnoughNative(msg.value);
return _nativeFee;
}
/**
* @dev Internal function to pay the LZ token fee associated with the message.
* @param _lzTokenFee The LZ token fee to be paid.
*
* @dev If the caller is trying to pay in the specified lzToken, then the lzTokenFee is passed to the endpoint.
* @dev Any excess sent, is passed back to the specified _refundAddress in the _lzSend().
*/
function _payLzToken(uint256 _lzTokenFee) internal virtual {
// @dev Cannot cache the token because it is not immutable in the endpoint.
address lzToken = endpoint.lzToken();
if (lzToken == address(0)) revert LzTokenUnavailable();
// Pay LZ token fee by sending tokens to the endpoint.
IERC20(lzToken).safeTransferFrom(msg.sender, address(endpoint), _lzTokenFee);
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import {Rover} from "./Rover.sol";
import {Types} from "./imports/Types.sol";
import {stdMath} from "forge-std/StdMath.sol";
import {IERC20} from "forge-std/interfaces/IERC20.sol";
import {IPool} from "aave-v3/interfaces/IPool.sol";
import {IUiPoolDataProviderV3 as IUiData,
IPoolAddressesProvider as PoolAddr} from "./imports/IUiPoolDataProviderV3.sol";
import {IV3SwapRouter as ISwapRouter} from "./imports/v3/IV3SwapRouter.sol";
import {BasketLib, AAVEv4, IHub, IAaveOracle} from "./imports/BasketLib.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {IUniswapV3Pool} from "./imports/v3/IUniswapV3Pool.sol";
import {FullMath} from "v4-core/src/libraries/FullMath.sol";
import {WETH as WETH9} from "solmate/src/tokens/WETH.sol";
interface IAux { // collects yield amplified...
function deposit(address from, address token,
uint amount) external returns (uint usd);
function getTWAP(uint32 secondsAgo)
external view returns (uint);
}
// Minimal interface to get pool data...
// provider address — used only in setup()...
// PoolAddressesProvider.getPoolDataProvider()
// is stable across v3.x versions...
interface IAaveProtocolDataProvider {
function getReserveTokensAddresses(address asset)
external view returns (address aTokenAddress,
address stableDebtTokenAddress,
address variableDebtTokenAddress);
}
/// @notice Handles AAVE APR/APY
/// @dev Integrates V3 for swaps
contract Amp is Ownable {
bool public token1isWETH;
uint constant WAD = 1e18;
uint constant RAY = 1e27;
uint USDCsharesSnapshot;
uint wethSharesSnapshot;
IERC20 USDC; WETH9 weth;
IUniswapV3Pool v3Pool;
Rover V3; IPool AAVE;
ISwapRouter v3Router;
AAVEv4 internal SPOKE;
IHub internal HUB; address public AUX;
IERC20 aWETHToken; IERC20 aUSDCToken;
IERC20 vWETHToken; IERC20 vUSDCToken;
mapping(address => Types.viaAAVE) public pledgesOneForZero;
mapping(address => Types.viaAAVE) public pledgesZeroForOne;
mapping(address => uint) totalBorrowed;
PoolAddr ADDR; IUiData DATA;
modifier onlyUs {
require(msg.sender == address(this)
|| msg.sender == address(V3)
|| msg.sender == AUX, "403"); _;
}
constructor(address _aave, address _data,
address _addr) Ownable(msg.sender) {
DATA = IUiData(_data);
ADDR = PoolAddr(_addr);
AAVE = IPool(_aave);
}
event LeveragedPositionOpened(
address indexed user,
bool indexed isLong,
uint supplied,
uint borrowed,
uint buffer,
int256 entryPrice,
uint breakeven,
uint blockNumber
);
event PositionUnwound(
address indexed user,
bool indexed isLong,
int256 exitPrice,
int256 priceDelta,
uint blockNumber
);
function setup(address payable _rover,
address _aux) external onlyOwner {
require(address(Rover(_rover).AMP())
== address(this)); AUX = _aux;
renounceOwnership();
V3 = Rover(_rover);
USDC = IERC20(V3.USDC());
weth = WETH9(payable(
address(V3.weth())));
v3Pool = IUniswapV3Pool(V3.POOL());
v3Router = ISwapRouter(V3.ROUTER());
USDC.approve(AUX, type(uint).max);
USDC.approve(address(AAVE), type(uint).max);
USDC.approve(address(v3Router), type(uint).max);
token1isWETH = v3Pool.token0() == address(USDC);
weth.approve(address(v3Router), type(uint).max);
weth.approve(address(AAVE), type(uint).max);
{ address pdp = PoolAddr(address(ADDR)).getPoolDataProvider();
(address aw,, address vw) = IAaveProtocolDataProvider(pdp)
.getReserveTokensAddresses(address(weth));
(address au,, address vu) = IAaveProtocolDataProvider(pdp)
.getReserveTokensAddresses(address(USDC));
aWETHToken = IERC20(aw); aUSDCToken = IERC20(au);
vWETHToken = IERC20(vw); vUSDCToken = IERC20(vu);
}
}
function setV4(address _hub,
address _spoke) external {
require(msg.sender == AUX
&& address(SPOKE) == address(0));
(,, uint wethCollat,
uint usdcCollat) = _readV3Positions();
if (wethCollat > 0) AAVE.withdraw(address(weth),
wethCollat, address(this));
if (usdcCollat > 0) AAVE.withdraw(address(USDC),
usdcCollat, address(this));
// Invariant: migration must not leave debt uncovered.
{ uint tb = totalBorrowed[address(weth)];
if (tb > 0) require(
aWETHToken.balanceOf(address(this)) >= tb, "HF:weth"); }
{ uint tb = totalBorrowed[address(USDC)];
if (tb > 0) require(
aUSDCToken.balanceOf(address(this)) >= tb, "HF:usdc"); }
_setupV4(_hub, _spoke);
uint wethBal = weth.balanceOf(address(this));
uint usdcBal = USDC.balanceOf(address(this));
if (wethBal > 0) SPOKE.supply(
_reserveId(address(weth)),
wethBal, address(this));
if (usdcBal > 0) SPOKE.supply(
_reserveId(address(USDC)),
usdcBal, address(this));
}
function hasOpenDebt() external view returns (bool) {
(uint wethDebt, uint usdcDebt,,) = _readV3Positions();
return wethDebt > 0 || usdcDebt > 0;
}
function _setupV4(address _hub,
address _spoke) internal {
SPOKE = AAVEv4(_spoke); HUB = IHub(_hub);
weth.approve(_hub, type(uint).max);
USDC.approve(_hub, type(uint).max);
}
/// @dev Read actual v3 debt and collateral
function _readV3Positions() internal view
returns (uint wethDebt, uint usdcDebt,
uint wethCollat, uint usdcCollat) {
(IUiData.UserReserveData[]
memory ud,) = DATA.getUserReservesData(
ADDR, address(this));
wethDebt = (ud[4].scaledVariableDebt
* AAVE.getReserveNormalizedVariableDebt(
address(weth))) / RAY;
wethCollat = aWETHToken.balanceOf(address(this));
usdcDebt = (ud[20].scaledVariableDebt
* AAVE.getReserveNormalizedVariableDebt(
address(USDC))) / RAY;
usdcCollat = aUSDCToken.balanceOf(address(this));
}
function _reserveId(address asset)
internal returns (uint) {
return SPOKE.getReserveId(address(HUB),
HUB.getAssetId(asset));
}
/// @notice leveraged long (borrow weth against USDC)
/// @dev 70% LTV, excess USDC locked as collateral
/// @param amount weth amount to deposit
function leverETH(address who, uint amount,
uint fromV4) payable external onlyUs {
uint price = IAux(AUX).getTWAP(0);
if (fromV4 > 0) {
weth.transferFrom(msg.sender,
address(this), amount);
USDC.transferFrom(msg.sender,
address(this), fromV4);
} else amount = _deposit(amount);
uint borrowing = amount * 7 / 10;
uint buffer = amount - borrowing;
uint totalValue = FullMath.mulDiv(
amount, price, WAD);
require(totalValue >= 50e18); // min $50...
// borrow full value of collateral to go long
// selling the amount borrowed for USDC and
// depositing the USDC for a future step in
// unwind which is a basketball crossover
uint usdcNeeded = totalValue / 1e12;
uint took = 0;
if (fromV4 < usdcNeeded) {
took = usdcNeeded - fromV4;
if (took > 0) {
uint got = V3.withdrawUSDC(took);
require(stdMath.delta(took, got) <= 1e6,
"withdrawUSDC");
took = got;
}
} _putUSDC(fromV4 + took);
uint aWETHBefore = aWETHToken.balanceOf(address(this));
_put(amount); // Capture actual aWETH
if (borrowing > 0) { // received (liquidityIndex floor amount-1)
uint vDebtBefore = vWETHToken.balanceOf(address(this));
if (address(SPOKE) != address(0))
SPOKE.borrow(_reserveId(address(weth)),
borrowing, address(this));
else AAVE.borrow(address(weth), borrowing,
2, 0, address(this));
uint actualBorrowed = vWETHToken.balanceOf(
address(this)) - vDebtBefore;
totalBorrowed[address(weth)] += actualBorrowed;
amount = FullMath.mulDiv(borrowing,
price, 1e12 * WAD);
amount = _buyUSDC(borrowing, price);
// ^ sell borrowed WETH to add lever
_putUSDC(amount);
Types.viaAAVE memory order = Types.viaAAVE({
breakeven: totalValue, // < "supplied" gets
// reset; need to remember original value
// in order to calculate gains eventually
supplied: took, borrowed: actualBorrowed,
buffer: aWETHToken.balanceOf(address(this)) -
aWETHBefore - actualBorrowed, price: int(price)});
if (token1isWETH) { // check for pre-existing order
require(pledgesOneForZero[who].breakeven == 0);
pledgesOneForZero[who] = order;
} else {
require(pledgesZeroForOne[who].breakeven == 0);
pledgesZeroForOne[who] = order;
}
emit LeveragedPositionOpened(
msg.sender, true, took,
borrowing, buffer, int(price),
totalValue,block.number);
}
}
/// @notice Open leveraged short position (USDC against weth)
/// @dev 70% LTV on AAVE, deposited stablecoins as collateral
/// @param amount Stablecoin amount to deposit
function leverUSD(address who, uint amount,
uint fromV4) payable external onlyUs {
uint price = IAux(AUX).getTWAP(0);
if (fromV4 > 0)
weth.transferFrom(msg.sender,
address(this), fromV4);
USDC.transferFrom(msg.sender,
address(this), amount);
uint deposited = amount;
require(deposited * 1e12 + FullMath.mulDiv(
fromV4, price, WAD) >= 50e18);
_putUSDC(amount);
uint inWETH = FullMath.mulDiv(WAD,
amount * 1e12, price);
// ^ convert USDC to 18 decimals
uint neededFromV3 = 0;
if (inWETH > fromV4)
neededFromV3 = V3.take(inWETH - fromV4);
// borrow WETH from V3, use in AAVE
// as collateral to borrow dollars
_put(fromV4 + neededFromV3); // collat
uint totalWETH = fromV4 + neededFromV3;
amount = FullMath.mulDiv(totalWETH * 7 / 10,
price, WAD * 1e12);
// borrow 70% of the WETH value in USDC
if (amount > 0) {
uint vUSDBefore = vUSDCToken.balanceOf(address(this));
if (address(SPOKE) != address(0))
SPOKE.borrow(_reserveId(address(USDC)),
amount, address(this));
else AAVE.borrow(address(USDC), amount, 2, 0,
address(this));
uint actualBorrowedUSD = vUSDCToken.balanceOf(address(this)) - vUSDBefore;
// Borrow sends exactly `amount` USDC to AMP; vUSDC minted is amount+1
// due to ceil rounding on the borrow index. Supply only what we received.
_putUSDC(amount);
totalBorrowed[address(USDC)] += actualBorrowedUSD;
Types.viaAAVE memory order = Types.viaAAVE({
breakeven: deposited * 1e12, // supplied\
// reset; need to remember original value
// in order to calculate gains eventually
supplied: neededFromV3, borrowed: actualBorrowedUSD,
buffer: 0, price: int(price) });
if (token1isWETH) { // check for pre-existing order
require(pledgesZeroForOne[who].breakeven == 0);
pledgesZeroForOne[who] = order;
}
else {
require(pledgesOneForZero[who].breakeven == 0);
pledgesOneForZero[who] = order;
}
emit LeveragedPositionOpened(
msg.sender, false, neededFromV3,
amount, 0, int(price),
deposited, block.number);
}
}
function _repayWETHGap(uint gap, uint spotPrice)
internal returns (uint spent) {
spent = FullMath.mulDiv(gap * 102 / 100,
uint(spotPrice), WAD * 1e12);
if (spent > aUSDCToken.balanceOf(address(this))) return 0;
uint got = _getUSDC(spent);
if (_buy(got, spotPrice) >= gap)
AAVE.repay(address(weth), gap, 2, address(this));
else spent = 0;
}
function _getUSDC(uint howMuch) internal
returns (uint withdrawn) {
uint amount = Math.min(
USDCsharesSnapshot, howMuch);
if (amount == 0) return 0;
if (address(SPOKE) != address(0))
(, withdrawn) = SPOKE.withdraw(
_reserveId(address(USDC)),
amount, address(this));
else withdrawn = AAVE.withdraw(address(USDC),
amount, address(this));
}
function _get(uint howMuch) internal
returns (uint withdrawn) {
uint amount = Math.min(
wethSharesSnapshot, howMuch);
if (amount == 0) return 0;
if (address(SPOKE) != address(0))
(, withdrawn) = SPOKE.withdraw(
_reserveId(address(weth)),
amount, address(this));
else withdrawn = AAVE.withdraw(address(weth),
amount, address(this));
}
function _put(uint amount) internal {
if (amount == 0) return;
if (address(SPOKE) != address(0))
SPOKE.supply(_reserveId(address(weth)),
amount, address(this));
else { AAVE.supply(address(weth), amount, address(this), 0);
AAVE.setUserUseReserveAsCollateral(address(weth), true); }
}
function _putUSDC(uint amount) internal {
if (amount == 0) return;
if (address(SPOKE) != address(0))
SPOKE.supply(_reserveId(address(USDC)),
amount, address(this));
else { AAVE.supply(address(USDC), amount, address(this), 0);
AAVE.setUserUseReserveAsCollateral(address(USDC), true); }
}
function _buyUSDC(uint howMuch,
uint price) internal returns (uint) {
Types.AuxContext memory ctx;
ctx.v3Pool = address(v3Pool);
ctx.v3Router = address(v3Router);
ctx.weth = address(weth);
ctx.usdc = address(USDC);
ctx.v3Fee = V3.POOL_FEE();
return BasketLib.swapWETHtoUSDC(
ctx, howMuch, price);
}
function _buy(uint howMuch,
uint price) internal returns (uint) {
Types.AuxContext memory ctx;
ctx.v3Pool = address(v3Pool);
ctx.v3Router = address(v3Router);
ctx.weth = address(weth);
ctx.usdc = address(USDC);
ctx.v3Fee = V3.POOL_FEE();
return BasketLib.swapUSDCtoWETH(
ctx, howMuch, price);
}
function _deposit(uint amount) internal returns (uint) {
if (amount > 0) { weth.transferFrom(msg.sender,
address(this), amount);
} if (msg.value > 0) { weth.deposit{
value: msg.value}();
amount += msg.value;
} return amount;
} /// @param out token to withdraw
/// @param borrowed Amount borrowed
/// @param supplied Amount supplied
function _unwind(address repay, address out,
uint borrowed, uint supplied) internal {
if (borrowed > 0) {
if (address(SPOKE) != address(0))
SPOKE.repay(_reserveId(repay),
borrowed, address(this));
else AAVE.repay(repay, borrowed,
2, address(this));
}
if (out != address(0)) {
uint tracked = totalBorrowed[repay];
totalBorrowed[repay] = borrowed >
tracked ? 0 : tracked - borrowed;
}
if (supplied > 0 && out != address(0)) {
uint withdrawn;
if (address(SPOKE) != address(0))
(, withdrawn) = SPOKE.withdraw(
_reserveId(out), supplied,
address(this));
else withdrawn = AAVE.withdraw(out,
supplied, address(this));
require(withdrawn >= supplied - 5,
"withdraw slippage");
}
// Invariant: after every unwind, remaining collateral >= remaining debt.
// aWETH >= totalBorrowed[weth] is strictly stronger than HF > 1 (LT=0.825),
// so this check guarantees liquidation is impossible across all positions.
{ uint tb = totalBorrowed[address(weth)];
if (tb > 0) require(
aWETHToken.balanceOf(address(this)) >= tb, "HF:weth"); }
{ uint tb = totalBorrowed[address(USDC)];
if (tb > 0) require(
aUSDCToken.balanceOf(address(this)) >= tb, "HF:usdc"); }
}
/// @notice Calculate APR on AAVE positions
/// @return repay Interest owed weth borrows
/// @return repayUSDC owed on USDC borrows
function _howMuchInterest() internal
returns (uint repay, uint repayUSDC) {
if (address(SPOKE) != address(0)) {
uint id = _reserveId(address(weth));
wethSharesSnapshot = SPOKE.getUserSuppliedAssets(
id, address(this));
id = _reserveId(address(USDC));
USDCsharesSnapshot = SPOKE.getUserSuppliedAssets(
id, address(this));
AAVEv4.UserAccountData memory acct =
SPOKE.getUserAccountData(address(this));
if (acct.totalDebtValue == 0) return (0, 0);
IAaveOracle oracle = IAaveOracle(SPOKE.ORACLE());
// repay/repayUSDC reused as price, unit, prinVal
repay = oracle.getReservePrice(
_reserveId(address(weth)));
repayUSDC = oracle.getReservePrice(
_reserveId(address(USDC)));
// wethPrinVal in `id`, usdcPrinVal...
// in `acct.totalCollateralValue` (reuse)
{ uint wethUnit = 10 ** SPOKE.getReserve(
_reserveId(address(weth))).decimals;
uint usdcUnit = 10 ** SPOKE.getReserve(
_reserveId(address(USDC))).decimals;
uint wethPrinVal = repay > 0
? (totalBorrowed[address(weth)] * repay)
/ wethUnit : 0;
uint usdcPrinVal = repayUSDC > 0
? (totalBorrowed[address(USDC)] * repayUSDC)
/ usdcUnit : 0;
id = wethPrinVal + usdcPrinVal; // totalPrinVal
if (acct.totalDebtValue > id && id > 0) {
uint interest = acct.totalDebtValue - id;
wethPrinVal = (interest * wethPrinVal) / id;
usdcPrinVal = interest - wethPrinVal;
// Convert back: repay still holds wethPrice
repay = repay > 0
? (wethPrinVal * wethUnit) / repay : 0;
repayUSDC = repayUSDC > 0
? (usdcPrinVal * usdcUnit) / repayUSDC : 0;
} else { repay = 0; repayUSDC = 0; }
}
} else {
( IUiData.UserReserveData[] memory userData,
) = DATA.getUserReservesData(ADDR, address(this));
{ uint borrowIndex = AAVE.getReserveNormalizedVariableDebt(address(weth));
uint actualDebt = (userData[4].scaledVariableDebt * borrowIndex) / RAY;
wethSharesSnapshot = aWETHToken.balanceOf(address(this));
repay = actualDebt > totalBorrowed[address(weth)]
// index 0 on L1 and Base, 4 on Arbi and Poly...
? actualDebt - totalBorrowed[address(weth)] : 0;
}
{ uint borrowIndex = AAVE.getReserveNormalizedVariableDebt(address(USDC));
uint actualDebt = (userData[20].scaledVariableDebt * borrowIndex) / RAY;
// index 3 on L1, 4 on Base, 12 on Arb, 20 on Polygon
USDCsharesSnapshot = aUSDCToken.balanceOf(address(this));
repayUSDC = actualDebt > totalBorrowed[address(USDC)]
? actualDebt - totalBorrowed[address(USDC)] : 0;
}
}
}
function unwindZeroForOne(
address[] calldata whose) external {
uint spot = IAux(AUX).getTWAP(0);
int price = int(IAux(AUX).getTWAP(1800));
Types.viaAAVE memory pledge;
uint i; uint buffer;
uint pivot; uint touched;
(uint repay, uint repayUSDC) = _howMuchInterest();
while (i < 30 && i < whose.length) {
address who = whose[i];
pledge = token1isWETH ? pledgesOneForZero[who]:
pledgesZeroForOne[who];
if (pledge.price == 0) { i++; continue; }
int delta = (price - pledge.price) * 1000 / pledge.price;
if (delta <= -25 || delta >= 24) { touched += 1;
if (pledge.borrowed > 0) {
// +1e9 ensures AAVE.repay clamps to actualDebt even when
// the borrow index advanced 1 wei between open and pivot.
pivot = _get(pledge.borrowed + 1e9);
require(pivot > 0);
uint gap = pledge.borrowed > pivot
? pledge.borrowed - pivot : 0;
_unwind(address(weth), address(USDC),
pivot, pledge.supplied);
// borrow APY > supply APY: aWETH may be slightly less than
// vWETH debt. Sell minimum USDC to repay residual WETH debt.
if (gap > 0) {
uint spent = _repayWETHGap(gap, spot);
if (spent > 0) pledge.supplied -= spent;
}
// AAVE caps withdrawal to available: pledge.buffer - 1e9; no require needed
uint bufLeft = pledge.buffer > 1e9
? pledge.buffer - 1e9 : 0;
// Refresh: _unwind stripped pledge.supplied from aUSDC.
// USDC liquidityIndex rounding means actual aUSDC < open-time
// snapshot by a few units; without this _getUSDC(buffer) reverts.
USDCsharesSnapshot = aUSDCToken.balanceOf(address(this));
if (delta <= -25) {
// buy the dip - convert all USDC to WETH
buffer = FullMath.mulDiv(pledge.borrowed,
uint(pledge.price), WAD * 1e12);
pivot = _getUSDC(buffer);
require(stdMath.delta(pivot, buffer) <= 5);
buffer = pivot + pledge.supplied;
pivot = FullMath.mulDiv(WAD,
buffer * 1e12, uint(price));
buffer = _buy(buffer, spot); // buy ETH
// Repay outstanding WETH interest before buffer withdrawal
if (repay > 0) {
uint toRepay = Math.min(buffer, repay);
buffer -= toRepay; repay -= toRepay;
_unwind(address(weth), address(0), toRepay, 0);
}
buffer += _get(bufLeft); _put(buffer);
pledge.supplied = buffer;
pledge.price = price;
pledge.buffer = 0;
} else { // Price up...
// sell buffer WETH for USDC
buffer = _get(bufLeft);
require(stdMath.delta(buffer + 1e9, pledge.buffer) <= 5);
pivot = FullMath.mulDiv(buffer, uint(price), WAD * 1e12);
pivot = _buyUSDC(buffer, spot) + pledge.supplied;
pledge.buffer = pivot + FullMath.mulDiv(pledge.borrowed,
uint(pledge.price), WAD * 1e12);
pledge.supplied = 0;
_putUSDC(pivot);
} pledge.borrowed = 0;
if (token1isWETH) pledgesOneForZero[who] = pledge;
else pledgesZeroForOne[who] = pledge;
} else if (delta <= -25 && pledge.buffer > 0) {
// Second pivot down - buffer is USDC, buy WETH
buffer = _getUSDC(pledge.buffer);
require(stdMath.delta(buffer, pledge.buffer) <= 5);
pivot = FullMath.mulDiv(WAD, buffer * 1e12, uint(price));
buffer = _buy(buffer, uint(price)); // buy ETH
pledge.supplied = buffer; _put(buffer);
pledge.buffer = 0; pledge.price = price;
if (token1isWETH) pledgesOneForZero[who] = pledge;
else pledgesZeroForOne[who] = pledge;
}
else if (delta >= 25 && pledge.supplied > 0) {
// Final exit: supplied is WETH, sell for $
buffer = _get(pledge.supplied);
// Pay global
// WETH interest
if (repay > 0) {
pivot = Math.min(
buffer, repay);
buffer -= pivot;
repay -= pivot;
_unwind(address(weth),
address(0), pivot, 0);
}
pivot = FullMath.mulDiv(uint(price), buffer, 1e12 * WAD);
pivot = _buyUSDC(buffer, spot);
uint breakeven = pledge.breakeven / 1e12;
// Handle underwater positions gracefully
if (pivot <= breakeven) {
// At a loss - return whatever we have
USDC.transfer(who, pivot);
} else {
uint profit = pivot - breakeven;
if (repayUSDC > 0) {
buffer = Math.min(
profit, repayUSDC);
profit -= buffer;
repayUSDC -= buffer;
_unwind(address(USDC),
address(0), buffer, 0);
}
USDC.transfer(who, breakeven + profit / 2);
IAux(AUX).deposit(address(this),
address(USDC), profit / 2);
}
if (token1isWETH)
delete pledgesOneForZero[who];
else delete pledgesZeroForOne[who];
} emit PositionUnwound(who, true, price,
delta, block.number);
} i++;
}
}
function unwindOneForZero(
address[] calldata whose) external {
uint spot = IAux(AUX).getTWAP(0);
int price = int(IAux(AUX).getTWAP(1800));
Types.viaAAVE memory pledge;
uint i; uint buffer;
uint pivot; uint touched;
(uint repay, uint repayUSDC) = _howMuchInterest();
while (i < 30 && i < whose.length) {
address who = whose[i];
pledge = token1isWETH ? pledgesZeroForOne[who]:
pledgesOneForZero[who];
if (pledge.price == 0) { i++; continue; }
int delta = (price - pledge.price) * 1000 / pledge.price;
if (delta <= -25 || delta >= 24) { touched += 1;
if (pledge.borrowed > 0) {
pivot = _getUSDC(pledge.borrowed + 100);
require(pivot > 0);
uint gap = pledge.borrowed > pivot
? pledge.borrowed - pivot : 0;
_unwind(address(USDC), address(weth), pivot, pledge.supplied);
// borrow APY > supply APY: aUSDC may be slightly less than
// vUSDC debt. Sell minimum WETH to repay residual so debt
// is fully cleared (hasOpenDebt() returns false after unwind).
if (gap > 0) {
uint wethForGap = FullMath.mulDiv(
gap * 102 / 100, WAD * 1e12, uint(spot));
if (wethForGap <= pledge.supplied) {
uint usdcOut = _buyUSDC(wethForGap, spot);
if (usdcOut >= gap) {
AAVE.repay(address(USDC), gap, 2, address(this));
pledge.supplied -= wethForGap;
}
}
}
if (delta >= 25) { // Price up (bad for short) - sell WETH for USDC
pivot = FullMath.mulDiv(pledge.supplied, uint(price), WAD * 1e12);
pledge.supplied = _buyUSDC(pledge.supplied, spot);
_putUSDC(pledge.supplied);
} else { // Price down (good for short)
if (repayUSDC > 0) {
uint toRepay = _getUSDC(repayUSDC);
if (toRepay > 0) {
repayUSDC -= toRepay;
_unwind(address(USDC), address(0), toRepay, 0);
}
}
pledge.buffer = pledge.supplied;
_put(pledge.supplied);
pledge.supplied = 0;
} pledge.borrowed = 0;
pledge.price = price;
if (token1isWETH) pledgesZeroForOne[who] = pledge;
else pledgesOneForZero[who] = pledge;
} else if (delta <= -25 && pledge.supplied > 0) {
// Second pivot - supplied is USDC, buy WETH
pivot = _getUSDC(pledge.supplied);
require(stdMath.delta(pledge.supplied, pivot) <= 5);
pivot = FullMath.mulDiv(WAD, pledge.supplied * 1e12, uint(price));
pledge.buffer = _buy(pledge.supplied, spot);
_put(pledge.buffer);
pledge.supplied = 0;
pledge.price = price;
if (token1isWETH) pledgesZeroForOne[who] = pledge;
else pledgesOneForZero[who] = pledge;
} else if (delta >= 25 && pledge.buffer > 0) {
// Final exit - buffer is WETH, sell for USDC
buffer = _get(pledge.buffer);
// Pay down global WETH interest first
if (repay > 0) {
pivot = Math.min(buffer, repay);
buffer -= pivot; repay -= pivot;
_unwind(address(weth),
address(0), pivot, 0);
}
pivot = FullMath.mulDiv(uint(price),
buffer, 1e12 * WAD);
pivot = _buyUSDC(buffer, spot);
// longs and shorts store breakeven in 18 dec
uint breakeven = pledge.breakeven / 1e12;
// Handle underwater positions
if (pivot <= breakeven) {
USDC.transfer(who, pivot);
} else {
uint profit = pivot - breakeven;
if (repayUSDC > 0) {
buffer = Math.min(
profit, repayUSDC);
profit -= buffer;
repayUSDC -= buffer;
_unwind(address(USDC),
address(0), buffer, 0);
}
USDC.transfer(who, breakeven + profit / 2);
IAux(AUX).deposit(address(this),
address(USDC), profit / 2);
}
if (token1isWETH)
delete pledgesZeroForOne[who];
else delete pledgesOneForZero[who];
} emit PositionUnwound(who, false, price,
delta, block.number);
} i++;
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import {LiquidityAmounts} from "v4-periphery/src/libraries/LiquidityAmounts.sol";
import {FixedPointMathLib} from "solmate/src/utils/FixedPointMathLib.sol";
import {ReentrancyGuard} from "solmate/src/utils/ReentrancyGuard.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {TickMath} from "v4-core/src/libraries/TickMath.sol";
import {FullMath} from "v4-core/src/libraries/FullMath.sol";
import {WETH as WETH9} from "solmate/src/tokens/WETH.sol";
import {IERC20} from "forge-std/interfaces/IERC20.sol";
import {BasketLib} from "./imports/BasketLib.sol";
import {stdMath} from "forge-std/StdMath.sol";
import {Types} from "./imports/Types.sol";
import {VogueCore} from "./VogueCore.sol";
import {Basket} from "./Basket.sol";
import {Aux} from "./Aux.sol";
contract Vogue is
Ownable, ReentrancyGuard {
uint constant RAY = 1e27;
uint constant WAD = 1e18;
VogueCore V4; WETH9 WETH;
bool public token1isETH;
// range = between ticks
int24 public UPPER_TICK;
int24 public LOWER_TICK;
uint internal lastVogueETH; // for yield delta
uint public LAST_REPACK;
// ^ timestamp allows us
// to measure APY% for
uint public USD_FEES;
uint public ETH_FEES;
Basket QUID; Aux AUX;
uint public YIELD;
// V4.POOLED_ETH() = principal + ALL compounded fees (even unclaimed)
// totalShares = sum of all LP.pooled_eth = principal + claimed fees
// that got added to positions; gap represents fees that compounded
// into the pool but haven't been attributed to any depositor yet...
// swap fees accumulating in the V4 pool aren't added to `POOLED_ETH`
// until collection via _modifyLiquidity during repack or withdrawal
uint public totalShares;
mapping(address => Types.Deposit) public autoManaged;
mapping(uint => Types.SelfManaged) public selfManaged;
// ^ key is tokenId of ID++ for that position
uint internal ID;
// ^ always grows
mapping(address => uint[]) public positions;
// ^ allows several selfManaged positions...
constructor()
Ownable(msg.sender) {
} fallback() external payable {}
modifier onlyUs {
require(msg.sender == address(AUX)
|| msg.sender == address(V4)
|| msg.sender == address(this), "403"); _;
}
function setup(address _quid,
address _aux, address _core) external {
require(address(AUX) == address(0), "!");
AUX = Aux(payable(_aux)); V4 = VogueCore(_core);
QUID = Basket(_quid); renounceOwnership();
require(QUID.V4() == address(this), "?");
WETH = WETH9(payable(address(AUX.WETH())));
WETH.approve(address(AUX), type(uint).max);
(uint160 sqrtPriceX96,,) = V4.poolStats(0, 0);
token1isETH = V4.token1isETH();
(LOWER_TICK,, UPPER_TICK,) = _updateTicks(
sqrtPriceX96, 200);
}
function _outOfRangeTicks(uint160 currentSqrtPrice,
int24 width, int24 range, int24 distance) internal
returns (int24 newLowerTick, int24 newUpperTick) {
int24 targetTick = TickMath.getTickAtSqrtPrice(
currentSqrtPrice) - distance;
if (distance < 0) { // above the current price
newLowerTick = _alignTick(targetTick, width);
newUpperTick = _alignTick(targetTick + range, width);
} else {
newUpperTick = _alignTick(targetTick, width);
newLowerTick = _alignTick(targetTick - range, width);
}
}
/// @notice Create a single-sided liquidity position outside the current price range
/// @dev Automatically adjusts for token ordering to ensure valid positions
/// @param amount Amount of tokens to deposit (0 if sending ETH as msg.value)
/// @param token Token address (address(0) for ETH, or stablecoin address for USD)
/// @param distance Distance from current price in ticks
/// positive = subtract (below), negative = add (above)
/// @param range Width of the position in ticks
/// @return next The ID of the newly created position
function outOfRange(uint amount, address token,
int24 distance, int24 range) public nonReentrant
payable returns (uint next) { int24 width = int24(10);
require(range >= 100 && range <= 1000 && range % 50 == 0,
"Range must be 100-1000 in increments of 50");
require(distance % 100 == 0 && distance != 0 &&
distance >= -5000 && distance <= 5000,
"must be -5000 to 5000 in increments of 100");
(uint160 currentSqrtPrice,
int24 currentLowerTick,
int24 currentUpperTick,) = _repack();
if (!token1isETH) distance = -distance;
(int24 newLowerTick,
int24 newUpperTick) = _outOfRangeTicks(
currentSqrtPrice, width, range, distance);
uint128 liquidity;
if (token == address(0)) { amount = _depositETH(
msg.sender, amount);
if (token1isETH) {
require(newLowerTick > currentUpperTick);
liquidity = LiquidityAmounts.getLiquidityForAmount1(
TickMath.getSqrtPriceAtTick(newLowerTick),
TickMath.getSqrtPriceAtTick(newUpperTick), amount);
} else {
require(newUpperTick < currentLowerTick);
liquidity = LiquidityAmounts.getLiquidityForAmount0(
TickMath.getSqrtPriceAtTick(newLowerTick),
TickMath.getSqrtPriceAtTick(newUpperTick), amount);
}
} else { amount = AUX.deposit(
msg.sender, token, amount);
// Normalize to 6 decimals for USD side
uint8 decimals = IERC20(token).decimals();
if (decimals != 6) amount = decimals > 6 ?
amount / 10 ** (decimals - 6):
amount * 10 ** (6 - decimals);
if (token1isETH) {
require(newUpperTick < currentLowerTick);
// Above current = buy ETH with USD (provide $)
// Below current = sell ETH for USD (provide ETH)
liquidity = LiquidityAmounts.getLiquidityForAmount0(
TickMath.getSqrtPriceAtTick(newLowerTick),
TickMath.getSqrtPriceAtTick(newUpperTick), amount);
} else {
require(newLowerTick > currentUpperTick);
liquidity = LiquidityAmounts.getLiquidityForAmount1(
TickMath.getSqrtPriceAtTick(newLowerTick),
TickMath.getSqrtPriceAtTick(newUpperTick), amount);
}
} Types.SelfManaged memory newPosition = Types.SelfManaged({
created: block.number, owner: msg.sender,
lower: newLowerTick, upper: newUpperTick,
liq: int(uint(liquidity)) }); next = ++ID;
require(liquidity > 0, "dust");
selfManaged[next] = newPosition;
positions[msg.sender].push(next);
V4.outOfRange(msg.sender, int(uint(liquidity)),
newLowerTick, newUpperTick, address(0));
}
function pendingRewards(address user) public
view returns (uint ethReward, uint usdReward) {
Types.Deposit memory LP = autoManaged[user];
if (LP.pooled_eth == 0) return (0, 0);
uint ethOwed = FullMath.mulDiv(LP.pooled_eth, ETH_FEES, WAD);
uint usdOwed = FullMath.mulDiv(LP.pooled_eth, USD_FEES, WAD);
// Saturating subtraction to prevent underflow
ethReward = ethOwed > LP.fees_eth ? ethOwed - LP.fees_eth : 0;
usdReward = usdOwed > LP.fees_usd ? usdOwed - LP.fees_usd : 0;
}
// withdrawal by LP of ETH specifically, depositor may
// not know exactly how much they have accumulated in
// fees, so it's alright to pass in a huge number...
function withdraw(uint amount)
external nonReentrant {
(uint160 sqrtPriceX96,
int24 tickLower, int24 tickUpper,) = _repack();
Types.Deposit storage LP = autoManaged[msg.sender];
uint pooled_eth = V4.POOLED_ETH();
uint fees_eth; uint fees_usd;
if (LP.pooled_eth > 0)
(fees_eth,
fees_usd) = pendingRewards(msg.sender);
if (fees_eth > 0) { // rewards
LP.pooled_eth += fees_eth;
totalShares += fees_eth;
} // Handle USD rewards
fees_usd += LP.usd_owed;
if (fees_usd > 0) {
LP.usd_owed = 0;
QUID.mint(msg.sender,
fees_usd, address(QUID), 0);
}
// Cap withdrawal at user's total balance
// (i.e. principal + compounded rewards)
amount = Math.min(amount, LP.pooled_eth);
if (amount > 0) { uint sent;
uint pulled = Math.min(
amount, pooled_eth);
if (pulled > 0) {
// Only pull from V4 pool if position actually exists at these ticks.
// After a depleted repack, ticks point to an empty position —
// calling modLP would revert (CannotUpdateEmptyPosition).
// Skip and let shortfall path (vault excess + arbETH) handle
(,, uint128 posLiquidity) = V4.poolStats(tickLower, tickUpper);
if (posLiquidity > 0) {
sent = V4.modLP(sqrtPriceX96, pulled, 0,
tickLower, tickUpper, msg.sender);
}
}
// Arb flows naturally balance over time...
// The V4/V3 spread keeps the pool in equilibrium
// Any temporary imbalance self-corrects before LP withdrawals,
// arbETH is mainly emergency code that should never execute...
if (amount > sent) { uint shortfall = amount - sent;
// FIX: Re-read POOLED_ETH after modLP reduced it
uint currentPooledETH = V4.POOLED_ETH();
uint available = AUX.vogueETH();
uint excess = available > currentPooledETH
? available - currentPooledETH : 0;
excess = Math.min(
shortfall, excess);
if (excess > 0) {
excess = _sendETH(excess, msg.sender);
sent += excess; shortfall -= excess;
}
if (shortfall > 0) { uint arbed = AUX.arbETH(shortfall);
if (arbed > 0) { uint arbSent = _sendETH(
Math.min(arbed, shortfall), msg.sender);
sent += arbSent;
}
} // Only burn shares equal to actual delivered amount
// unrecovered shortfall is socialized across all LPs
amount = Math.min(sent, amount);
LP.pooled_eth -= amount;
totalShares -= amount;
// _sendETH / arbETH reduced vogueETH but lastVogueETH
// is stale-high → next _syncYield sees current < last → skips.
// Re-sync bookmark so gap yield isn't lost.
lastVogueETH = AUX.vogueETH();
} else { // Pool delivered enough
// (or more) - burn full amount
LP.pooled_eth -= amount;
totalShares -= amount;
}
} if (LP.pooled_eth == 0) delete autoManaged[msg.sender];
else {
LP.fees_eth = FullMath.mulDiv(LP.pooled_eth, ETH_FEES, WAD);
LP.fees_usd = FullMath.mulDiv(LP.pooled_eth, USD_FEES, WAD);
}
}
// this is for single-sided liquidity (ETH deposit)
// if you want to deposit dollars, mint with Basket
function deposit(uint amount)
external payable nonReentrant {
uint price = AUX.getTWAP(1800);
require(price > 0, "TWAP");
uint deltaETH; uint deltaUSD;
if (amount == 0 && msg.value == 0) return;
// _repack MUST run before _depositETH so that _syncYield
// reads the AAVE balance *before* the new deposit lands.
// Otherwise the deposit is misattributed as AAVE yield,
// inflating ETH_FEES and double-counting into pooled_eth.
Types.Deposit storage LP = autoManaged[msg.sender];
(uint160 sqrtPriceX96, int24 tickLower,
int24 tickUpper,) = _repack();
amount = _depositETH(msg.sender, amount);
// Advance the yield bookmark past the deposit so the
// *next* _syncYield doesn't mistake it for yield either.
lastVogueETH += amount;
uint eth_fees = ETH_FEES;
uint usd_fees = USD_FEES;
if (LP.pooled_eth > 0) {
(uint ethReward, uint usdReward) = pendingRewards(msg.sender);
LP.pooled_eth += ethReward; LP.usd_owed += usdReward;
totalShares += ethReward;
}
(deltaUSD, // how much can be paired
deltaETH) = this.addLiquidityHelper(
amount, price);
if (deltaETH > 0) {
LP.pooled_eth += deltaETH; totalShares += deltaETH;
LP.fees_eth = FullMath.mulDiv(LP.pooled_eth, eth_fees, WAD);
LP.fees_usd = FullMath.mulDiv(LP.pooled_eth, usd_fees, WAD);
V4.modLP(sqrtPriceX96, deltaETH, deltaUSD,
tickLower, tickUpper, msg.sender);
} // withdraw and refund excess from vault...
if (deltaETH < amount) {
_sendETH(amount - deltaETH, msg.sender);
// _sendETH may pull from vogueETH, stale WETH, or native ETH.
// Re-sync bookmark to actual vogueETH so _syncYield
// doesn't misattribute the refunded portion as yield.
lastVogueETH = AUX.vogueETH();
}
}
function addLiquidityHelper(
uint deltaETH, uint price) public
onlyUs returns (uint, uint) {
uint[14] memory deposits = AUX.get_deposits();
uint liquidTotal = deposits[12] - deposits[11]
+ AUX.getUSYCRedeemable();
uint committed = V4.POOLED_USD() * 1e12;
if (committed >= liquidTotal) return (0, 0);
uint surplus = liquidTotal - committed;
uint aaveAvail = AUX.vogueETH();
uint pooledETH = V4.POOLED_ETH();
uint availableETH = aaveAvail > pooledETH
? aaveAvail - pooledETH : 0;
uint targetUSD = FullMath.mulDiv(
deltaETH, price, WAD);
if (targetUSD > surplus) {
targetUSD = surplus;
deltaETH = FullMath.mulDiv(
surplus, WAD, price);
}
if (deltaETH > availableETH) {
deltaETH = availableETH;
targetUSD = FullMath.mulDiv(
deltaETH, price, WAD);
}
uint usdOut = targetUSD / 1e12;
if (usdOut == 0) return (0, 0);
return (usdOut, deltaETH);
}
// pull liquidity from. . .
function pull(uint id, // existing self-managed position
int percent, address token) external nonReentrant {
Types.SelfManaged storage position = selfManaged[id];
require(position.owner == msg.sender, "403");
require(block.number >=
position.created + 47, "too soon");
require(percent > 0 && percent < 101, "%");
int liquidity = position.liq * percent / 100;
require(liquidity > 0, "dust");
int24 lower = position.lower;
int24 upper = position.upper;
uint[] storage myIds = positions[msg.sender];
uint lastIndex = myIds.length > 0 ?
myIds.length - 1 : 0;
if (percent == 100) { delete selfManaged[id];
for (uint i = 0; i <= lastIndex; i++) {
if (myIds[i] == id) {
if (i < lastIndex) {
myIds[i] = myIds[lastIndex];
} myIds.pop(); break;
}
}
} else { position.liq -= liquidity;
require(position.liq > 0, "pull");
}
V4.outOfRange(msg.sender, -liquidity,
lower, upper, token);
}
function _calculateYield(uint fees0, uint fees1, uint delta0,
uint delta1, uint price) internal returns (uint yield) {
uint last_repack = LAST_REPACK;
uint deltaUSD; uint delta;
uint usd_fees; uint fees;
if (token1isETH) {
(delta, deltaUSD) = (delta1, delta0);
(fees, usd_fees) = (fees1, fees0);
} else {
(delta, deltaUSD) = (delta0, delta1);
(fees, usd_fees) = (fees0, fees1);
}
if (totalShares > 0) {
ETH_FEES += FullMath.mulDiv(fees, WAD, totalShares);
USD_FEES += FullMath.mulDiv(usd_fees, WAD, totalShares);
}
if (last_repack > 0) {
uint elapsed = block.timestamp - last_repack;
uint denom = (deltaUSD * 1e12 + FullMath.mulDiv(
price, delta, WAD)) * elapsed;
if (denom > 0) {
yield = FullMath.mulDiv((usd_fees * 1e12 +
FullMath.mulDiv(price, fees, WAD)) * 365 days,
WAD, denom) / WAD;
}
} LAST_REPACK = block.timestamp;
}
function _depositETH(address sender,
uint amount) internal returns (uint sent) {
if (msg.value > 0) {
WETH.deposit{value: msg.value}();
sent = msg.value; amount -= Math.min(
amount, msg.value);
}
if (amount > 0) { uint available = Math.min(
WETH.allowance(sender, address(this)),
WETH.balanceOf(sender));
uint took = Math.min(amount, available);
if (took > 0) { WETH.transferFrom(sender,
address(this), took);
sent += took;
}
} if (sent > 0) {
// Sweep ALL WETH to AAVE — captures sent + any residual.
// Only `sent` is returned for LP accounting; any extra
// goes to vogueETH and _syncYield distributes it as yield.
uint toDeposit = WETH.balanceOf(address(this));
AUX.vogueETHOp(toDeposit, 0);
}
}
function takeETH(uint howMuch, address recipient)
external onlyUs returns (uint sent) {
sent = _sendETH(howMuch, recipient);
}
/// @notice Sync AAVE yield into ETH_FEES
/// @dev Reads Vogue's entitled ETH from Aux
function _syncYield() internal {
uint current = AUX.vogueETHOp(0, 2);
if (lastVogueETH > 0 && current > lastVogueETH) {
uint aaveYield = current - lastVogueETH;
if (totalShares > 0)
ETH_FEES += FullMath.mulDiv(
aaveYield, WAD, totalShares);
} lastVogueETH = current;
}
function _sendETH(uint howMuch,
address toWhom) internal returns (uint sent) {
uint alreadyInETH = address(this).balance;
if (alreadyInETH >= howMuch) sent = howMuch;
else { uint needed = howMuch - alreadyInETH;
uint inWETH = WETH.balanceOf(address(this));
if (needed > inWETH) {
uint got = AUX.vogueETHOp(
needed - inWETH, 1);
inWETH += got;
} WETH.withdraw(inWETH);
sent = inWETH + alreadyInETH;
}
(bool success, ) = payable(toWhom).call{
value: sent }("");
if (!success) sent = 0;
}
function _repack() internal returns (uint160 sqrtPriceX96,
int24 tickLower, int24 tickUpper, uint128 myLiquidity) {
_syncYield(); // capture vault yield before anything else
int24 currentTick; tickUpper = UPPER_TICK; tickLower = LOWER_TICK;
(sqrtPriceX96, currentTick, myLiquidity) = V4.poolStats(
tickLower, tickUpper);
uint price; uint fees0; uint fees1; uint delta0; uint delta1;
if (currentTick > tickUpper || currentTick < tickLower) {
// Don't repack if deviating significantly from TWAP
delta1 = AUX.getTWAP(1800);
delta0 = BasketLib.getPrice(
sqrtPriceX96, token1isETH);
if (BasketLib.isManipulated(delta0, delta1, 3))
return (sqrtPriceX96, tickLower,
tickUpper, myLiquidity);
(int24 newTickLower,,
int24 newTickUpper,) = _updateTicks(
sqrtPriceX96, 200);
if (myLiquidity > 0) {
(price, fees0, fees1,
delta0, delta1) = V4.repack(myLiquidity, sqrtPriceX96,
tickLower, tickUpper, newTickLower, newTickUpper);
YIELD = _calculateYield(fees0, fees1, delta0, delta1, price);
} LOWER_TICK = newTickLower; UPPER_TICK = newTickUpper;
tickLower = newTickLower; tickUpper = newTickUpper;
}
}
function repack() public onlyUs returns (uint160 sqrtPriceX96,
int24 tickLower, int24 tickUpper, uint128 myLiquidity) {
(sqrtPriceX96, tickLower, tickUpper, myLiquidity) = _repack();
}
function paddedSqrtPrice(uint160 sqrtPriceX96,
bool up, uint delta) public view returns (uint160) {
uint factor = up ? FixedPointMathLib.sqrt((10000 + delta) * 1e18 / 10000)
: FixedPointMathLib.sqrt((10000 - delta) * 1e18 / 10000);
return uint160(FixedPointMathLib.mulDivDown(sqrtPriceX96, factor, 1e9));
}
function _alignTick(int24 tick, int24 width)
internal pure returns (int24) {
if (tick < 0 && tick % width != 0) {
return ((tick - width + 1) / width) * width;
} return (tick / width) * width;
}
function _updateTicks(uint160 sqrtPriceX96, uint delta)
internal view returns (int24 tickLower, uint160 lower,
int24 tickUpper, uint160 upper) {
lower = paddedSqrtPrice(sqrtPriceX96, false, delta);
upper = paddedSqrtPrice(sqrtPriceX96, true, delta);
require(lower >= TickMath.MIN_SQRT_PRICE + 1, "minPrice");
require(upper <= TickMath.MAX_SQRT_PRICE - 1, "maxPrice");
tickLower = _alignTick(TickMath.getTickAtSqrtPrice(lower), int24(10));
tickUpper = _alignTick(TickMath.getTickAtSqrtPrice(upper), int24(10));
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import {Amp} from "./Amp.sol";
import {Aux} from "./Aux.sol";
import {WETH} from "solmate/src/tokens/WETH.sol";
import {ERC20} from "solmate/src/tokens/ERC20.sol";
import {TickMath} from "./imports/v3/TickMath.sol";
import {FullMath} from "./imports/v3/FullMath.sol";
import {SafeTransferLib} from "solmate/src/utils/SafeTransferLib.sol";
import {ReentrancyGuard} from "solmate/src/utils/ReentrancyGuard.sol";
import {IUniswapV3Pool} from "./imports/v3/IUniswapV3Pool.sol";
import {LiquidityAmounts} from "./imports/v3/LiquidityAmounts.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IV3SwapRouter as ISwapRouter} from "./imports/v3/IV3SwapRouter.sol";
import {INonfungiblePositionManager} from "./imports/v3/INonfungiblePositionManager.sol";
// import {ISwapRouter} from "./imports/v3/ISwapRouter.sol"; // on L1, Arbitrum, Polygon
// import {AuxBase as Aux} from "./L2/AuxBase.sol";
// import {AuxArb as Aux} from "./L2/AuxArb.sol";
// import {AuxPoly as Aux} from "./L2/AuxPoly.sol";
// import {AuxUni as Aux} from "./L2/AuxUni.sol";
contract Rover is ReentrancyGuard, Ownable {
using SafeTransferLib for ERC20;
using SafeTransferLib for WETH;
address public immutable USDC;
bool public immutable nativeWETH;
WETH public immutable weth;
uint public YIELD; // %
uint public ID; // NFT
uint constant WAD = 1e18;
bool public token1isWETH;
uint public totalShares;
uint public LAST_REPACK;
int24 public UPPER_TICK;
int24 public LOWER_TICK;
int24 public LAST_TICK;
uint160 public LAST_SQRT_PRICE;
uint private _deployed;
int24 constant MAX_TICK = 887220;
address public AUX; Amp public AMP;
INonfungiblePositionManager public NFPM;
mapping(address => uint128) public positions;
int24 TICK_SPACING; uint24 public POOL_FEE;
address public POOL; address public ROUTER;
uint128 public liquidityUnderManagement;
function fetch(address beneficiary) public
returns (uint128, uint, uint160) {
uint128 liq = positions[beneficiary];
(uint160 sqrtPrice,
int24 tick,,,,,) = IUniswapV3Pool(POOL).slot0();
LAST_TICK = tick; LAST_SQRT_PRICE = sqrtPrice;
uint price = getPrice(sqrtPrice);
_repackNFT(0, 0, price); return (liq, price, sqrtPrice);
} receive() external payable {}
modifier onlyUs {
require(msg.sender == AUX
|| msg.sender == address(AMP), "403"); _;
}
constructor(address _amp,
address _weth, address _usdc,
address _nfpm, address _pool,
address _router, bool _nativeWETH)
Ownable(msg.sender) {
USDC = _usdc; POOL = _pool;
_deployed = block.timestamp;
weth = WETH(payable(_weth));
nativeWETH = _nativeWETH;
if (_amp != address(0)) {
AMP = Amp(payable(_amp));
ERC20(weth).approve(_amp, type(uint).max);
ERC20(USDC).approve(_amp, type(uint).max);
}
ROUTER = _router; totalShares = 1;
POOL_FEE = IUniswapV3Pool(POOL).fee();
TICK_SPACING = IUniswapV3Pool(POOL).tickSpacing();
address token0 = IUniswapV3Pool(POOL).token0();
address token1 = IUniswapV3Pool(POOL).token1();
token1isWETH = (token1 == _weth);
require((token1isWETH && token0 == _usdc)
|| (!token1isWETH && token1 == _usdc),
"wrong pool");
NFPM = INonfungiblePositionManager(_nfpm);
ERC20(weth).approve(_router, type(uint).max);
ERC20(USDC).approve(_router, type(uint).max);
ERC20(weth).approve(_nfpm, type(uint).max);
ERC20(USDC).approve(_nfpm, type(uint).max);
}
function setAux(address _aux) external onlyOwner {
require(AUX == address(0)); AUX = _aux;
renounceOwnership();
}
function _repackNFT(uint amount0, uint amount1,
uint price) internal { uint128 liquidity;
(int24 newLower, int24 newUpper) = _adjustTicks(LAST_TICK);
if (LAST_REPACK != 0) { // not the first time packing the NFT
if ((LAST_TICK > UPPER_TICK || LAST_TICK < LOWER_TICK) &&
// "to improve is to change, to perfect is to change often"
block.timestamp - LAST_REPACK >= 10 minutes) {
// we want to make sure that all of the WETH deposited to this
// contract is always in range (collecting), total range is ~7%
// below and above tick, as voltage regulators watch currents
// and control a relay (which turns on & off the alternator,
// if below or above 12 volts, re-charging battery as such)
(,,,,,,, liquidity,,,,) = NFPM.positions(ID);
liquidityUnderManagement = liquidity;
(uint collected0,
uint collected1,) = _withdrawAndCollect(liquidity);
amount0 += collected0; amount1 += collected1;
NFPM.burn(ID); ID = 0;
// Update ticks for new
// position after burning
LOWER_TICK = newLower;
UPPER_TICK = newUpper;
}
} else { // First time ever
LOWER_TICK = newLower;
UPPER_TICK = newUpper;
}
if (liquidity > 0 || ID == 0) {
if (amount0 == 0 && amount1 == 0) return;
// Convert to (wethAmount, usdcAmount) for potential _swap
(uint wethAmount, uint usdcAmount) = token1isWETH ?
(amount1, amount0) : (amount0, amount1);
// Only skip _swap if:
// - We didn't just burn a position (liquidity == 0), AND
// - We have pre-balanced amounts from deposit() (both > 0)
// When liquidity > 0, we just burned and collected amounts that
// are unbalanced for the NEW tick range - must rebalance via _swap
bool needsSwap = liquidity > 0 || wethAmount == 0 || usdcAmount == 0;
if (needsSwap) {
(wethAmount, usdcAmount) = _swap(
wethAmount, usdcAmount, price);
} // Convert back to (amount0, amount1)
// token0 is always the lower address
(uint mintAmount0, uint mintAmount1) = token1isWETH ?
(usdcAmount, wethAmount) : (wethAmount, usdcAmount);
(ID, liquidityUnderManagement,,) = NFPM.mint(
INonfungiblePositionManager.MintParams({
token0: token1isWETH ? USDC : address(weth),
token1: token1isWETH ? address(weth) : USDC,
fee: POOL_FEE, tickLower: LOWER_TICK,
tickUpper: UPPER_TICK,
amount0Desired: mintAmount0,
amount1Desired: mintAmount1,
amount0Min: 0, // atomic with swap, no MEV risk
amount1Min: 0,
recipient: address(this),
deadline: block.timestamp }));
LAST_REPACK = block.timestamp;
} else {
(uint collected0, uint collected1) = _collect(price);
amount0 += collected0; amount1 += collected1;
if (amount0 > 0 || amount1 > 0) {
// Try to compound collected fees. May fail if:
// - Position is in-range but fees are single-sided
// - Amounts are too small to create any liquidity
// On fail, tokens stay for next deposit/repack
try NFPM.increaseLiquidity(
INonfungiblePositionManager.IncreaseLiquidityParams(
ID, amount0, amount1, 0,
0, block.timestamp))
returns (uint128 addedLiquidity, uint, uint) {
liquidityUnderManagement += addedLiquidity;
} catch { // Silently continue - tokens remain
}
}
}
} function repackNFT() public nonReentrant
returns (uint160) { (uint160 sqrtPriceX96,
int24 tick,,,,,) = IUniswapV3Pool(POOL).slot0();
LAST_TICK = tick; LAST_SQRT_PRICE = sqrtPriceX96;
_repackNFT(0, 0, getPrice(sqrtPriceX96));
return sqrtPriceX96;
} // from v3-periphery/OracleLibrary
// Returns price as USDC per WETH...
function getPrice(uint160 sqrtRatioX96)
public view returns (uint price) {
uint casted = uint(sqrtRatioX96);
uint ratioX128 = FullMath.mulDiv(
casted, casted, 1 << 64);
if (token1isWETH) // sqrtPrice represents token0/token1 = USDC/WETH
// We want USDC per WETH, so invert: WETH/USDC -> 1/(USDC/WETH)
price = FullMath.mulDiv(1 << 128, WAD * 1e12, ratioX128);
else // sqrtPrice represents token0/token1 = WETH/USDC
// We want USDC per WETH, which is the ratio * decimal adjustment
price = FullMath.mulDiv(ratioX128, WAD * 1e12, 1 << 128);
}
function _collect(uint price) internal
returns (uint amount0, uint amount1) {
(amount0, amount1) = NFPM.collect(
INonfungiblePositionManager.CollectParams(ID,
address(this), type(uint128).max, type(uint128).max
)); // "collect calls to the tip sayin' how ya changed"
} //
function _withdrawAndCollect(uint128 liquidity) internal
returns (uint amount0, uint amount1, uint128 liq) {
// Early return if nothing requested or no position
if (liquidity == 0 || ID == 0) return (0, 0, 0);
// actual position liquidity from NFT - this is ground truth
(,,,,,,, uint128 positionLiquidity,,,,) = NFPM.positions(ID);
// Cap to actual available in NFT position (prevents NFPM revert)
if (liquidity > positionLiquidity)
liquidity = positionLiquidity;
// Also cap to our tracking variable...
if (liquidity > liquidityUnderManagement) {
liquidity = liquidityUnderManagement;
liquidityUnderManagement = 0;
} else
liquidityUnderManagement -= liquidity;
if (liquidity > 0) {
(uint160 sqrtLower, uint160 sqrtUpper,
uint160 sqrtCurrent) = _getTickSqrtPrices();
(uint exp0, uint exp1) = LiquidityAmounts
.getAmountsForLiquidity(sqrtCurrent,
sqrtLower, sqrtUpper, liquidity);
NFPM.decreaseLiquidity(// there's liquidity to withdraw
INonfungiblePositionManager.DecreaseLiquidityParams(
ID, liquidity, exp0 * 980 / 1000,
exp1 * 980 / 1000, block.timestamp));
(amount0, amount1) = _collect(0);
return (amount0, amount1, liquidity);
} return (0, 0, 0);
}
function _adjustToNearestIncrement(int24 input)
internal view returns (int24) {
int24 remainder = input % TICK_SPACING;
if (remainder == 0) return input;
if (remainder < 0) remainder += TICK_SPACING;
int24 result = remainder >= TICK_SPACING / 2
? input + (TICK_SPACING - remainder)
: input - remainder;
return result > MAX_TICK ? MAX_TICK :
result < -MAX_TICK ? -MAX_TICK : result;
}
function _adjustTicks(int24 currentTick) internal
view returns (int24 lower, int24 upper) {
// Calculate tick delta as ~3.57% of current tick
int256 tickDelta = (int256(currentTick) * 357) / 10000;
// Take absolute value - we always want
// to expand outward from current tick
if (tickDelta < 0) tickDelta = -tickDelta;
if (tickDelta < TICK_SPACING) tickDelta = TICK_SPACING;
// Lower tick is always currentTick - delta (more negative)
// Upper tick is always currentTick + delta (more positive)
lower = _adjustToNearestIncrement(currentTick - int24(int256(tickDelta)));
upper = _adjustToNearestIncrement(currentTick + int24(int256(tickDelta)));
if (lower > upper) (lower, upper) = (upper, lower);
// Ensure proper ordering (safety check)
// Ensure they're not equal
if (upper == lower)
upper += TICK_SPACING;
}
function _getTickSqrtPrices() internal view returns
(uint160 sqrtLower, uint160 sqrtUpper, uint160 sqrtCurrent) {
sqrtLower = TickMath.getSqrtPriceAtTick(LOWER_TICK);
sqrtUpper = TickMath.getSqrtPriceAtTick(UPPER_TICK);
sqrtCurrent = LAST_SQRT_PRICE;
}
function _swap(uint eth, uint usdc, uint price)
internal returns (uint, uint) {
if (eth == 0 && usdc == 0) return (0, 0);
uint targetETH; uint targetUSDC; usdc *= 1e12;
{ (uint160 sqrtLower, uint160 sqrtUpper,
uint160 sqrtCurrent) = _getTickSqrtPrices(); uint128 liquidity;
if (eth > 0) {
if (token1isWETH) { // ETH token1, getLiquidityForAmount1
liquidity = LiquidityAmounts.getLiquidityForAmount1(
sqrtCurrent, sqrtUpper, eth);
} else { // ETH is token0, use getLiquidityForAmount0
liquidity = LiquidityAmounts.getLiquidityForAmount0(
sqrtCurrent, sqrtUpper, eth);
}
} else { // We have USDC, calculate liquidity based on $ amount
if (token1isWETH) { // USDC token0, getLiquidityForAmount0
liquidity = LiquidityAmounts.getLiquidityForAmount0(
sqrtLower, sqrtCurrent, usdc);
} else { // USDC is token1, use getLiquidityForAmount1
liquidity = LiquidityAmounts.getLiquidityForAmount1(
sqrtLower, sqrtCurrent, usdc);
}
} if (liquidity == 0) return (eth, usdc / 1e12);
// Get target amounts for this liquidity
(uint amount0, uint amount1) = LiquidityAmounts.getAmountsForLiquidity(
sqrtCurrent, sqrtLower, sqrtUpper, liquidity);
if (token1isWETH) {
targetETH = amount1;
targetUSDC = amount0;
} else {
targetETH = amount0;
targetUSDC = amount1;
}
} if (targetETH == 0
&& targetUSDC == 0)
return (eth, usdc / 1e12);
// Assume ETH is X and USDC is Y...
// the formula is (x - ky)/(1 + kp);
// we're selling X to buy Y, where
// p is the price of ETH. So,
// derivation steps: assume n
// is amount being swapped...
// (x - n)/(y + np) = k target
// x - n = ky + knp
// x - ky = n + knp
// x - ky = n(1 + kp)
targetUSDC *= 1e12;
if (usdc > targetUSDC)
usdc = targetUSDC;
if (eth > targetETH)
eth = targetETH;
if (targetUSDC > usdc && eth > 0 && targetUSDC > 0) {
uint k = FullMath.mulDiv(targetETH, WAD, targetUSDC);
uint ky = FullMath.mulDiv(k, usdc, WAD);
if (eth > ky) { uint kp = FullMath.mulDiv(
k, price, WAD);
if (kp == 0) kp = 1;
uint toSwap = FullMath.mulDiv(WAD, eth - ky, WAD + kp);
if (toSwap > 0 && toSwap <= eth) { eth -= toSwap;
uint minUSDC = FullMath.mulDiv(toSwap, price, WAD) / 1e12;
minUSDC = minUSDC * 980 / 1000;
usdc += ISwapRouter(ROUTER).exactInput(ISwapRouter.ExactInputParams(
abi.encodePacked(address(weth), POOL_FEE, USDC),
address(this), /*block.timestamp,*/ toSwap, minUSDC)) * 1e12;
}
}
} if (targetETH > eth && usdc > 0
&& targetUSDC > 0 && targetETH > 0) { uint toSwapScaled;
uint k = FullMath.mulDiv(targetETH, WAD, targetUSDC);
if (k == 0) return (eth, usdc / 1e12);
uint kp = FullMath.mulDiv(k, price, WAD);
if (kp == 0) return (eth, usdc / 1e12);
if (eth > 0) {
uint ethValueInUsdc = FullMath.mulDiv(eth, WAD, k);
toSwapScaled = usdc > ethValueInUsdc ?
usdc - ethValueInUsdc : 0;
} else
toSwapScaled = usdc;
if (toSwapScaled > 0) {
uint toSwap = FullMath.mulDiv(WAD, toSwapScaled,
WAD + FullMath.mulDiv(WAD, WAD, kp)) / 1e12;
// Cap at available USDC...
uint maxSwap = usdc / 1e12;
if (toSwap > maxSwap) toSwap = maxSwap;
if (toSwap > 0) { usdc -= toSwap * 1e12;
uint minETH = FullMath.mulDiv(toSwap * 1e12, WAD, price);
minETH = minETH * 980 / 1000;
eth += ISwapRouter(ROUTER).exactInput(
ISwapRouter.ExactInputParams(abi.encodePacked(USDC, POOL_FEE,
address(weth)),address(this), /*block.timestamp,*/ toSwap, minETH));
}
}
} return (eth, usdc / 1e12);
}
function deposit(uint amount)
external nonReentrant payable {
(uint128 liq, uint price, uint160 sqrtPrice) = fetch(msg.sender);
if (amount > 0) weth.transferFrom(msg.sender, address(this), amount);
if (msg.value > 0) { require(nativeWETH, "no native wrap");
weth.deposit{value: msg.value}();
} uint in_dollars;
(amount, in_dollars) = _swap(amount + msg.value, 0, price);
(uint amount0, uint amount1) = token1isWETH ?
(in_dollars, amount) : (amount, in_dollars);
(uint160 sqrtLower, uint160 sqrtUpper,) = _getTickSqrtPrices();
uint128 newLiq = LiquidityAmounts.getLiquidityForAmounts(
sqrtPrice, sqrtLower, sqrtUpper, amount0, amount1);
uint128 newShares;
if (totalShares == 0) newShares = newLiq;
else { // Use max to prevent new depositors from benefiting if NAV decreased
// - If LUM > totalShares (fees accrued): new depositors get fewer shares
// - If LUM < totalShares (loss occurred): new depositors get 1:1 (neutral)
uint denominator = liquidityUnderManagement > totalShares ?
liquidityUnderManagement : totalShares;
newShares = uint128(FullMath.mulDiv(uint(newLiq),
totalShares, denominator));
} totalShares += newShares;
_repackNFT(amount0, amount1, price);
positions[msg.sender] += newShares;
}
// LP.liq = user's share (set at deposit, doesn't auto-grow)
// liquidityUnderManagement = actual V3 position liquidity
// (grows when fees compound via increaseLiquidity); when
// fees compound: liquidityUnderManagement += newLiquidity,
// but NO individual LP.liq changes;
// totalShares ≠ liquidityUnderManagement
// because the gap = compounded fees...
function take(uint amount) public onlyUs
returns (uint wethAmount) { repackNFT();
uint128 liquidity; uint usdcAmount;
(uint160 sqrtLower, uint160 sqrtUpper,
uint160 sqrtCurrent) = _getTickSqrtPrices();
liquidity = token1isWETH ? LiquidityAmounts.getLiquidityForAmount1(
sqrtCurrent, sqrtUpper, amount / 2):
LiquidityAmounts.getLiquidityForAmount0(
sqrtCurrent, sqrtUpper, amount / 2);
(uint amount0, uint amount1, ) = _withdrawAndCollect(liquidity);
if (token1isWETH) { usdcAmount = amount0; wethAmount = amount1; }
else { wethAmount = amount0; usdcAmount = amount1; }
if (usdcAmount > 0) {
uint minETH = FullMath.mulDiv(usdcAmount * 1e12, WAD,
getPrice(LAST_SQRT_PRICE));
minETH = minETH * 980 / 1000;
wethAmount += ISwapRouter(ROUTER).exactInput(ISwapRouter.ExactInputParams(
abi.encodePacked(USDC, POOL_FEE, address(weth)),
address(this), /*block.timestamp,*/ usdcAmount, minETH));
}
if (wethAmount > 0)
weth.transfer(msg.sender, wethAmount);
}
function depositUSDC(uint amount, uint price)
public onlyUs { repackNFT();
ERC20(USDC).transferFrom(msg.sender, address(this), amount);
(uint eth, uint usd) = _swap(0, amount, price);
(uint amount0, uint amount1) = token1isWETH ? (usd, eth)
: (eth, usd);
try NFPM.increaseLiquidity(
INonfungiblePositionManager.IncreaseLiquidityParams(
ID, amount0, amount1, 0,
0, block.timestamp))
returns (uint128 liquidity, uint, uint) {
liquidityUnderManagement += liquidity;
} catch {
// Tokens stay in Rover for next repack/deposit
}
}
function withdrawUSDC(uint amount)
public onlyUs returns (uint usd) {
uint eth; repackNFT(); uint128 liquidity;
(uint160 sqrtLower, uint160 sqrtUpper,
uint160 sqrtCurrent) = _getTickSqrtPrices();
liquidity = token1isWETH ?
LiquidityAmounts.getLiquidityForAmount0(
sqrtLower, sqrtCurrent, amount / 2):
LiquidityAmounts.getLiquidityForAmount1(
sqrtLower, sqrtCurrent, amount / 2);
(uint amount0,
uint amount1, ) = _withdrawAndCollect(liquidity);
if (token1isWETH) { eth = amount1; usd = amount0; }
else { eth = amount0; usd = amount1; }
if (eth > 0) {
uint minUSDC = FullMath.mulDiv(eth,
getPrice(LAST_SQRT_PRICE), WAD) / 1e12;
minUSDC = minUSDC * 980 / 1000;
usd += ISwapRouter(ROUTER).exactInput(ISwapRouter.ExactInputParams(
abi.encodePacked(address(weth), POOL_FEE, address(USDC)),
address(this), /*block.timestamp,*/ eth, minUSDC));
}
if (usd > 0)
ERC20(USDC).transfer(msg.sender, usd);
}
// @param (amount) is actually
// a % of their total liquidity
// if msg.sender != address(AUX)
function withdraw(uint amount) public nonReentrant {
require(amount > 0 && amount <= 1000, "%");
(uint128 liq, uint price,
uint160 sqrtPrice) = fetch(msg.sender);
require(liq > 0, "nothing to withdraw");
uint128 withdrawingShares = uint128(FullMath.mulDiv(
amount, uint(liq), 1000));
uint128 liquidity = uint128(FullMath.mulDiv(liquidityUnderManagement,
withdrawingShares, totalShares));
(uint amount0, uint amount1, ) = _withdrawAndCollect(liquidity);
(uint ethAmount, uint usdAmount) = token1isWETH ? (amount1, amount0)
: (amount0, amount1);
if (usdAmount > 0) {
uint minETH = FullMath.mulDiv(usdAmount * 1e12, WAD, price) * 980 / 1000;
ethAmount += ISwapRouter(ROUTER).exactInput(ISwapRouter.ExactInputParams(
abi.encodePacked(address(USDC), POOL_FEE, address(weth)),
address(this), /*block.timestamp,*/ usdAmount, minETH));
}
weth.withdraw(ethAmount);
liq -= withdrawingShares;
totalShares -= withdrawingShares;
// LP receives swap output...
// (bearing the slippage cost)
(bool success, ) = msg.sender.call{
value: ethAmount}("");
require(success, "$");
if (liq > 0) positions[msg.sender] = liq;
else delete positions[msg.sender];
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import {Aux} from "./Aux.sol";
import {mock} from "./mock.sol";
import {Vogue} from "./Vogue.sol";
import {Types} from "./imports/Types.sol";
import {BasketLib} from "./imports/BasketLib.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {IUniswapV3Pool} from "./imports/v3/IUniswapV3Pool.sol";
import {SafeCallback} from "v4-periphery/src/base/SafeCallback.sol";
import {LiquidityAmounts} from "v4-periphery/src/libraries/LiquidityAmounts.sol";
import {BalanceDelta, BalanceDeltaLibrary} from "v4-core/src/types/BalanceDelta.sol";
import {TransientStateLibrary} from "v4-core/src/libraries/TransientStateLibrary.sol";
import {Currency, CurrencyLibrary} from "v4-core/src/types/Currency.sol";
import {CurrencySettler} from "v4-core/test/utils/CurrencySettler.sol";
import {IPoolManager} from "v4-core/src/interfaces/IPoolManager.sol";
import {PoolId, PoolIdLibrary} from "v4-core/src/types/PoolId.sol";
import {StateLibrary} from "v4-core/src/libraries/StateLibrary.sol";
import {IERC20} from "forge-std/interfaces/IERC20.sol";
import {IHooks} from "v4-core/src/interfaces/IHooks.sol";
import {TickMath} from "v4-core/src/libraries/TickMath.sol";
import {FullMath} from "v4-core/src/libraries/FullMath.sol";
import {PoolKey} from "v4-core/src/types/PoolKey.sol";
contract VogueCore is SafeCallback {
using TransientStateLibrary for IPoolManager;
using BalanceDeltaLibrary for BalanceDelta;
using StateLibrary for IPoolManager;
using CurrencyLibrary for Currency;
using CurrencySettler for Currency;
using PoolIdLibrary for PoolKey;
int24 public initialTick;
int24 public lastTick;
struct Observation {
uint32 blockTimestamp;
int56 tickCumulative;
bool initialized;
} PoolKey VANILLA;
Observation[65535] public observations;
uint16 public observationCardinality;
uint16 public observationIndex;
uint public MAX_POOLED_USD;
uint public POOLED_ETH;
uint public POOLED_USD;
mock internal mockETH;
mock internal mockUSD;
uint constant WAD = 1e18;
bool public token1isETH;
Aux AUX; Vogue VOGUE;
enum Action { Swap,
Repack, ModLP,
OutsideRange } // 4 actions...
modifier onlyUs { // 2 contracts...
require(msg.sender == address(AUX)
|| msg.sender == address(VOGUE), "403"); _;
} bytes internal constant ZERO_BYTES = bytes("");
constructor(IPoolManager _manager) SafeCallback(_manager) {}
function setup(address _vogue, // vanilla pool (hookless)
address _aux, address _poolETH) external {
require(address(VOGUE) == address(0), "!");
mockETH = new mock(address(this), 18);
mockUSD = new mock(address(this), 6);
address token0; address token1;
// requires address currency0 < currency1
if (address(mockETH) > address(mockUSD)) {
token1isETH = true;
token0 = address(mockUSD);
token1 = address(mockETH);
} else {
token0 = address(mockETH);
token1 = address(mockUSD);
}
VOGUE = Vogue(payable(_vogue));
AUX = Aux(payable(_aux));
VANILLA = PoolKey({
currency0: Currency.wrap(address(token0)),
currency1: Currency.wrap(address(token1)),
fee: 420, tickSpacing: 10,
hooks: IHooks(address(0))});
(,int24 tickETH,,,,,) = IUniswapV3Pool(_poolETH).slot0();
mockUSD.approve(address(poolManager), type(uint).max);
mockETH.approve(address(poolManager), type(uint).max);
// Adjust tick if
// V4 and V3 have
// different order
if (token1isETH)
tickETH *= AUX.token1isWETH()
? int24(1) : int24(-1);
else
tickETH *= AUX.token1isWETH()
? int24(-1) : int24(1);
poolManager.initialize(VANILLA,
TickMath.getSqrtPriceAtTick(tickETH));
// Initialize the oracle observations...
initialTick = tickETH; lastTick = tickETH;
observations[0] = Observation({
blockTimestamp: uint32(block.timestamp),
tickCumulative: 0, initialized: true });
observationCardinality = 1;
}
function modLP(uint160 sqrtPriceX96, uint deltaETH,
uint deltaUSD, int24 tickLower, int24 tickUpper,
address sender) public onlyUs returns (uint ethSent) {
BalanceDelta delta = abi.decode(poolManager.unlock(abi.encode(
Action.ModLP, sqrtPriceX96, deltaETH, deltaUSD,
tickLower, tickUpper, sender)), (BalanceDelta));
int128 ethDelta = token1isETH ? delta.amount1() : delta.amount0();
ethSent = ethDelta > 0 ? uint(int(ethDelta)) : 0;
}
function outOfRange(address sender, int liquidity,
int24 tickLower, int24 tickUpper, address token)
public onlyUs {
abi.decode(poolManager.unlock(abi.encode(
Action.OutsideRange, sender, liquidity,
tickLower, tickUpper, token)), (BalanceDelta));
}
function swap(uint160 sqrtPriceX96, address sender,
bool forOne, address token, uint amount)
onlyUs public returns (uint out) {
BalanceDelta delta = abi.decode(poolManager.unlock(
abi.encode(Action.Swap, sqrtPriceX96, sender,
forOne, token, amount)), (BalanceDelta));
// zeroForOne=true: input token0, output token1
// zeroForOne=false: input token1, output token0
out = uint(int(forOne ? delta.amount1():
delta.amount0()));
uint totalShares = VOGUE.totalShares();
if (POOLED_ETH < totalShares) {
uint shortfall = totalShares - POOLED_ETH;
// Arb if shortfall >= 1% of total LP claims
if (shortfall * 100 >= totalShares)
AUX.arbETH(shortfall);
}
}
function _unlockCallback(bytes calldata data)
internal override returns (bytes memory) {
uint8 firstByte;
assembly {
let word := calldataload(data.offset)
firstByte := and(word, 0xFF)
}
Action discriminator = Action(firstByte);
if (discriminator == Action.Swap)
return _handleSwap(data[32:]);
else if (discriminator == Action.Repack)
return _handleRepack(data[32:]);
else if (discriminator == Action.OutsideRange)
return _handleOutsideRange(data[32:]);
else if (discriminator == Action.ModLP)
return _handleMod(data[32:]);
return "";
}
function _handleSwap(bytes calldata data)
internal returns (bytes memory) {
(uint160 sqrtPriceX96, address sender, bool forOne,
address token, uint amount) = abi.decode(data,
(uint160, address, bool, address, uint));
BalanceDelta delta = poolManager.swap(VANILLA,
IPoolManager.SwapParams({ zeroForOne: forOne,
amountSpecified: -int(amount), sqrtPriceLimitX96:
VOGUE.paddedSqrtPrice(sqrtPriceX96,
!forOne, 300) }), ZERO_BYTES);
(, int24 currentTick,,) = poolManager.getSlot0(VANILLA.toId());
_writeObservation(currentTick); _handleDelta(delta, true,
false, sender, token);
return abi.encode(delta);
}
function _handleRepack(bytes calldata data)
internal returns (bytes memory) {
POOLED_USD = 0; POOLED_ETH = 0;
(uint128 myLiquidity, uint160 sqrtPriceX96,
int24 oldTickLower, int24 oldTickUpper,
int24 newTickLower, int24 newTickUpper) = abi.decode(data,
(uint128, uint160, int24, int24, int24, int24));
(BalanceDelta delta,
BalanceDelta fees) = _modifyLiquidity(-int(uint(myLiquidity)),
oldTickLower, oldTickUpper);
(uint delta0, uint delta1) = _handleDelta(delta, false, true,
address(0), address(0));
BalanceDelta addDelta;
uint price = BasketLib.getPrice(
sqrtPriceX96, token1isETH);
if (token1isETH) {
(delta0, delta1) = VOGUE.addLiquidityHelper(delta1, price);
if (delta0 > 0 && delta1 > 0) {
addDelta = _modLP(delta0, delta1, newTickLower,
newTickUpper, sqrtPriceX96);
_handleDelta(addDelta, true, false,
address(0), address(0));
}
} else {
(delta1, delta0) = VOGUE.addLiquidityHelper(delta0, price);
if (delta1 > 0 && delta0 > 0) {
addDelta = _modLP(delta1, delta0, newTickLower,
newTickUpper, sqrtPriceX96);
_handleDelta(addDelta, true, false,
address(0), address(0));
}
} (, int24 currentTick,,) = poolManager.getSlot0(VANILLA.toId());
_writeObservation(currentTick);
return abi.encode(price, uint(int(fees.amount0())), uint(int(fees.amount1())),
uint(int(addDelta.amount0())), uint(int(addDelta.amount1())));
}
function _handleOutsideRange(bytes calldata data)
internal returns (bytes memory) { (address sender, int liquidity,
int24 tickLower, int24 tickUpper, address token) = abi.decode(data,
(address, int, int24, int24, address));
(BalanceDelta delta, ) = _modifyLiquidity(liquidity, tickLower, tickUpper);
_handleDelta(delta, false, false, sender, token); return abi.encode(delta);
}
function _handleMod(bytes calldata data)
internal returns (bytes memory) {
(uint160 sqrtPriceX96, uint deltaETH, uint deltaUSD,
int24 tickLower, int24 tickUpper, address sender) = abi.decode(
data, (uint160, uint, uint, int24, int24, address));
BalanceDelta delta = _modLP(deltaUSD, deltaETH,
tickLower, tickUpper, sqrtPriceX96);
bool keep = deltaUSD == 0;
_handleDelta(delta, true,
keep, sender, address(0));
return abi.encode(delta);
}
function _handleDelta(BalanceDelta delta, bool inRange, bool keep,
address who, address token) internal returns (uint, uint) {
Currency usdCurrency; Currency ethCurrency;
int128 usdDelta; int128 ethDelta;
uint usdAmount; uint ethAmount;
if (token1isETH) {
usdDelta = delta.amount0();
ethDelta = delta.amount1();
usdCurrency = VANILLA.currency0;
ethCurrency = VANILLA.currency1;
} else {
ethDelta = delta.amount0();
usdDelta = delta.amount1();
usdCurrency = VANILLA.currency1;
ethCurrency = VANILLA.currency0;
}
if (usdDelta > 0) {
usdAmount = uint(int(usdDelta));
usdCurrency.take(poolManager,
address(this), usdAmount, false);
mockUSD.burn(usdAmount);
if (inRange) POOLED_USD -= Math.min(
usdAmount, POOLED_USD);
if (!keep && token != address(0))
AUX.take(who, usdAmount, token, 0);
}
else if (usdDelta < 0) {
usdAmount = uint(int(-usdDelta));
mockUSD.mint(usdAmount);
usdCurrency.settle(poolManager,
address(this), usdAmount, false);
if (inRange) { POOLED_USD += usdAmount;
if (POOLED_USD > MAX_POOLED_USD)
MAX_POOLED_USD = POOLED_USD;
}
} if (ethDelta > 0) {
ethAmount = uint(int(ethDelta));
ethCurrency.take(poolManager,
address(this), ethAmount, false);
mockETH.burn(ethAmount);
if (inRange) POOLED_ETH -= Math.min(
ethAmount, POOLED_ETH);
if (who != address(0)) VOGUE.takeETH(
ethAmount, who);
} else if (ethDelta < 0) {
ethAmount = uint(int(-ethDelta));
mockETH.mint(ethAmount);
ethCurrency.settle(poolManager,
address(this), ethAmount, false);
if (inRange) POOLED_ETH += ethAmount;
} if (token1isETH)
return (usdAmount, ethAmount);
else return (ethAmount, usdAmount);
}
function _modifyLiquidity(int delta, int24 lowerTick, int24 upperTick)
internal returns (BalanceDelta totalDelta, BalanceDelta feesAccrued) {
(totalDelta, feesAccrued) = poolManager.modifyLiquidity(
VANILLA, IPoolManager.ModifyLiquidityParams({
tickLower: lowerTick, tickUpper: upperTick,
liquidityDelta: delta, salt: bytes32(0) }), ZERO_BYTES);
}
function _modLP(uint deltaUSD, uint deltaETH,
int24 tickLower, int24 tickUpper, uint160 sqrtPriceX96)
internal returns (BalanceDelta totalDelta) {
int flip = deltaUSD > 0 ? int(1) : int(-1);
uint128 liquidity = token1isETH ? LiquidityAmounts.getLiquidityForAmount1(
TickMath.getSqrtPriceAtTick(tickLower), sqrtPriceX96, deltaETH):
LiquidityAmounts.getLiquidityForAmount0(sqrtPriceX96,
TickMath.getSqrtPriceAtTick(tickUpper), deltaETH);
if (flip < 0) {
(,, uint128 posLiquidity) = poolStats(tickLower, tickUpper);
if (posLiquidity == 0) return BalanceDeltaLibrary.ZERO_DELTA;
if (liquidity > posLiquidity) liquidity = posLiquidity;
}
(totalDelta, ) = _modifyLiquidity(flip *
int(uint(liquidity)), tickLower, tickUpper);
}
function poolStats(int24 tickLower, int24 tickUpper) public view returns
(uint160 sqrtPriceX96, int24 currentTick, uint128 liquidity) { PoolId pool;
(pool, sqrtPriceX96, currentTick) = poolTicks();
(liquidity,,) = poolManager.getPositionInfo(pool,
address(this), tickLower, tickUpper, bytes32(0));
}
function poolTicks() public view
returns(PoolId, uint160, int24) {
PoolId pool = VANILLA.toId();
(uint160 sqrtPriceX96,
int24 currentTick,,) = poolManager.getSlot0(pool);
return (pool, sqrtPriceX96, currentTick);
}
/// @notice Write a new observation to the oracle
/// @dev Called on each swap/repack to track tick history
/// @param tick The current tick AFTER the action
function _writeObservation(int24 tick) internal {
uint32 blockTimestamp = uint32(block.timestamp);
Observation memory last = observations[observationIndex];
// Only write if time has passed since last observation
if (last.blockTimestamp == blockTimestamp) {
lastTick = tick; return;
}
// cumulative: accumulate lastTick over the elapsed time
uint32 delta = blockTimestamp - last.blockTimestamp;
int56 tickCumulative = last.tickCumulative
+ int56(lastTick) * int56(uint56(delta));
// Write to next slot (ring buffer)
uint16 indexNext = (observationIndex + 1) % 65535;
// Grow cardinality if needed (up to 65535 max)
if (indexNext >= observationCardinality &&
observationCardinality < 65535) {
observationCardinality = indexNext + 1;
}
observations[indexNext] = Observation({
blockTimestamp: blockTimestamp,
tickCumulative: tickCumulative,
initialized: true });
observationIndex = indexNext;
lastTick = tick;
}
/// @notice Observe tick cumulatives at given seconds ago
/// @param secondsAgos Array of seconds ago to observe
/// @return tickCumulatives Array of tick cumulatives at each time
function observe(uint32[] calldata secondsAgos)
external view returns (int56[] memory tickCumulatives) {
tickCumulatives = new int56[](secondsAgos.length);
uint32 time = uint32(block.timestamp);
Observation memory latest = observations[observationIndex];
Observation memory oldest = _getOldestObservation();
for (uint i = 0; i < secondsAgos.length; i++) {
uint32 target = time - secondsAgos[i];
// Current: extrapolate forward from latest
if (secondsAgos[i] == 0) {
uint32 delta = time - latest.blockTimestamp;
tickCumulatives[i] = latest.tickCumulative
+ int56(lastTick) * int56(uint56(delta));
}
else if (target <= oldest.blockTimestamp) {
// before/at oldest observation - extrapolate BACKWARDS
// This handles "not enough history", case by the way
uint32 beforeDelta = oldest.blockTimestamp - target;
// Use initialTick for backward extrapolation
// (assume tick was constant before init)...
tickCumulatives[i] = oldest.tickCumulative -
int56(initialTick) * int56(uint56(beforeDelta));
} // Target is at or after latest - extrapolate forward
else if (target >= latest.blockTimestamp) {
uint32 delta = target - latest.blockTimestamp;
tickCumulatives[i] = latest.tickCumulative
+ int56(lastTick) * int56(uint56(delta));
} else { // Target is between oldest and latest - interpolate
tickCumulatives[i] = _interpolate(target, oldest, latest);
}
}
}
function _getOldestObservation()
internal view returns (Observation memory) {
// In a ring buffer, oldest is at
// (observationIndex + 1) % cardinality
// But only if that slot is initialized
if (observationCardinality == 1)
return observations[0];
uint16 oldestIndex = (observationIndex + 1) % observationCardinality;
Observation memory oldest = observations[oldestIndex];
// If not initialized (ring buffer not full), oldest 0
if (!oldest.initialized)
return observations[0];
return oldest;
}
/// @notice Interpolate between
/// observations to find the
/// tickCumulative at target time
function _interpolate(uint32 target, Observation memory oldest,
Observation memory latest) internal view returns (int56) {
// If only 2 observations (oldest and latest), interpolate directly
if (observationCardinality <= 2) {
uint32 totalDelta = latest.blockTimestamp - oldest.blockTimestamp;
uint32 targetDelta = target - oldest.blockTimestamp;
if (totalDelta == 0) return oldest.tickCumulative;
int56 cumulativeDelta = latest.tickCumulative - oldest.tickCumulative;
return oldest.tickCumulative + (cumulativeDelta *
int56(uint56(targetDelta))) / int56(uint56(totalDelta));
} // Binary search for the bracketing pair of observations...
// so that TWAP reflects actual price history, not just
// a straight line between oldest and latest
uint16 card = observationCardinality;
uint16 oldestIdx = (observationIndex + 1) % card;
if (!observations[oldestIdx].initialized) oldestIdx = 0;
// Search space: offsets [0, card-1] from oldestIdx
// Find largest offset where timestamp <= target
uint16 lo = 0; uint16 hi = card - 1;
while (lo < hi) {
uint16 mid = lo + (hi - lo + 1) / 2;
uint16 idx = (oldestIdx + mid) % card;
if (observations[idx].blockTimestamp <= target)
lo = mid;
else
hi = mid - 1;
}
// lo is now the offset of the observation at or just before target
Observation memory before = observations[(oldestIdx + lo) % card];
Observation memory later = observations[(oldestIdx + lo + 1) % card];
uint32 totalDelta = later.blockTimestamp - before.blockTimestamp;
if (totalDelta == 0) return before.tickCumulative;
uint32 targetDelta = target - before.blockTimestamp;
int56 cumulativeDelta = later.tickCumulative - before.tickCumulative;
return before.tickCumulative +
(cumulativeDelta * int56(uint56(targetDelta))) /
int56(uint56(totalDelta));
}
function repack(uint128 myLiquidity,
uint160 sqrtPriceX96, int24 oldTickLower,
int24 oldTickUpper, int24 newTickLower, int24 newTickUpper) public onlyUs
returns (uint price, uint fees0, uint fees1, uint delta0, uint delta1) {
(price, fees0, fees1, delta0, delta1) = abi.decode(poolManager.unlock(
abi.encode(Action.Repack, myLiquidity, sqrtPriceX96, oldTickLower,
oldTickUpper, newTickLower, newTickUpper)),
(uint, uint, uint, uint, uint));
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import {FullMath} from "v4-core/src/libraries/FullMath.sol";
import {Types} from "./Types.sol";
interface IHook {
function stablecoinToSide(address) external view returns (uint8);
function getDepegStats(address) external view
returns (Types.DepegStats memory);
function getDepegSeverityBps() external view returns (uint);
}
interface IChainlinkOracle {
function latestRoundData() external view
returns (uint80, int, uint, uint, uint80);
}
interface IDSRRate {
function getRate() external view returns (uint);
}
interface ISCRVOracle {
function pricePerShare() external view returns (uint);
}
library FeeLib {
uint public constant WAD = 1e18;
uint public constant MONTH = 2420000;
uint public constant BASE = 4; // Fixed base fee (bps)
uint public constant MAX_FEE = 5000;
uint private constant MIN_MARKET_CAPITAL = 10_000e18;
uint8 internal constant MAX_SIDES = 12;
uint constant E_NUM = 2718281828;
uint constant E_DEN = 1000000000;
/// @dev b is capped at 6× INITIAL_B → max market-maker loss = 6×INITIAL_B×ln(12) ≈ 14.91×INITIAL_B
int128 public constant INITIAL_B = 10_000e18;
int128 public constant MAX_B = 60_000e18; // 6× cap
// Oracle type flags
uint8 public constant ORACLE_CHAINLINK = 3;
uint8 public constant ORACLE_CRV = 2;
uint8 public constant ORACLE_DSR_RATE = 1;
error StaleOracle();
error BadPrice();
function depositFee(uint amount, uint bps)
public pure returns (uint) {
return FullMath.mulDiv(
amount, bps, 10000);
}
/// @dev fee(X) = max(0, totalExposure - riskX)
/// where totalExposure = Σ(share_i × risk_i) across all tokens
/// Withdrawing the depegged token → fee = 0 (heals basket)
/// Withdrawing a healthy token → fee = basket's depeg exposure
/// Equal risk across all tokens → fee = BASE (no selective advantage)
/// @param thisRisk calcRisk score for the token being withdrawn (bps)
function calcFee(uint thisRisk,
uint totalExposure) public pure returns (uint) {
if (totalExposure <= thisRisk) return BASE;
uint fee = totalExposure - thisRisk;
if (fee < BASE) return BASE;
return fee > MAX_FEE ? MAX_FEE : fee;
}
/// @notice risk score from prediction market data
/// @dev Simple capital-ratio model matching Hook's Types.DepegStats
/// @param s Depeg stats from prediction market hook
/// @return Risk score in basis points
/// (0 = safe, 10000 = definitely depegging)
function calcRisk(Types.DepegStats memory s)
internal pure returns (uint) {
if (s.depegged) return 10000;
bool hasPrior = s.avgConf > 0;
uint prior = hasPrior ? uint(s.avgConf) : 6500;
if (s.capTotal == 0) return prior;
uint capitalSignal = (uint(s.capOnSide) * 10000) / uint(s.capTotal);
uint n = uint(s.capTotal) / MIN_MARKET_CAPITAL;
if (n == 0) return prior; // thin market → prior only
// Thick market with no prior → pure capital signal
if (!hasPrior) return capitalSignal;
// Thick market with prior → Bayesian blend
return (prior + n * capitalSignal) / (1 + n);
}
function calcFeeL1(address token, uint idx,
uint[14] memory deps, address[] memory stables,
address hook) public view returns (uint) {
uint totalDeposits = deps[12];
if (totalDeposits == 0) return BASE;
// Token without prediction market → no M1 signal, base fee only
if (IHook(hook).stablecoinToSide(token) == 0) return BASE;
// Get this token's risk score
uint thisRisk; uint totalExposure;
{ Types.DepegStats memory stats = IHook(hook).getDepegStats(token);
thisRisk = stats.side > 0 ? calcRisk(stats) : 0; }
// Compute basket-wide totalExposure = Σ(share_i × risk_i)
// Each term: (deps[i+1] / totalDeposits) × risk_i → in bps
for (uint i = 0; i < stables.length; i++) {
if (deps[i + 1] < 100 * WAD) continue;
uint8 side = IHook(hook).stablecoinToSide(stables[i]);
if (side == 0) continue;
Types.DepegStats memory s = IHook(hook).getDepegStats(stables[i]);
if (s.side > 0) {
uint risk = calcRisk(s);
totalExposure += (deps[i + 1] * risk) / totalDeposits;
}
} return calcFee(thisRisk, totalExposure);
}
/// @notice Calculate fee with automatic index lookup
/// @dev Wrapper around calcFeeL1 that finds index internally
function calcFeeL1WithLookup(address token,
uint[14] memory deps, address[] memory stables,
address hook) public view returns (uint) {
uint len = stables.length;
for (uint i; i < len;) {
if (stables[i] == token)
return calcFeeL1(token,
i, deps, stables, hook);
unchecked { ++i; }
} return 0;
}
/// @notice Check if token is depegged via Hook
function isDepegged(address token, address hook)
external view returns (bool) {
return IHook(hook).getDepegStats(token).depegged;
}
/// @notice Single-token fee + haircut → adjusted needed amount
function calcNeeded(address token, uint amount,
uint[14] memory deps, address[] memory stables,
address hook) external view
returns (uint needed) {
uint fee = (calcFeeL1WithLookup(token,
deps, stables, hook) * WAD) / 10000;
needed = (fee > 0 && fee < WAD / 10) ?
FullMath.mulDiv(amount, WAD - fee, WAD) : amount;
Types.DepegStats memory ds = IHook(hook).getDepegStats(token);
if (ds.depegged) {
uint haircutBps = IHook(hook).getDepegSeverityBps();
if (haircutBps > 0)
needed -= FullMath.mulDiv(needed, haircutBps, 10000);
}
}
/// @notice Apply pieralberto fee + depeg haircut in one call.
/// @dev Moves fee+haircut logic from Aux._take into FeeLib,
/// saving ~100 bytes of Aux bytecode.
function applyFeeAndHaircut(address token, uint idx,
uint amount, uint[14] memory deps,
address[] memory stables, address hook) external view returns (uint) {
uint feeBps = calcFeeL1(token,
idx, deps, stables, hook);
if (feeBps > 0) amount -= FullMath.mulDiv(
amount, feeBps, 10000);
Types.DepegStats memory ds = IHook(hook).getDepegStats(token);
if (ds.depegged) {
uint haircutBps = IHook(hook).getDepegSeverityBps();
if (haircutBps > 0) amount -= FullMath.mulDiv(
amount, haircutBps, 10000);
}
return amount;
}
/// @notice Find token with highest imbalance score (fee)
/// @dev Higher fee = more overweight + risky = priority to reduce
/// @return idx Index of most imbalanced token
/// @return fee Imbalance score (higher = reduce first)
/// @return excess Amount over equal weight (18 dec)
function getMostImbalanced(uint[14] memory deps,
address[] memory stables, address hook) external
view returns (uint idx, uint fee, uint excess) {
uint len = stables.length; uint high;
uint total = deps[12];
if (total == 0)
return (0, 0, 0);
for (uint i; i < len;) {
if (deps[i + 1] > 100 * WAD) {
uint f = calcFeeL1(stables[i],
i, deps, stables, hook);
if (f > high) { high = f; idx = i; }
} unchecked { ++i; }
} fee = high;
uint eq = total / len;
if (deps[idx + 1] > eq)
excess = deps[idx + 1] - eq;
}
function price(int128[MAX_SIDES] memory q,
uint8 n, int128 b, uint8 side) public
pure returns (uint p) {
int256 maxQ = _max(q, n);
uint eSide; uint eSum;
for (uint8 j; j < n; j++) {
uint ej = _expNorm(q[j], maxQ, b);
eSum += ej;
if (j == side) eSide = ej;
}
if (eSum == 0) return WAD / n;
p = (eSide * WAD) / eSum;
}
/// @notice Cost of buying `delta` tokens on `side`
/// @return c Unsigned cost in WAD
function cost(int128[MAX_SIDES] memory q, uint8 n,
int128 b, uint8 side, int128 delta) public
pure returns (uint c) { int128[MAX_SIDES] memory qA;
uint lseBefore = _logSumExp(q, n, b);
for (uint8 j; j < n; j++) qA[j] = q[j];
qA[side] += delta;
uint lseAfter = _logSumExp(qA, n, b);
uint bAbs = uint(int256(b));
c = lseAfter >= lseBefore
? (bAbs * (lseAfter - lseBefore)) / WAD
: (bAbs * (lseBefore - lseAfter)) / WAD;
}
/// @dev exp((q_j − maxQ) / b),
/// result WAD-scaled. arg ≤ 0.
function _expNorm(int128 qj,
int256 maxQ, int128 b)
internal pure returns (uint) {
int256 d = int256(qj) - maxQ; // ≤ 0
if (d == 0) return WAD;
uint absD = uint(-d);
uint absB = uint(int256(b));
// exp(-x) < 1 wei (WAD) when x > ~41.4
// (18 × ln(10)). findPosition limit ~56, so clamp at 41.
if (absD / absB > 41) return 0;
(uint pN, uint pD) =
_amPow(E_NUM,
E_DEN, absD, absB);
if (pN == 0) return 0;
return (pD * WAD) / pN;
// 1 / exp(|d|/b)
}
/// @dev max/b + ln(Σ exp((q_j−max)/b)) in WAD
function _logSumExp(int128[MAX_SIDES] memory q,
uint8 n, int128 b) internal pure returns (uint) {
int256 maxQ = _max(q, n); uint sum; uint lnWad;
for (uint8 j; j < n; j++)
sum += _expNorm(q[j], maxQ, b);
// ln(sum) where sum is WAD-scaled
if (sum > WAD) { (uint lnN, uint lnD) =
_amLog(sum, WAD);
lnWad = (lnN * WAD) / lnD;
} // else ln(≤1) ≤ 0, clamp to 0
// add back maxQ / b
uint bAbs = uint(int256(b));
if (maxQ >= 0)
lnWad += (uint(maxQ) * WAD) / bAbs;
else {
uint sub = (uint(-maxQ) * WAD) / bAbs;
lnWad = lnWad > sub ? lnWad - sub : 0;
} return lnWad;
}
// ── Inlined math (pow/log + IntegralMath + Uint) ──────────────────
uint8 private constant _MIN_PREC = 32;
uint8 private constant _MAX_PREC = 127;
uint256 private constant _FIXED_1 = 1 << 127;
uint256 private constant _FIXED_2 = 2 << 127;
uint256 private constant _LN2_NUM = 0x3f80fe03f80fe03f80fe03f80fe03f8;
uint256 private constant _LN2_DEN = 0x5b9de1d10bf4103d647b0955897ba80;
uint256 private constant _OPT_LOG_MAX = 0x15bf0a8b1457695355fb8ac404e7a79e4;
uint256 private constant _OPT_EXP_MAX = 0x800000000000000000000000000000000;
function _amPow(uint256 a, uint256 b, uint256 c, uint256 d)
private pure returns (uint256, uint256) { unchecked {
if (a >= b) return _mulDivExp(_mulDivLog(_FIXED_1, a, b), c, d);
(uint256 q, uint256 p) = _mulDivExp(_mulDivLog(_FIXED_1, b, a), c, d);
return (p, q);
}}
function _amLog(uint256 a, uint256 b)
private pure returns (uint256, uint256) { unchecked {
require(a >= b, "log: a < b");
return (_mulDivLog(_FIXED_1, a, b), _FIXED_1);
}}
function _mulDivLog(uint256 x, uint256 y, uint256 z)
private pure returns (uint256) {
return _fixedLog(_mulDivF(x, y, z));
}
function _mulDivExp(uint256 x, uint256 y, uint256 z)
private pure returns (uint256, uint256) {
return _fixedExp(_mulDivF(x, y, z));
}
function _fixedLog(uint256 x) private pure returns (uint256) { unchecked {
return x < _OPT_LOG_MAX ? _optimalLog(x) : _generalLog(x);
}}
function _fixedExp(uint256 x) private pure returns (uint256, uint256) { unchecked {
if (x < _OPT_EXP_MAX) return (_optimalExp(x), 1 << 127);
uint8 precision = _findPosition(x);
return (_generalExp(x >> (127 - precision), precision), 1 << precision);
}}
function _findPosition(uint256 x) private pure returns (uint8) { unchecked {
uint8 lo = _MIN_PREC; uint8 hi = _MAX_PREC;
while (lo + 1 < hi) {
uint8 mid = (lo + hi) / 2;
if (_maxExpArray(mid) >= x) lo = mid; else hi = mid;
}
if (_maxExpArray(hi) >= x) return hi;
if (_maxExpArray(lo) >= x) return lo;
revert("findPosition: x > max");
}}
function _generalLog(uint256 x) private pure returns (uint256) { unchecked {
uint256 res = 0;
if (x >= _FIXED_2) {
uint8 count = _floorLog2(x / _FIXED_1);
x >>= count; res = count * _FIXED_1;
}
if (x > _FIXED_1) {
for (uint8 i = 127; i > 0; --i) {
x = (x * x) / _FIXED_1;
if (x >= _FIXED_2) { x >>= 1; res += 1 << (i - 1); }
}
}
return res * _LN2_NUM / _LN2_DEN;
}}
function _generalExp(uint256 x, uint8 precision) private pure returns (uint256) { unchecked {
uint256 xi = x; uint256 res = 0;
xi = (xi * x) >> precision; res += xi * 0x3442c4e6074a82f1797f72ac0000000;
xi = (xi * x) >> precision; res += xi * 0x116b96f757c380fb287fd0e40000000;
xi = (xi * x) >> precision; res += xi * 0x045ae5bdd5f0e03eca1ff4390000000;
xi = (xi * x) >> precision; res += xi * 0x00defabf91302cd95b9ffda50000000;
xi = (xi * x) >> precision; res += xi * 0x002529ca9832b22439efff9b8000000;
xi = (xi * x) >> precision; res += xi * 0x00054f1cf12bd04e516b6da88000000;
xi = (xi * x) >> precision; res += xi * 0x0000a9e39e257a09ca2d6db51000000;
xi = (xi * x) >> precision; res += xi * 0x000012e066e7b839fa050c309000000;
xi = (xi * x) >> precision; res += xi * 0x000001e33d7d926c329a1ad1a800000;
xi = (xi * x) >> precision; res += xi * 0x0000002bee513bdb4a6b19b5f800000;
xi = (xi * x) >> precision; res += xi * 0x00000003a9316fa79b88eccf2a00000;
xi = (xi * x) >> precision; res += xi * 0x0000000048177ebe1fa812375200000;
xi = (xi * x) >> precision; res += xi * 0x0000000005263fe90242dcbacf00000;
xi = (xi * x) >> precision; res += xi * 0x000000000057e22099c030d94100000;
xi = (xi * x) >> precision; res += xi * 0x0000000000057e22099c030d9410000;
xi = (xi * x) >> precision; res += xi * 0x00000000000052b6b54569976310000;
xi = (xi * x) >> precision; res += xi * 0x00000000000004985f67696bf748000;
xi = (xi * x) >> precision; res += xi * 0x000000000000003dea12ea99e498000;
xi = (xi * x) >> precision; res += xi * 0x00000000000000031880f2214b6e000;
xi = (xi * x) >> precision; res += xi * 0x000000000000000025bcff56eb36000;
xi = (xi * x) >> precision; res += xi * 0x000000000000000001b722e10ab1000;
xi = (xi * x) >> precision; res += xi * 0x0000000000000000001317c70077000;
xi = (xi * x) >> precision; res += xi * 0x00000000000000000000cba84aafa00;
xi = (xi * x) >> precision; res += xi * 0x00000000000000000000082573a0a00;
xi = (xi * x) >> precision; res += xi * 0x00000000000000000000005035ad900;
xi = (xi * x) >> precision; res += xi * 0x000000000000000000000002f881b00;
xi = (xi * x) >> precision; res += xi * 0x0000000000000000000000001b29340;
xi = (xi * x) >> precision; res += xi * 0x00000000000000000000000000efc40;
xi = (xi * x) >> precision; res += xi * 0x0000000000000000000000000007fe0;
xi = (xi * x) >> precision; res += xi * 0x0000000000000000000000000000420;
xi = (xi * x) >> precision; res += xi * 0x0000000000000000000000000000021;
xi = (xi * x) >> precision; res += xi * 0x0000000000000000000000000000001;
return res / 0x688589cc0e9505e2f2fee5580000000 + x + (1 << precision);
}}
function _optimalLog(uint256 x) private pure returns (uint256) { unchecked {
uint256 res = 0; uint256 y; uint256 z; uint256 w;
if (x >= 0xd3094c70f034de4b96ff7d5b6f99fcd9) {res += 0x40000000000000000000000000000000; x = x * _FIXED_1 / 0xd3094c70f034de4b96ff7d5b6f99fcd9;}
if (x >= 0xa45af1e1f40c333b3de1db4dd55f29a8) {res += 0x20000000000000000000000000000000; x = x * _FIXED_1 / 0xa45af1e1f40c333b3de1db4dd55f29a8;}
if (x >= 0x910b022db7ae67ce76b441c27035c6a2) {res += 0x10000000000000000000000000000000; x = x * _FIXED_1 / 0x910b022db7ae67ce76b441c27035c6a2;}
if (x >= 0x88415abbe9a76bead8d00cf112e4d4a9) {res += 0x08000000000000000000000000000000; x = x * _FIXED_1 / 0x88415abbe9a76bead8d00cf112e4d4a9;}
if (x >= 0x84102b00893f64c705e841d5d4064bd4) {res += 0x04000000000000000000000000000000; x = x * _FIXED_1 / 0x84102b00893f64c705e841d5d4064bd4;}
if (x >= 0x8204055aaef1c8bd5c3259f4822735a3) {res += 0x02000000000000000000000000000000; x = x * _FIXED_1 / 0x8204055aaef1c8bd5c3259f4822735a3;}
if (x >= 0x810100ab00222d861931c15e39b44e9a) {res += 0x01000000000000000000000000000000; x = x * _FIXED_1 / 0x810100ab00222d861931c15e39b44e9a;}
if (x >= 0x808040155aabbbe9451521693554f734) {res += 0x00800000000000000000000000000000; x = x * _FIXED_1 / 0x808040155aabbbe9451521693554f734;}
z = y = x - _FIXED_1; w = y * y / _FIXED_1;
res += z * (0x100000000000000000000000000000000 - y) / 0x100000000000000000000000000000000; z = z * w / _FIXED_1;
res += z * (0x0aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - y) / 0x200000000000000000000000000000000; z = z * w / _FIXED_1;
res += z * (0x099999999999999999999999999999999 - y) / 0x300000000000000000000000000000000; z = z * w / _FIXED_1;
res += z * (0x092492492492492492492492492492492 - y) / 0x400000000000000000000000000000000; z = z * w / _FIXED_1;
res += z * (0x08e38e38e38e38e38e38e38e38e38e38e - y) / 0x500000000000000000000000000000000; z = z * w / _FIXED_1;
res += z * (0x08ba2e8ba2e8ba2e8ba2e8ba2e8ba2e8b - y) / 0x600000000000000000000000000000000; z = z * w / _FIXED_1;
res += z * (0x089d89d89d89d89d89d89d89d89d89d89 - y) / 0x700000000000000000000000000000000; z = z * w / _FIXED_1;
res += z * (0x088888888888888888888888888888888 - y) / 0x800000000000000000000000000000000;
return res;
}}
function _optimalExp(uint256 x) private pure returns (uint256) { unchecked {
uint256 res = 0; uint256 y; uint256 z;
z = y = x % 0x10000000000000000000000000000000;
z = z * y / _FIXED_1; res += z * 0x10e1b3be415a0000;
z = z * y / _FIXED_1; res += z * 0x05a0913f6b1e0000;
z = z * y / _FIXED_1; res += z * 0x0168244fdac78000;
z = z * y / _FIXED_1; res += z * 0x004807432bc18000;
z = z * y / _FIXED_1; res += z * 0x000c0135dca04000;
z = z * y / _FIXED_1; res += z * 0x0001b707b1cdc000;
z = z * y / _FIXED_1; res += z * 0x000036e0f639b800;
z = z * y / _FIXED_1; res += z * 0x00000618fee9f800;
z = z * y / _FIXED_1; res += z * 0x0000009c197dcc00;
z = z * y / _FIXED_1; res += z * 0x0000000e30dce400;
z = z * y / _FIXED_1; res += z * 0x000000012ebd1300;
z = z * y / _FIXED_1; res += z * 0x0000000017499f00;
z = z * y / _FIXED_1; res += z * 0x0000000001a9d480;
z = z * y / _FIXED_1; res += z * 0x00000000001c6380;
z = z * y / _FIXED_1; res += z * 0x000000000001c638;
z = z * y / _FIXED_1; res += z * 0x0000000000001ab8;
z = z * y / _FIXED_1; res += z * 0x000000000000017c;
z = z * y / _FIXED_1; res += z * 0x0000000000000014;
z = z * y / _FIXED_1; res += z * 0x0000000000000001;
res = res / 0x21c3677c82b40000 + y + _FIXED_1;
if ((x & 0x010000000000000000000000000000000) != 0) res = res * 0x1c3d6a24ed82218787d624d3e5eba95f9 / 0x18ebef9eac820ae8682b9793ac6d1e776;
if ((x & 0x020000000000000000000000000000000) != 0) res = res * 0x18ebef9eac820ae8682b9793ac6d1e778 / 0x1368b2fc6f9609fe7aceb46aa619baed4;
if ((x & 0x040000000000000000000000000000000) != 0) res = res * 0x1368b2fc6f9609fe7aceb46aa619baed5 / 0x0bc5ab1b16779be3575bd8f0520a9f21f;
if ((x & 0x080000000000000000000000000000000) != 0) res = res * 0x0bc5ab1b16779be3575bd8f0520a9f21e / 0x0454aaa8efe072e7f6ddbab84b40a55c9;
if ((x & 0x100000000000000000000000000000000) != 0) res = res * 0x0454aaa8efe072e7f6ddbab84b40a55c5 / 0x00960aadc109e7a3bf4578099615711ea;
if ((x & 0x200000000000000000000000000000000) != 0) res = res * 0x00960aadc109e7a3bf4578099615711d7 / 0x0002bf84208204f5977f9a8cf01fdce3d;
if ((x & 0x400000000000000000000000000000000) != 0) res = res * 0x0002bf84208204f5977f9a8cf01fdc307 / 0x0000003c6ab775dd0b95b4cbee7e65d11;
return res;
}}
// IntegralMath.floorLog2
function _floorLog2(uint256 n) private pure returns (uint8) { unchecked {
uint8 res = 0;
if (n < 256) { while (n > 1) { n >>= 1; res += 1; } }
else { for (uint8 s = 128; s > 0; s >>= 1) { if (n >= 1 << s) { n >>= s; res |= s; } } }
return res;
}}
// IntegralMath.mulDivF
function _mulDivF(uint256 x, uint256 y, uint256 z) private pure returns (uint256) { unchecked {
(uint256 xyh, uint256 xyl) = _mul512(x, y);
if (xyh == 0) return xyl / z;
if (xyh < z) {
uint256 m = mulmod(x, y, z);
(uint256 nh, uint256 nl) = _sub512(xyh, xyl, m);
if (nh == 0) return nl / z;
uint256 p = (~z + 1) & z;
uint256 q = _div512(nh, nl, p);
uint256 r = _inv256(z / p);
return q * r;
}
revert();
}}
function _mul512(uint256 x, uint256 y) private pure returns (uint256, uint256) { unchecked {
uint256 p = mulmod(x, y, type(uint256).max);
uint256 q = x * y;
if (p >= q) return (p - q, q);
return (p - q - 1, q); // underflow intentional
}}
function _sub512(uint256 xh, uint256 xl, uint256 y) private pure returns (uint256, uint256) { unchecked {
if (xl >= y) return (xh, xl - y);
return (xh - 1, xl - y); // underflow intentional
}}
function _div512(uint256 xh, uint256 xl, uint256 pow2n) private pure returns (uint256) { unchecked {
uint256 pow2nInv = (~pow2n + 1) / pow2n + 1;
return xh * pow2nInv | (xl / pow2n);
}}
function _inv256(uint256 d) private pure returns (uint256) { unchecked {
uint256 x = 1;
for (uint256 i = 0; i < 8; ++i) x = x * (2 - x * d);
return x;
}}
// maxExpArray[32..127] as a pure lookup (replaces contract storage)
function _maxExpArray(uint8 i) private pure returns (uint256) { unchecked {
if (i == 32) return 0x1c35fedd14ffffffffffffffffffffffff;
if (i == 33) return 0x1b0ce43b323fffffffffffffffffffffff;
if (i == 34) return 0x19f0028ec1ffffffffffffffffffffffff;
if (i == 35) return 0x18ded91f0e7fffffffffffffffffffffff;
if (i == 36) return 0x17d8ec7f0417ffffffffffffffffffffff;
if (i == 37) return 0x16ddc6556cdbffffffffffffffffffffff;
if (i == 38) return 0x15ecf52776a1ffffffffffffffffffffff;
if (i == 39) return 0x15060c256cb2ffffffffffffffffffffff;
if (i == 40) return 0x1428a2f98d72ffffffffffffffffffffff;
if (i == 41) return 0x13545598e5c23fffffffffffffffffffff;
if (i == 42) return 0x1288c4161ce1dfffffffffffffffffffff;
if (i == 43) return 0x11c592761c666fffffffffffffffffffff;
if (i == 44) return 0x110a688680a757ffffffffffffffffffff;
if (i == 45) return 0x1056f1b5bedf77ffffffffffffffffffff;
if (i == 46) return 0x0faadceceeff8bffffffffffffffffffff;
if (i == 47) return 0x0f05dc6b27edadffffffffffffffffffff;
if (i == 48) return 0x0e67a5a25da4107fffffffffffffffffff;
if (i == 49) return 0x0dcff115b14eedffffffffffffffffffff;
if (i == 50) return 0x0d3e7a392431239fffffffffffffffffff;
if (i == 51) return 0x0cb2ff529eb71e4fffffffffffffffffff;
if (i == 52) return 0x0c2d415c3db974afffffffffffffffffff;
if (i == 53) return 0x0bad03e7d883f69bffffffffffffffffff;
if (i == 54) return 0x0b320d03b2c343d5ffffffffffffffffff;
if (i == 55) return 0x0abc25204e02828dffffffffffffffffff;
if (i == 56) return 0x0a4b16f74ee4bb207fffffffffffffffff;
if (i == 57) return 0x09deaf736ac1f569ffffffffffffffffff;
if (i == 58) return 0x0976bd9952c7aa957fffffffffffffffff;
if (i == 59) return 0x09131271922eaa606fffffffffffffffff;
if (i == 60) return 0x08b380f3558668c46fffffffffffffffff;
if (i == 61) return 0x0857ddf0117efa215bffffffffffffffff;
if (i == 62) return 0x07ffffffffffffffffffffffffffffffff;
if (i == 63) return 0x07abbf6f6abb9d087fffffffffffffffff;
if (i == 64) return 0x075af62cbac95f7dfa7fffffffffffffff;
if (i == 65) return 0x070d7fb7452e187ac13fffffffffffffff;
if (i == 66) return 0x06c3390ecc8af379295fffffffffffffff;
if (i == 67) return 0x067c00a3b07ffc01fd6fffffffffffffff;
if (i == 68) return 0x0637b647c39cbb9d3d27ffffffffffffff;
if (i == 69) return 0x05f63b1fc104dbd39587ffffffffffffff;
if (i == 70) return 0x05b771955b36e12f7235ffffffffffffff;
if (i == 71) return 0x057b3d49dda84556d6f6ffffffffffffff;
if (i == 72) return 0x054183095b2c8ececf30ffffffffffffff;
if (i == 73) return 0x050a28be635ca2b888f77fffffffffffff;
if (i == 74) return 0x04d5156639708c9db33c3fffffffffffff;
if (i == 75) return 0x04a23105873875bd52dfdfffffffffffff;
if (i == 76) return 0x0471649d87199aa990756fffffffffffff;
if (i == 77) return 0x04429a21a029d4c1457cfbffffffffffff;
if (i == 78) return 0x0415bc6d6fb7dd71af2cb3ffffffffffff;
if (i == 79) return 0x03eab73b3bbfe282243ce1ffffffffffff;
if (i == 80) return 0x03c1771ac9fb6b4c18e229ffffffffffff;
if (i == 81) return 0x0399e96897690418f785257fffffffffff;
if (i == 82) return 0x0373fc456c53bb779bf0ea9fffffffffff;
if (i == 83) return 0x034f9e8e490c48e67e6ab8bfffffffffff;
if (i == 84) return 0x032cbfd4a7adc790560b3337ffffffffff;
if (i == 85) return 0x030b50570f6e5d2acca94613ffffffffff;
if (i == 86) return 0x02eb40f9f620fda6b56c2861ffffffffff;
if (i == 87) return 0x02cc8340ecb0d0f520a6af58ffffffffff;
if (i == 88) return 0x02af09481380a0a35cf1ba02ffffffffff;
if (i == 89) return 0x0292c5bdd3b92ec810287b1b3fffffffff;
if (i == 90) return 0x0277abdcdab07d5a77ac6d6b9fffffffff;
if (i == 91) return 0x025daf6654b1eaa55fd64df5efffffffff;
if (i == 92) return 0x0244c49c648baa98192dce88b7ffffffff;
if (i == 93) return 0x022ce03cd5619a311b2471268bffffffff;
if (i == 94) return 0x0215f77c045fbe885654a44a0fffffffff;
if (i == 95) return 0x01ffffffffffffffffffffffffffffffff;
if (i == 96) return 0x01eaefdbdaaee7421fc4d3ede5ffffffff;
if (i == 97) return 0x01d6bd8b2eb257df7e8ca57b09bfffffff;
if (i == 98) return 0x01c35fedd14b861eb0443f7f133fffffff;
if (i == 99) return 0x01b0ce43b322bcde4a56e8ada5afffffff;
if (i == 100) return 0x019f0028ec1fff007f5a195a39dfffffff;
if (i == 101) return 0x018ded91f0e72ee74f49b15ba527ffffff;
if (i == 102) return 0x017d8ec7f04136f4e5615fd41a63ffffff;
if (i == 103) return 0x016ddc6556cdb84bdc8d12d22e6fffffff;
if (i == 104) return 0x015ecf52776a1155b5bd8395814f7fffff;
if (i == 105) return 0x015060c256cb23b3b3cc3754cf40ffffff;
if (i == 106) return 0x01428a2f98d728ae223ddab715be3fffff;
if (i == 107) return 0x013545598e5c23276ccf0ede68034fffff;
if (i == 108) return 0x01288c4161ce1d6f54b7f61081194fffff;
if (i == 109) return 0x011c592761c666aa641d5a01a40f17ffff;
if (i == 110) return 0x0110a688680a7530515f3e6e6cfdcdffff;
if (i == 111) return 0x01056f1b5bedf75c6bcb2ce8aed428ffff;
if (i == 112) return 0x00faadceceeff8a0890f3875f008277fff;
if (i == 113) return 0x00f05dc6b27edad306388a600f6ba0bfff;
if (i == 114) return 0x00e67a5a25da41063de1495d5b18cdbfff;
if (i == 115) return 0x00dcff115b14eedde6fc3aa5353f2e4fff;
if (i == 116) return 0x00d3e7a3924312399f9aae2e0f868f8fff;
if (i == 117) return 0x00cb2ff529eb71e41582cccd5a1ee26fff;
if (i == 118) return 0x00c2d415c3db974ab32a51840c0b67edff;
if (i == 119) return 0x00bad03e7d883f69ad5b0a186184e06bff;
if (i == 120) return 0x00b320d03b2c343d4829abd6075f0cc5ff;
if (i == 121) return 0x00abc25204e02828d73c6e80bcdb1a95bf;
if (i == 122) return 0x00a4b16f74ee4bb2040a1ec6c15fbbf2df;
if (i == 123) return 0x009deaf736ac1f569deb1b5ae3f36c130f;
if (i == 124) return 0x00976bd9952c7aa957f5937d790ef65037;
if (i == 125) return 0x009131271922eaa6064b73a22d0bd4f2bf;
if (i == 126) return 0x008b380f3558668c46c91c49a2f8e967b9;
if (i == 127) return 0x00857ddf0117efa215952912839f6473e6;
revert("maxExpArray: out of range");
}}
function _max(int128[MAX_SIDES] memory q, uint8 n)
internal pure returns (int256 m) {
m = int256(q[0]);
for (uint8 j = 1; j < n; j++)
if (int256(q[j]) > m) m = int256(q[j]);
}
struct StakedPairs {
uint8[4] base; // Base token indices
uint8[4] staked; // Corresponding staked token indices
}
function getStakedPrice(address oracle, uint8 oracleType)
public view returns (uint price) {
if (oracleType == ORACLE_DSR_RATE) {
price = IDSRRate(oracle).getRate();
} else if (oracleType == ORACLE_CRV) {
price = ISCRVOracle(oracle).pricePerShare();
} else if (oracleType == ORACLE_CHAINLINK) {
(, int answer,, uint ts,) = IChainlinkOracle(oracle).latestRoundData();
price = uint(answer);
if (ts == 0 || ts > block.timestamp) revert StaleOracle();
}
if (price < WAD) revert BadPrice();
}
function getBaseIndex(uint idx,
StakedPairs memory pairs)
internal pure returns (uint) {
for (uint i = 0; i < 4; i++)
if (pairs.staked[i] == idx)
return pairs.base[i];
return idx;
}
function getCombinedDeposits(uint base, uint[14] memory deps,
StakedPairs memory pairs) internal pure returns (uint) {
uint combined = deps[base + 2];
for (uint i = 0; i < 4; i++)
if (pairs.base[i] == base) {
combined += deps[pairs.staked[i] + 2];
break;
}
return combined;
}
function isStakedToken(uint idx,
StakedPairs memory pairs)
internal pure returns (bool) {
for (uint i = 0; i < 4; i++)
if (pairs.staked[i] == idx)
return true;
return false;
}
function calcFeeWithPairs(uint idx, uint[14] memory deps,
StakedPairs memory pairs, address[] memory stables,
address hook) external view returns (uint) {
uint base = getBaseIndex(idx, pairs);
if (IHook(hook).stablecoinToSide(stables[base]) == 0) return BASE;
Types.DepegStats memory stats = IHook(hook).getDepegStats(stables[base]);
if (stats.side == 0) return BASE;
uint totalDeposits = deps[1];
if (totalDeposits == 0) return BASE;
uint thisRisk = calcRisk(stats);
uint totalExposure;
for (uint i = 0; i < stables.length; i++) {
if (isStakedToken(i, pairs)) continue;
uint combined = getCombinedDeposits(i, deps, pairs);
if (combined < 100 * WAD) continue;
uint8 side = IHook(hook).stablecoinToSide(stables[i]);
if (side == 0) continue;
Types.DepegStats memory s = IHook(hook).getDepegStats(stables[i]);
if (s.side > 0) {
uint risk = calcRisk(s);
totalExposure += (combined * risk) / totalDeposits;
}
} return calcFee(thisRisk, totalExposure);
}
function calcFeeUni(uint idx, uint[7] memory deps,
address[] memory stables, address hook) external view
returns (uint) { uint base = (idx == 3) ? 2 : idx; // sUSDS maps to USDS
if (IHook(hook).stablecoinToSide(stables[base]) == 0) return BASE;
Types.DepegStats memory stats = IHook(hook).getDepegStats(stables[base]);
if (stats.side == 0 || deps[1] == 0) return BASE;
uint thisRisk = calcRisk(stats); uint totalExposure;
for (uint i = 0; i < 5; i++) {
if (i == 3) continue; // skip sUSDS, count USDS
uint dep = (i == 2) ? deps[4] + deps[5] : deps[i + 2];
if (dep < 100 * WAD) continue;
uint8 side = IHook(hook).stablecoinToSide(stables[i]);
if (side == 0) continue;
Types.DepegStats memory s = IHook(hook).getDepegStats(stables[i]);
if (s.side > 0) {
uint risk = calcRisk(s);
totalExposure += (dep * risk) / deps[1];
}
} return calcFee(thisRisk, totalExposure);
}
function calcFeePoly(uint idx, uint[8] memory deps,
address[] memory stables, address hook) external view returns (uint) {
if (IHook(hook).stablecoinToSide(stables[idx]) == 0) return BASE;
Types.DepegStats memory stats = IHook(hook).getDepegStats(stables[idx]);
if (stats.side == 0 || deps[1] == 0) return BASE;
uint thisRisk = calcRisk(stats); uint totalExposure;
for (uint i = 0; i < 6; i++) {
if (deps[i + 2] < 100 * WAD) continue;
uint8 side = IHook(hook).stablecoinToSide(stables[i]);
if (side == 0) continue;
Types.DepegStats memory s = IHook(hook).getDepegStats(stables[i]);
if (s.side > 0) { uint risk = calcRisk(s);
totalExposure += (deps[i + 2] * risk) / deps[1];
}
} return calcFee(thisRisk, totalExposure);
}
function calcWithdrawAmounts(uint amount,
uint[14] memory deposits, int indexToSkip,
bool strict, uint[3] memory prices,
uint8[3] memory priceIndices,
uint16 sixDecMask) public pure
returns (uint[12] memory w) {
uint totalDeposits = deposits[1];
if (totalDeposits == 0) return w;
for (uint i = 0; i < 12; i++) {
if (int(i) == indexToSkip) continue;
w[i] = _calcOne(amount, totalDeposits,
deposits[i + 2], i, strict, prices,
priceIndices, sixDecMask);
}
}
function _calcOne(uint amount, uint total, uint dep,
uint i, bool strict, uint[3] memory prices,
uint8[3] memory priceIndices,
uint16 sixDecMask) internal pure returns (uint out) {
if (dep == 0) return 0;
uint share = FullMath.mulDiv(amount, dep, total);
// Apply staked price conversion if this index has a price
uint p;
if (priceIndices[0] == i) p = prices[0];
else if (priceIndices[1] == i) p = prices[1];
else if (priceIndices[2] == i) p = prices[2];
if (p > 0 && p != WAD)
share = FullMath.mulDiv(share, WAD, p);
// Apply 6-decimal divisor if bit is set
bool isSixDec = (sixDecMask >> i) & 1 == 1;
if (isSixDec) {
share = share / 1e12;
if (strict && share * 1e12 > dep)
share = dep / 1e12;
} else if (strict && share > dep) {
share = dep;
}
out = share;
}
/// @notice Entropy-weighted adaptive liquidity parameter.
/// @dev b grows only when capital is uniformly distributed across sides.
/// A whale concentrating capital on one side produces low entropy →
/// b barely moves → thin sides stay protected.
/// A healthy market with genuine multi-side participation earns deeper
/// liquidity, making late-round manipulation progressively more expensive.
///
/// Formula (from simulation):
/// H = -Σ p_i × ln(p_i) (Shannon entropy of capital dist.)
/// entropy_ratio = H / ln(n) (0=fully concentrated, 1=uniform)
/// total_scale = (totalCap / (INITIAL_B × n)) ^ 0.25 capped at 6
/// b_target = INITIAL_B × (1 + (total_scale − 1) × entropy_ratio)
///
/// The ^0.25 exponent keeps growth conservative — b only compounds when
/// entropy stays high across multiple rounds.
///
/// Optional EMA smoothing (alpha=0.3) damps round-to-round variance:
/// b_new = 0.3 × b_target + 0.7 × b_prev
/// Pass b_prev = 0 to skip EMA (first round, or if caller tracks state).
///
/// @param capitalPerSide Capital deposited per outcome this round (WAD)
/// @param n Number of active sides
/// @param b_prev Previous round's b (pass 0 to skip EMA)
/// @return b_new Adaptive b for this round (WAD, int128)
function adaptiveB(
uint[MAX_SIDES] memory capitalPerSide,
uint8 n,
int128 b_prev
) public pure returns (int128 b_new) {
require(n >= 2 && n <= MAX_SIDES, "adaptiveB: n out of range");
// ── 1. Total capital ────────────────────────────────────────────────
uint totalCap;
for (uint8 i; i < n; i++) totalCap += capitalPerSide[i];
if (totalCap == 0) return INITIAL_B;
// ── 2. Shannon entropy of capital distribution ──────────────────────
// H = -Σ (p_i × ln(p_i)) computed in fixed-point WAD arithmetic.
// We use the AnalyticMath log inlined as _amLog.
// Entropy is accumulated in WAD units; H_max = ln(n) × WAD.
uint H_WAD;
for (uint8 i; i < n; i++) {
if (capitalPerSide[i] == 0) continue;
// p_i = capitalPerSide[i] / totalCap (WAD fraction)
// ln(p_i) = _amLog(capitalPerSide[i], totalCap) → (num, den)
// term = p_i × |ln(p_i)| (entropy term, always positive since p_i ≤ 1)
uint pWAD = FullMath.mulDiv(capitalPerSide[i], WAD, totalCap);
(uint lnN, uint lnD) = _amLog(totalCap, capitalPerSide[i]); // ln(1/p_i) = -ln(p_i)
uint lnTerm = FullMath.mulDiv(lnN, WAD, lnD); // |ln(p_i)| in WAD
H_WAD += FullMath.mulDiv(pWAD, lnTerm, WAD); // p_i × |ln(p_i)|
}
// H_max = ln(n) × WAD
// Use _amLog(n, 1) but we need integer log — use _amLog scaled.
// ln(n): pass (n × WAD, WAD) → gives ln(n) as (num/den) pair.
(uint lnNnum, uint lnNden) = _amLog(uint(n) * WAD, WAD);
uint H_max_WAD = FullMath.mulDiv(lnNnum, WAD, lnNden);
if (H_max_WAD == 0) return INITIAL_B; // n==1 guard (already blocked above)
// entropy_ratio in WAD: 0 → concentrated, WAD → uniform
uint entropyRatioWAD = FullMath.mulDiv(H_WAD, WAD, H_max_WAD);
// clamp to [0, WAD]
if (entropyRatioWAD > WAD) entropyRatioWAD = WAD;
// ── 3. Total-capital scale factor (^0.25, conservative growth) ───────
// total_scale = (totalCap / (INITIAL_B × n)) ^ 0.25
// Compute ratio = totalCap / (INITIAL_B × n) in WAD.
uint denom = uint(uint128(INITIAL_B)) * uint(n); // INITIAL_B is WAD
uint ratioWAD = FullMath.mulDiv(totalCap, WAD, denom);
if (ratioWAD < WAD) ratioWAD = WAD; // floor at 1.0 — never shrink b
// ^0.25 via two successive sqrt (integer Newton's method).
uint scaleWAD = _sqrtWAD(_sqrtWAD(ratioWAD));
// cap scale at 6× → b capped at MAX_B
uint sixWAD = 6 * WAD;
if (scaleWAD > sixWAD) scaleWAD = sixWAD;
// ── 4. b_target = INITIAL_B × (1 + (scale − 1) × entropy_ratio) ────
// excess = (scale − 1) × entropy_ratio (both in WAD)
uint excessWAD = FullMath.mulDiv(scaleWAD - WAD, entropyRatioWAD, WAD);
uint b_target_WAD = uint(uint128(INITIAL_B)) + FullMath.mulDiv(
uint(uint128(INITIAL_B)), excessWAD, WAD
);
// ── 5. Optional EMA smoothing (alpha = 0.3 = 3/10) ──────────────────
uint b_new_WAD;
if (b_prev > 0) {
// b_new = 0.3 × b_target + 0.7 × b_prev
uint bp = uint(uint128(b_prev));
b_new_WAD = (3 * b_target_WAD + 7 * bp) / 10;
} else {
b_new_WAD = b_target_WAD;
}
// floor at INITIAL_B, ceil at MAX_B
if (b_new_WAD < uint(uint128(INITIAL_B))) b_new_WAD = uint(uint128(INITIAL_B));
if (b_new_WAD > uint(uint128(MAX_B))) b_new_WAD = uint(uint128(MAX_B));
b_new = int128(int256(b_new_WAD));
}
/// @dev Integer sqrt on WAD-scaled values. Returns floor(sqrt(x)) in WAD.
/// Input x is a WAD ratio (e.g. 2e18 = 2.0).
/// Output is also WAD (e.g. sqrt(2e18) ≈ 1.414e18).
function _sqrtWAD(uint x) private pure returns (uint) { unchecked {
if (x == 0) return 0;
// Newton's method on x × WAD (to keep WAD scaling through sqrt)
uint y = x * WAD;
uint z = (y + WAD) / 2;
while (z < y) { y = z; z = (y + x * WAD / y) / 2; }
return y / 1e9; // sqrt(x × WAD) / sqrt(WAD) = sqrt(x) in WAD (1e9 = sqrt(1e18))
}}
}// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
import {ERC20} from "./ERC20.sol";
import {SafeTransferLib} from "../utils/SafeTransferLib.sol";
/// @notice Minimalist and modern Wrapped Ether implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/WETH.sol)
/// @author Inspired by WETH9 (https://github.com/dapphub/ds-weth/blob/master/src/weth9.sol)
contract WETH is ERC20("Wrapped Ether", "WETH", 18) {
using SafeTransferLib for address;
event Deposit(address indexed from, uint256 amount);
event Withdrawal(address indexed to, uint256 amount);
function deposit() public payable virtual {
_mint(msg.sender, msg.value);
emit Deposit(msg.sender, msg.value);
}
function withdraw(uint256 amount) public virtual {
_burn(msg.sender, amount);
emit Withdrawal(msg.sender, amount);
msg.sender.safeTransferETH(amount);
}
receive() external payable virtual {
deposit();
}
}// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.5.0;
import "./IUniswapV3PoolImmutables.sol";
import "./IUniswapV3PoolState.sol";
import "./IUniswapV3PoolDerivedState.sol";
import "./IUniswapV3PoolActions.sol";
import "./IUniswapV3PoolOwnerActions.sol";
import "./IUniswapV3PoolEvents.sol";
/// @title The interface for a Uniswap V3 Pool
/// @notice A Uniswap pool facilitates swapping and automated market making between any two assets that strictly conform
/// to the ERC20 specification
/// @dev The pool interface is broken up into many smaller pieces
interface IUniswapV3Pool is
IUniswapV3PoolImmutables,
IUniswapV3PoolState,
IUniswapV3PoolDerivedState,
IUniswapV3PoolActions,
IUniswapV3PoolOwnerActions,
IUniswapV3PoolEvents
{}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 value) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 value) external returns (bool);
}// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.16;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
/**
* @title Optimistic Oracle V3 Interface that callers must use to assert truths about the world.
*/
interface OptimisticOracleV3Interface {
// Struct grouping together the settings related to the escalation manager stored in the assertion.
struct EscalationManagerSettings {
bool arbitrateViaEscalationManager; // False if the DVM is used as an oracle (EscalationManager on True).
bool discardOracle; // False if Oracle result is used for resolving assertion after dispute.
bool validateDisputers; // True if the EM isDisputeAllowed should be checked on disputes.
address assertingCaller; // Stores msg.sender when assertion was made.
address escalationManager; // Address of the escalation manager (zero address if not configured).
}
// Struct for storing properties and lifecycle of an assertion.
struct Assertion {
EscalationManagerSettings escalationManagerSettings; // Settings related to the escalation manager.
address asserter; // Address of the asserter.
uint64 assertionTime; // Time of the assertion.
bool settled; // True if the request is settled.
IERC20 currency; // ERC20 token used to pay rewards and fees.
uint64 expirationTime; // Unix timestamp marking threshold when the assertion can no longer be disputed.
bool settlementResolution; // Resolution of the assertion (false till resolved).
bytes32 domainId; // Optional domain that can be used to relate the assertion to others in the escalationManager.
bytes32 identifier; // UMA DVM identifier to use for price requests in the event of a dispute.
uint256 bond; // Amount of currency that the asserter has bonded.
address callbackRecipient; // Address that receives the callback.
address disputer; // Address of the disputer.
}
// Struct for storing cached currency whitelist.
struct WhitelistedCurrency {
bool isWhitelisted; // True if the currency is whitelisted.
uint256 finalFee; // Final fee of the currency.
}
/**
* @notice Disputes an assertion. Depending on how the assertion was configured, this may either escalate to the UMA
* DVM or the configured escalation manager for arbitration.
* @dev The caller must approve this contract to spend at least bond amount of currency for the associated assertion.
* @param assertionId unique identifier for the assertion to dispute.
* @param disputer receives bonds back at settlement.
*/
function disputeAssertion(bytes32 assertionId, address disputer) external;
/**
* @notice Returns the default identifier used by the Optimistic Oracle V3.
* @return The default identifier.
*/
function defaultIdentifier() external view returns (bytes32);
/**
* @notice Fetches information about a specific assertion and returns it.
* @param assertionId unique identifier for the assertion to fetch information for.
* @return assertion information about the assertion.
*/
function getAssertion(bytes32 assertionId) external view returns (Assertion memory);
/**
* @notice Asserts a truth about the world, using the default currency and liveness. No callback recipient or
* escalation manager is enabled. The caller is expected to provide a bond of finalFee/burnedBondPercentage
* (with burnedBondPercentage set to 50%, the bond is 2x final fee) of the default currency.
* @dev The caller must approve this contract to spend at least the result of getMinimumBond(defaultCurrency).
* @param claim the truth claim being asserted. This is an assertion about the world, and is verified by disputers.
* @param asserter receives bonds back at settlement. This could be msg.sender or
* any other account that the caller wants to receive the bond at settlement time.
* @return assertionId unique identifier for this assertion.
*/
function assertTruthWithDefaults(bytes memory claim, address asserter) external returns (bytes32);
/**
* @notice Asserts a truth about the world, using a fully custom configuration.
* @dev The caller must approve this contract to spend at least bond amount of currency.
* @param claim the truth claim being asserted. This is an assertion about the world, and is verified by disputers.
* @param asserter receives bonds back at settlement. This could be msg.sender or
* any other account that the caller wants to receive the bond at settlement time.
* @param callbackRecipient if configured, this address will receive a function call assertionResolvedCallback and
* assertionDisputedCallback at resolution or dispute respectively. Enables dynamic responses to these events. The
* recipient _must_ implement these callbacks and not revert or the assertion resolution will be blocked.
* @param escalationManager if configured, this address will control escalation properties of the assertion. This
* means a) choosing to arbitrate via the UMA DVM, b) choosing to discard assertions on dispute, or choosing to
* validate disputes. Combining these, the asserter can define their own security properties for the assertion.
* escalationManager also _must_ implement the same callbacks as callbackRecipient.
* @param liveness time to wait before the assertion can be resolved. Assertion can be disputed in this time.
* @param currency bond currency pulled from the caller and held in escrow until the assertion is resolved.
* @param bond amount of currency to pull from the caller and hold in escrow until the assertion is resolved. This
* must be >= getMinimumBond(address(currency)).
* @param identifier UMA DVM identifier to use for price requests in the event of a dispute. Must be pre-approved.
* @param domainId optional domain that can be used to relate this assertion to others in the escalationManager and
* can be used by the configured escalationManager to define custom behavior for groups of assertions. This is
* typically used for "escalation games" by changing bonds or other assertion properties based on the other
* assertions that have come before. If not needed this value should be 0 to save gas.
* @return assertionId unique identifier for this assertion.
*/
function assertTruth(
bytes memory claim,
address asserter,
address callbackRecipient,
address escalationManager,
uint64 liveness,
IERC20 currency,
uint256 bond,
bytes32 identifier,
bytes32 domainId
) external returns (bytes32);
/**
* @notice Fetches information about a specific identifier & currency from the UMA contracts and stores a local copy
* of the information within this contract. This is used to save gas when making assertions as we can avoid an
* external call to the UMA contracts to fetch this.
* @param identifier identifier to fetch information for and store locally.
* @param currency currency to fetch information for and store locally.
*/
function syncUmaParams(bytes32 identifier, address currency) external;
/**
* @notice Resolves an assertion. If the assertion has not been disputed, the assertion is resolved as true and the
* asserter receives the bond. If the assertion has been disputed, the assertion is resolved depending on the oracle
* result. Based on the result, the asserter or disputer receives the bond. If the assertion was disputed then an
* amount of the bond is sent to the UMA Store as an oracle fee based on the burnedBondPercentage. The remainder of
* the bond is returned to the asserter or disputer.
* @param assertionId unique identifier for the assertion to resolve.
*/
function settleAssertion(bytes32 assertionId) external;
/**
* @notice Settles an assertion and returns the resolution.
* @param assertionId unique identifier for the assertion to resolve and return the resolution for.
* @return resolution of the assertion.
*/
function settleAndGetAssertionResult(bytes32 assertionId) external returns (bool);
/**
* @notice Fetches the resolution of a specific assertion and returns it. If the assertion has not been settled then
* this will revert. If the assertion was disputed and configured to discard the oracle resolution return false.
* @param assertionId unique identifier for the assertion to fetch the resolution for.
* @return resolution of the assertion.
*/
function getAssertionResult(bytes32 assertionId) external view returns (bool);
/**
* @notice Returns the minimum bond amount required to make an assertion. This is calculated as the final fee of the
* currency divided by the burnedBondPercentage. If burn percentage is 50% then the min bond is 2x the final fee.
* @param currency currency to calculate the minimum bond for.
* @return minimum bond amount.
*/
function getMinimumBond(address currency) external view returns (uint256);
event AssertionMade(
bytes32 indexed assertionId,
bytes32 domainId,
bytes claim,
address indexed asserter,
address callbackRecipient,
address escalationManager,
address caller,
uint64 expirationTime,
IERC20 currency,
uint256 bond,
bytes32 indexed identifier
);
event AssertionDisputed(bytes32 indexed assertionId, address indexed caller, address indexed disputer);
event AssertionSettled(
bytes32 indexed assertionId,
address indexed bondRecipient,
bool disputed,
bool settlementResolution,
address settleCaller
);
event AdminPropertiesSet(IERC20 defaultCurrency, uint64 defaultLiveness, uint256 burnedBondPercentage);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {RLPDecoder} from "./RLPDecoder.sol";
// Original Author: https://github.com/ethstorage/storage-contracts-v1/blob/main/contracts/RandaoLib.sol
// Functionality: Verification and extraction of RANDAO (mixHash) value from an RLP-encoded block header.
library RandaoLib {
using RLPDecoder for RLPDecoder.RLPItem;
using RLPDecoder for RLPDecoder.Iterator;
using RLPDecoder for bytes;
// Extracts the RANDAO value from the provided RLP-encoded header
function getRandaoFromHeader(RLPDecoder.RLPItem memory item) pure internal returns (bytes32) {
RLPDecoder.Iterator memory iterator = item.iterator();
for (uint i = 0; i < 13; i++) {
iterator.next(); // mixHash is at item 13 (0-base index)
}
return bytes32(iterator.next().toUint());
}
// Verifies the provided RLP-encoded block header matches the known hash of that header
function verifyHeaderAndGetRandao(bytes32 headerHash, bytes memory headerRlpBytes) pure internal returns (bytes32) {
RLPDecoder.RLPItem memory item = headerRlpBytes.toRlpItem();
require(headerHash == item.rlpBytesKeccak256(), "header hash mismatch");
return getRandaoFromHeader(item);
}
// Given a block number and the block header in RLP bytes, returns the RANDAO value after proving it's correct.
function getHistoricalRandaoValue(uint blockNumber, bytes memory headerRlpBytes) view internal returns (bytes32) {
bytes32 bh = blockhash(blockNumber); // up to 256 in the past I think?
require(bh != bytes32(0), "failed to obtain blockhash");
return verifyHeaderAndGetRandao(bh, headerRlpBytes);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/ERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "./IERC20.sol";
import {IERC20Metadata} from "./extensions/IERC20Metadata.sol";
import {Context} from "../../utils/Context.sol";
import {IERC20Errors} from "../../interfaces/draft-IERC6093.sol";
/**
* @dev Implementation of the {IERC20} interface.
*
* This implementation is agnostic to the way tokens are created. This means
* that a supply mechanism has to be added in a derived contract using {_mint}.
*
* TIP: For a detailed writeup see our guide
* https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
* to implement supply mechanisms].
*
* The default value of {decimals} is 18. To change this, you should override
* this function so it returns a different value.
*
* We have followed general OpenZeppelin Contracts guidelines: functions revert
* instead returning `false` on failure. This behavior is nonetheless
* conventional and does not conflict with the expectations of ERC20
* applications.
*
* Additionally, an {Approval} event is emitted on calls to {transferFrom}.
* This allows applications to reconstruct the allowance for all accounts just
* by listening to said events. Other implementations of the EIP may not emit
* these events, as it isn't required by the specification.
*/
abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {
mapping(address account => uint256) private _balances;
mapping(address account => mapping(address spender => uint256)) private _allowances;
uint256 private _totalSupply;
string private _name;
string private _symbol;
/**
* @dev Sets the values for {name} and {symbol}.
*
* All two of these values are immutable: they can only be set once during
* construction.
*/
constructor(string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
}
/**
* @dev Returns the name of the token.
*/
function name() public view virtual returns (string memory) {
return _name;
}
/**
* @dev Returns the symbol of the token, usually a shorter version of the
* name.
*/
function symbol() public view virtual returns (string memory) {
return _symbol;
}
/**
* @dev Returns the number of decimals used to get its user representation.
* For example, if `decimals` equals `2`, a balance of `505` tokens should
* be displayed to a user as `5.05` (`505 / 10 ** 2`).
*
* Tokens usually opt for a value of 18, imitating the relationship between
* Ether and Wei. This is the default value returned by this function, unless
* it's overridden.
*
* NOTE: This information is only used for _display_ purposes: it in
* no way affects any of the arithmetic of the contract, including
* {IERC20-balanceOf} and {IERC20-transfer}.
*/
function decimals() public view virtual returns (uint8) {
return 18;
}
/**
* @dev See {IERC20-totalSupply}.
*/
function totalSupply() public view virtual returns (uint256) {
return _totalSupply;
}
/**
* @dev See {IERC20-balanceOf}.
*/
function balanceOf(address account) public view virtual returns (uint256) {
return _balances[account];
}
/**
* @dev See {IERC20-transfer}.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - the caller must have a balance of at least `value`.
*/
function transfer(address to, uint256 value) public virtual returns (bool) {
address owner = _msgSender();
_transfer(owner, to, value);
return true;
}
/**
* @dev See {IERC20-allowance}.
*/
function allowance(address owner, address spender) public view virtual returns (uint256) {
return _allowances[owner][spender];
}
/**
* @dev See {IERC20-approve}.
*
* NOTE: If `value` is the maximum `uint256`, the allowance is not updated on
* `transferFrom`. This is semantically equivalent to an infinite approval.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function approve(address spender, uint256 value) public virtual returns (bool) {
address owner = _msgSender();
_approve(owner, spender, value);
return true;
}
/**
* @dev See {IERC20-transferFrom}.
*
* Emits an {Approval} event indicating the updated allowance. This is not
* required by the EIP. See the note at the beginning of {ERC20}.
*
* NOTE: Does not update the allowance if the current allowance
* is the maximum `uint256`.
*
* Requirements:
*
* - `from` and `to` cannot be the zero address.
* - `from` must have a balance of at least `value`.
* - the caller must have allowance for ``from``'s tokens of at least
* `value`.
*/
function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {
address spender = _msgSender();
_spendAllowance(from, spender, value);
_transfer(from, to, value);
return true;
}
/**
* @dev Moves a `value` amount of tokens from `from` to `to`.
*
* This internal function is equivalent to {transfer}, and can be used to
* e.g. implement automatic token fees, slashing mechanisms, etc.
*
* Emits a {Transfer} event.
*
* NOTE: This function is not virtual, {_update} should be overridden instead.
*/
function _transfer(address from, address to, uint256 value) internal {
if (from == address(0)) {
revert ERC20InvalidSender(address(0));
}
if (to == address(0)) {
revert ERC20InvalidReceiver(address(0));
}
_update(from, to, value);
}
/**
* @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`
* (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding
* this function.
*
* Emits a {Transfer} event.
*/
function _update(address from, address to, uint256 value) internal virtual {
if (from == address(0)) {
// Overflow check required: The rest of the code assumes that totalSupply never overflows
_totalSupply += value;
} else {
uint256 fromBalance = _balances[from];
if (fromBalance < value) {
revert ERC20InsufficientBalance(from, fromBalance, value);
}
unchecked {
// Overflow not possible: value <= fromBalance <= totalSupply.
_balances[from] = fromBalance - value;
}
}
if (to == address(0)) {
unchecked {
// Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.
_totalSupply -= value;
}
} else {
unchecked {
// Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.
_balances[to] += value;
}
}
emit Transfer(from, to, value);
}
/**
* @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).
* Relies on the `_update` mechanism
*
* Emits a {Transfer} event with `from` set to the zero address.
*
* NOTE: This function is not virtual, {_update} should be overridden instead.
*/
function _mint(address account, uint256 value) internal {
if (account == address(0)) {
revert ERC20InvalidReceiver(address(0));
}
_update(address(0), account, value);
}
/**
* @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.
* Relies on the `_update` mechanism.
*
* Emits a {Transfer} event with `to` set to the zero address.
*
* NOTE: This function is not virtual, {_update} should be overridden instead
*/
function _burn(address account, uint256 value) internal {
if (account == address(0)) {
revert ERC20InvalidSender(address(0));
}
_update(account, address(0), value);
}
/**
* @dev Sets `value` as the allowance of `spender` over the `owner` s tokens.
*
* This internal function is equivalent to `approve`, and can be used to
* e.g. set automatic allowances for certain subsystems, etc.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `owner` cannot be the zero address.
* - `spender` cannot be the zero address.
*
* Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.
*/
function _approve(address owner, address spender, uint256 value) internal {
_approve(owner, spender, value, true);
}
/**
* @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.
*
* By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by
* `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any
* `Approval` event during `transferFrom` operations.
*
* Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to
* true using the following override:
* ```
* function _approve(address owner, address spender, uint256 value, bool) internal virtual override {
* super._approve(owner, spender, value, true);
* }
* ```
*
* Requirements are the same as {_approve}.
*/
function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {
if (owner == address(0)) {
revert ERC20InvalidApprover(address(0));
}
if (spender == address(0)) {
revert ERC20InvalidSpender(address(0));
}
_allowances[owner][spender] = value;
if (emitEvent) {
emit Approval(owner, spender, value);
}
}
/**
* @dev Updates `owner` s allowance for `spender` based on spent `value`.
*
* Does not update the allowance value in case of infinite allowance.
* Revert if not enough allowance is available.
*
* Does not emit an {Approval} event.
*/
function _spendAllowance(address owner, address spender, uint256 value) internal virtual {
uint256 currentAllowance = allowance(owner, spender);
if (currentAllowance != type(uint256).max) {
if (currentAllowance < value) {
revert ERC20InsufficientAllowance(spender, currentAllowance, value);
}
unchecked {
_approve(owner, spender, currentAllowance - value, false);
}
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import { OApp, Origin } from "./oapp/OApp.sol";
import { OAppOptionsType3 } from "./oapp/libs/OAppOptionsType3.sol";
import { IOAppMsgInspector } from "./oapp/interfaces/IOAppMsgInspector.sol";
import { OAppPreCrimeSimulator } from "./oapp/OAppPreCrimeSimulator.sol";
import { IOFT, SendParam, OFTLimit, OFTReceipt, OFTFeeDetail, MessagingReceipt, MessagingFee } from "./oapp/interfaces/IOFT.sol";
import { OFTMsgCodec } from "./oapp/libs/OFTMsgCodec.sol";
import { OFTComposeMsgCodec } from "./oapp/libs/OFTComposeMsgCodec.sol";
/**
* @title OFTCore
* @dev Abstract contract for the OftChain (OFT) token.
*/
abstract contract OFTCore is IOFT, OApp, OAppPreCrimeSimulator, OAppOptionsType3 {
using OFTMsgCodec for bytes;
using OFTMsgCodec for bytes32;
// @notice Provides a conversion rate when swapping between denominations of SD and LD
// - shareDecimals == SD == shared Decimals
// - localDecimals == LD == local decimals
// @dev Considers that tokens have different decimal amounts on various chains.
// @dev eg.
// For a token
// - locally with 4 decimals --> 1.2345 => uint(12345)
// - remotely with 2 decimals --> 1.23 => uint(123)
// - The conversion rate would be 10 ** (4 - 2) = 100
// @dev If you want to send 1.2345 -> (uint 12345), you CANNOT represent that value on the remote,
// you can only display 1.23 -> uint(123).
// @dev To preserve the dust that would otherwise be lost on that conversion,
// we need to unify a denomination that can be represented on ALL chains inside of the OFT mesh
uint256 public immutable decimalConversionRate;
// @notice Msg types that are used to identify the various OFT operations.
// @dev This can be extended in child contracts for non-default oft operations
// @dev These values are used in things like combineOptions() in OAppOptionsType3.sol.
uint16 public constant SEND = 1;
uint16 public constant SEND_AND_CALL = 2;
// Address of an optional contract to inspect both 'message' and 'options'
address public msgInspector;
event MsgInspectorSet(address inspector);
/**
* @dev Constructor.
* @param _localDecimals The decimals of the token on the local chain (this chain).
* @param _endpoint The address of the LayerZero endpoint.
* @param _delegate The delegate capable of making OApp configurations inside of the endpoint.
*/
constructor(uint8 _localDecimals, address _endpoint, address _delegate) OApp(_endpoint, _delegate) {
if (_localDecimals < sharedDecimals()) revert InvalidLocalDecimals();
decimalConversionRate = 10 ** (_localDecimals - sharedDecimals());
}
/**
* @notice Retrieves interfaceID and the version of the OFT.
* @return interfaceId The interface ID.
* @return version The version.
*
* @dev interfaceId: This specific interface ID is '0x02e49c2c'.
* @dev version: Indicates a cross-chain compatible msg encoding with other OFTs.
* @dev If a new feature is added to the OFT cross-chain msg encoding, the version will be incremented.
* ie. localOFT version(x,1) CAN send messages to remoteOFT version(x,1)
*/
function oftVersion() external pure virtual returns (bytes4 interfaceId, uint64 version) {
return (type(IOFT).interfaceId, 1);
}
/**
* @dev Retrieves the shared decimals of the OFT.
* @return The shared decimals of the OFT.
*
* @dev Sets an implicit cap on the amount of tokens, over uint64.max() will need some sort of outbound cap / totalSupply cap
* Lowest common decimal denominator between chains.
* Defaults to 6 decimal places to provide up to 18,446,744,073,709.551615 units (max uint64).
* For tokens exceeding this totalSupply(), they will need to override the sharedDecimals function with something smaller.
* ie. 4 sharedDecimals would be 1,844,674,407,370,955.1615
*/
function sharedDecimals() public pure virtual returns (uint8) {
return 6;
}
/**
* @dev Sets the message inspector address for the OFT.
* @param _msgInspector The address of the message inspector.
*
* @dev This is an optional contract that can be used to inspect both 'message' and 'options'.
* @dev Set it to address(0) to disable it, or set it to a contract address to enable it.
*/
function setMsgInspector(address _msgInspector) public virtual onlyOwner {
msgInspector = _msgInspector;
emit MsgInspectorSet(_msgInspector);
}
/**
* @notice Provides a quote for OFT-related operations.
* @param _sendParam The parameters for the send operation.
* @return oftLimit The OFT limit information.
* @return oftFeeDetails The details of OFT fees.
* @return oftReceipt The OFT receipt information.
*/
function quoteOFT(
SendParam calldata _sendParam
)
external
view
virtual
returns (OFTLimit memory oftLimit, OFTFeeDetail[] memory oftFeeDetails, OFTReceipt memory oftReceipt)
{
uint256 minAmountLD = 0; // Unused in the default implementation.
uint256 maxAmountLD = type(uint64).max; // Unused in the default implementation.
oftLimit = OFTLimit(minAmountLD, maxAmountLD);
// Unused in the default implementation; reserved for future complex fee details.
oftFeeDetails = new OFTFeeDetail[](0);
// @dev This is the same as the send() operation, but without the actual send.
// - amountSentLD is the amount in local decimals that would be sent from the sender.
// - amountReceivedLD is the amount in local decimals that will be credited to the recipient on the remote OFT instance.
// @dev The amountSentLD MIGHT not equal the amount the user actually receives. HOWEVER, the default does.
(uint256 amountSentLD, uint256 amountReceivedLD) = _debitView(
_sendParam.amountLD,
_sendParam.minAmountLD,
_sendParam.dstEid
);
oftReceipt = OFTReceipt(amountSentLD, amountReceivedLD);
}
/**
* @notice Provides a quote for the send() operation.
* @param _sendParam The parameters for the send() operation.
* @param _payInLzToken Flag indicating whether the caller is paying in the LZ token.
* @return msgFee The calculated LayerZero messaging fee from the send() operation.
*
* @dev MessagingFee: LayerZero msg fee
* - nativeFee: The native fee.
* - lzTokenFee: The lzToken fee.
*/
function quoteSend(
SendParam calldata _sendParam,
bool _payInLzToken
) external view virtual returns (MessagingFee memory msgFee) {
// @dev mock the amount to receive, this is the same operation used in the send().
// The quote is as similar as possible to the actual send() operation.
(, uint256 amountReceivedLD) = _debitView(_sendParam.amountLD, _sendParam.minAmountLD, _sendParam.dstEid);
// @dev Builds the options and OFT message to quote in the endpoint.
(bytes memory message, bytes memory options) = _buildMsgAndOptions(_sendParam, amountReceivedLD);
// @dev Calculates the LayerZero fee for the send() operation.
return _quote(_sendParam.dstEid, message, options, _payInLzToken);
}
/**
* @dev Executes the send operation.
* @param _sendParam The parameters for the send operation.
* @param _fee The calculated fee for the send() operation.
* - nativeFee: The native fee.
* - lzTokenFee: The lzToken fee.
* @param _refundAddress The address to receive any excess funds.
* @return msgReceipt The receipt for the send operation.
* @return oftReceipt The OFT receipt information.
*
* @dev MessagingReceipt: LayerZero msg receipt
* - guid: The unique identifier for the sent message.
* - nonce: The nonce of the sent message.
* - fee: The LayerZero fee incurred for the message.
*/
function send(
SendParam calldata _sendParam,
MessagingFee calldata _fee,
address _refundAddress
) external payable virtual returns (MessagingReceipt memory msgReceipt, OFTReceipt memory oftReceipt) {
// @dev Applies the token transfers regarding this send() operation.
// - amountSentLD is the amount in local decimals that was ACTUALLY sent/debited from the sender.
// - amountReceivedLD is the amount in local decimals that will be received/credited to the recipient on the remote OFT instance.
(uint256 amountSentLD, uint256 amountReceivedLD) = _debit(
_sendParam.amountLD,
_sendParam.minAmountLD,
_sendParam.dstEid
);
// @dev Builds the options and OFT message to quote in the endpoint.
(bytes memory message, bytes memory options) = _buildMsgAndOptions(_sendParam, amountReceivedLD);
// @dev Sends the message to the LayerZero endpoint and returns the LayerZero msg receipt.
msgReceipt = _lzSend(_sendParam.dstEid, message, options, _fee, _refundAddress);
// @dev Formulate the OFT receipt.
oftReceipt = OFTReceipt(amountSentLD, amountReceivedLD);
emit OFTSent(msgReceipt.guid, _sendParam.dstEid, msg.sender, amountSentLD, amountReceivedLD);
}
/**
* @dev Internal function to build the message and options.
* @param _sendParam The parameters for the send() operation.
* @param _amountLD The amount in local decimals.
* @return message The encoded message.
* @return options The encoded options.
*/
function _buildMsgAndOptions(
SendParam calldata _sendParam,
uint256 _amountLD
) internal view virtual returns (bytes memory message, bytes memory options) {
bool hasCompose;
// @dev This generated message has the msg.sender encoded into the payload so the remote knows who the caller is.
(message, hasCompose) = OFTMsgCodec.encode(
_sendParam.to,
_toSD(_amountLD),
// @dev Must be include a non empty bytes if you want to compose, EVEN if you dont need it on the remote.
// EVEN if you dont require an arbitrary payload to be sent... eg. '0x01'
_sendParam.composeMsg
);
// @dev Change the msg type depending if its composed or not.
uint16 msgType = hasCompose ? SEND_AND_CALL : SEND;
// @dev Combine the callers _extraOptions with the enforced options via the OAppOptionsType3.
options = combineOptions(_sendParam.dstEid, msgType, _sendParam.extraOptions);
// @dev Optionally inspect the message and options depending if the OApp owner has set a msg inspector.
// @dev If it fails inspection, needs to revert in the implementation. ie. does not rely on return boolean
if (msgInspector != address(0)) IOAppMsgInspector(msgInspector).inspect(message, options);
}
/**
* @dev Internal function to handle the receive on the LayerZero endpoint.
* @param _origin The origin information.
* - srcEid: The source chain endpoint ID.
* - sender: The sender address from the src chain.
* - nonce: The nonce of the LayerZero message.
* @param _guid The unique identifier for the received LayerZero message.
* @param _message The encoded message.
* @dev _executor The address of the executor.
* @dev _extraData Additional data.
*/
function _lzReceive(
Origin calldata _origin,
bytes32 _guid,
bytes calldata _message,
address /*_executor*/, // @dev unused in the default implementation.
bytes calldata /*_extraData*/ // @dev unused in the default implementation.
) internal virtual override {
// @dev The src sending chain doesnt know the address length on this chain (potentially non-evm)
// Thus everything is bytes32() encoded in flight.
address toAddress = _message.sendTo().bytes32ToAddress();
// @dev Credit the amountLD to the recipient and return the ACTUAL amount the recipient received in local decimals
uint256 amountReceivedLD = _credit(toAddress, _toLD(_message.amountSD()), _origin.srcEid);
if (_message.isComposed()) {
// @dev Proprietary composeMsg format for the OFT.
bytes memory composeMsg = OFTComposeMsgCodec.encode(
_origin.nonce,
_origin.srcEid,
amountReceivedLD,
_message.composeMsg()
);
// @dev Stores the lzCompose payload that will be executed in a separate tx.
// Standardizes functionality for executing arbitrary contract invocation on some non-evm chains.
// @dev The off-chain executor will listen and process the msg based on the src-chain-callers compose options passed.
// @dev The index is used when a OApp needs to compose multiple msgs on lzReceive.
// For default OFT implementation there is only 1 compose msg per lzReceive, thus its always 0.
endpoint.sendCompose(toAddress, _guid, 0 /* the index of the composed message*/, composeMsg);
}
emit OFTReceived(_guid, _origin.srcEid, toAddress, amountReceivedLD);
}
/**
* @dev Internal function to handle the OAppPreCrimeSimulator simulated receive.
* @param _origin The origin information.
* - srcEid: The source chain endpoint ID.
* - sender: The sender address from the src chain.
* - nonce: The nonce of the LayerZero message.
* @param _guid The unique identifier for the received LayerZero message.
* @param _message The LayerZero message.
* @param _executor The address of the off-chain executor.
* @param _extraData Arbitrary data passed by the msg executor.
*
* @dev Enables the preCrime simulator to mock sending lzReceive() messages,
* routes the msg down from the OAppPreCrimeSimulator, and back up to the OAppReceiver.
*/
function _lzReceiveSimulate(
Origin calldata _origin,
bytes32 _guid,
bytes calldata _message,
address _executor,
bytes calldata _extraData
) internal virtual override {
_lzReceive(_origin, _guid, _message, _executor, _extraData);
}
/**
* @dev Check if the peer is considered 'trusted' by the OApp.
* @param _eid The endpoint ID to check.
* @param _peer The peer to check.
* @return Whether the peer passed is considered 'trusted' by the OApp.
*
* @dev Enables OAppPreCrimeSimulator to check whether a potential Inbound Packet is from a trusted source.
*/
function isPeer(uint32 _eid, bytes32 _peer) public view virtual override returns (bool) {
return peers[_eid] == _peer;
}
/**
* @dev Internal function to remove dust from the given local decimal amount.
* @param _amountLD The amount in local decimals.
* @return amountLD The amount after removing dust.
*
* @dev Prevents the loss of dust when moving amounts between chains with different decimals.
* @dev eg. uint(123) with a conversion rate of 100 becomes uint(100).
*/
function _removeDust(uint256 _amountLD) internal view virtual returns (uint256 amountLD) {
return (_amountLD / decimalConversionRate) * decimalConversionRate;
}
/**
* @dev Internal function to convert an amount from shared decimals into local decimals.
* @param _amountSD The amount in shared decimals.
* @return amountLD The amount in local decimals.
*/
function _toLD(uint64 _amountSD) internal view virtual returns (uint256 amountLD) {
return _amountSD * decimalConversionRate;
}
/**
* @dev Internal function to convert an amount from local decimals into shared decimals.
* @param _amountLD The amount in local decimals.
* @return amountSD The amount in shared decimals.
*/
function _toSD(uint256 _amountLD) internal view virtual returns (uint64 amountSD) {
return uint64(_amountLD / decimalConversionRate);
}
/**
* @dev Internal function to mock the amount mutation from a OFT debit() operation.
* @param _amountLD The amount to send in local decimals.
* @param _minAmountLD The minimum amount to send in local decimals.
* @dev _dstEid The destination endpoint ID.
* @return amountSentLD The amount sent, in local decimals.
* @return amountReceivedLD The amount to be received on the remote chain, in local decimals.
*
* @dev This is where things like fees would be calculated and deducted from the amount to be received on the remote.
*/
function _debitView(
uint256 _amountLD,
uint256 _minAmountLD,
uint32 /*_dstEid*/
) internal view virtual returns (uint256 amountSentLD, uint256 amountReceivedLD) {
// @dev Remove the dust so nothing is lost on the conversion between chains with different decimals for the token.
amountSentLD = _removeDust(_amountLD);
// @dev The amount to send is the same as amount received in the default implementation.
amountReceivedLD = amountSentLD;
// @dev Check for slippage.
if (amountReceivedLD < _minAmountLD) {
revert SlippageExceeded(amountReceivedLD, _minAmountLD);
}
}
/**
* @dev Internal function to perform a debit operation.
* @param _amountLD The amount to send in local decimals.
* @param _minAmountLD The minimum amount to send in local decimals.
* @param _dstEid The destination endpoint ID.
* @return amountSentLD The amount sent in local decimals.
* @return amountReceivedLD The amount received in local decimals on the remote.
*
* @dev Defined here but are intended to be overriden depending on the OFT implementation.
* @dev Depending on OFT implementation the _amountLD could differ from the amountReceivedLD.
*/
function _debit(
uint256 _amountLD,
uint256 _minAmountLD,
uint32 _dstEid
) internal virtual returns (uint256 amountSentLD, uint256 amountReceivedLD);
/**
* @dev Internal function to perform a credit operation.
* @param _to The address to credit.
* @param _amountLD The amount to credit in local decimals.
* @param _srcEid The source endpoint ID.
* @return amountReceivedLD The amount ACTUALLY received in local decimals.
*
* @dev Defined here but are intended to be overriden depending on the OFT implementation.
* @dev Depending on OFT implementation the _amountLD could differ from the amountReceivedLD.
*/
function _credit(
address _to,
uint256 _amountLD,
uint32 _srcEid
) internal virtual returns (uint256 amountReceivedLD);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import { IOAppReceiver, Origin } from "./interfaces/IOAppReceiver.sol";
import { OAppCore } from "./OAppCore.sol";
/**
* @title OAppReceiver
* @dev Abstract contract implementing the ILayerZeroReceiver interface and extending OAppCore for OApp receivers.
*/
abstract contract OAppReceiver is IOAppReceiver, OAppCore {
// Custom error message for when the caller is not the registered endpoint/
error OnlyEndpoint(address addr);
// @dev The version of the OAppReceiver implementation.
// @dev Version is bumped when changes are made to this contract.
uint64 internal constant RECEIVER_VERSION = 1;
/**
* @notice Retrieves the OApp version information.
* @return senderVersion The version of the OAppSender.sol contract.
* @return receiverVersion The version of the OAppReceiver.sol contract.
*
* @dev Providing 0 as the default for OAppSender version. Indicates that the OAppSender is not implemented.
* ie. this is a RECEIVE only OApp.
* @dev If the OApp uses both OAppSender and OAppReceiver, then this needs to be override returning the correct versions.
*/
function oAppVersion() public view virtual returns (uint64 senderVersion, uint64 receiverVersion) {
return (0, RECEIVER_VERSION);
}
/**
* @notice Retrieves the address responsible for 'sending' composeMsg's to the Endpoint.
* @return sender The address responsible for 'sending' composeMsg's to the Endpoint.
*
* @dev Applications can optionally choose to implement a separate composeMsg sender that is NOT the bridging layer.
* @dev The default sender IS the OApp implementer.
*/
function composeMsgSender() public view virtual returns (address sender) {
return address(this);
}
/**
* @notice Checks if the path initialization is allowed based on the provided origin.
* @param origin The origin information containing the source endpoint and sender address.
* @return Whether the path has been initialized.
*
* @dev This indicates to the endpoint that the OApp has enabled msgs for this particular path to be received.
* @dev This defaults to assuming if a peer has been set, its initialized.
* Can be overridden by the OApp if there is other logic to determine this.
*/
function allowInitializePath(Origin calldata origin) public view virtual returns (bool) {
return peers[origin.srcEid] == origin.sender;
}
/**
* @notice Retrieves the next nonce for a given source endpoint and sender address.
* @dev _srcEid The source endpoint ID.
* @dev _sender The sender address.
* @return nonce The next nonce.
*
* @dev The path nonce starts from 1. If 0 is returned it means that there is NO nonce ordered enforcement.
* @dev Is required by the off-chain executor to determine the OApp expects msg execution is ordered.
* @dev This is also enforced by the OApp.
* @dev By default this is NOT enabled. ie. nextNonce is hardcoded to return 0.
*/
function nextNonce(uint32 /*_srcEid*/, bytes32 /*_sender*/) public view virtual returns (uint64 nonce) {
return 0;
}
/**
* @dev Entry point for receiving messages or packets from the endpoint.
* @param _origin The origin information containing the source endpoint and sender address.
* - srcEid: The source chain endpoint ID.
* - sender: The sender address on the src chain.
* - nonce: The nonce of the message.
* @param _guid The unique identifier for the received LayerZero message.
* @param _message The payload of the received message.
* @param _executor The address of the executor for the received message.
* @param _extraData Additional arbitrary data provided by the corresponding executor.
*
* @dev Entry point for receiving msg/packet from the LayerZero endpoint.
*/
function lzReceive(
Origin calldata _origin,
bytes32 _guid,
bytes calldata _message,
address _executor,
bytes calldata _extraData
) public payable virtual {
// Ensures that only the endpoint can attempt to lzReceive() messages to this OApp.
if (address(endpoint) != msg.sender) revert OnlyEndpoint(msg.sender);
// Ensure that the sender matches the expected peer for the source endpoint.
if (_getPeerOrRevert(_origin.srcEid) != _origin.sender) revert OnlyPeer(_origin.srcEid, _origin.sender);
// Call the internal OApp implementation of lzReceive.
_lzReceive(_origin, _guid, _message, _executor, _extraData);
}
/**
* @dev Internal function to implement lzReceive logic without needing to copy the basic parameter validation.
*/
function _lzReceive(
Origin calldata _origin,
bytes32 _guid,
bytes calldata _message,
address _executor,
bytes calldata _extraData
) internal virtual;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { IOAppCore, ILayerZeroEndpointV2 } from "./interfaces/IOAppCore.sol";
/**
* @title OAppCore
* @dev Abstract contract implementing the IOAppCore interface with basic OApp configurations.
*/
abstract contract OAppCore is IOAppCore, Ownable {
// The LayerZero endpoint associated with the given OApp
ILayerZeroEndpointV2 public immutable endpoint;
// Mapping to store peers associated with corresponding endpoints
mapping(uint32 eid => bytes32 peer) public peers;
/**
* @dev Constructor to initialize the OAppCore with the provided endpoint and delegate.
* @param _endpoint The address of the LOCAL Layer Zero endpoint.
* @param _delegate The delegate capable of making OApp configurations inside of the endpoint.
*
* @dev The delegate typically should be set as the owner of the contract.
*/
constructor(address _endpoint, address _delegate) {
endpoint = ILayerZeroEndpointV2(_endpoint);
if (_delegate == address(0)) revert InvalidDelegate();
endpoint.setDelegate(_delegate);
}
/**
* @notice Sets the peer address (OApp instance) for a corresponding endpoint.
* @param _eid The endpoint ID.
* @param _peer The address of the peer to be associated with the corresponding endpoint.
*
* @dev Only the owner/admin of the OApp can call this function.
* @dev Indicates that the peer is trusted to send LayerZero messages to this OApp.
* @dev Set this to bytes32(0) to remove the peer address.
* @dev Peer is a bytes32 to accommodate non-evm chains.
*/
function setPeer(uint32 _eid, bytes32 _peer) public virtual onlyOwner {
peers[_eid] = _peer;
emit PeerSet(_eid, _peer);
}
/**
* @notice Internal function to get the peer address associated with a specific endpoint; reverts if NOT set.
* ie. the peer is set to bytes32(0).
* @param _eid The endpoint ID.
* @return peer The address of the peer associated with the specified endpoint.
*/
function _getPeerOrRevert(uint32 _eid) internal view virtual returns (bytes32) {
bytes32 peer = peers[_eid];
if (peer == bytes32(0)) revert NoPeer(_eid);
return peer;
}
/**
* @notice Sets the delegate address for the OApp.
* @param _delegate The address of the delegate to be set.
*
* @dev Only the owner/admin of the OApp can call this function.
* @dev Provides the ability for a delegate to set configs, on behalf of the OApp, directly on the Endpoint contract.
*/
function setDelegate(address _delegate) public onlyOwner {
endpoint.setDelegate(_delegate);
}
}// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.7.5;
pragma abicoder v2;
/// @title Callback for IUniswapV3PoolActions#swap
/// @notice Any contract that calls IUniswapV3PoolActions#swap must implement this interface
interface IUniswapV3SwapCallback {
/// @notice Called to `msg.sender` after executing a swap via IUniswapV3Pool#swap.
/// @dev In the implementation you must pay the pool tokens owed for the swap.
/// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory.
/// amount0Delta and amount1Delta can both be 0 if no tokens were swapped.
/// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by
/// the end of the swap. If positive, the callback must send that amount of token0 to the pool.
/// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by
/// the end of the swap. If positive, the callback must send that amount of token1 to the pool.
/// @param data Any data passed through by the caller via the IUniswapV3PoolActions#swap call
function uniswapV3SwapCallback(
int256 amount0Delta,
int256 amount1Delta,
bytes calldata data
) external;
}
/// @title Router token swapping functionality
/// @notice Functions for swapping tokens via Uniswap V3
interface IV3SwapRouter is IUniswapV3SwapCallback {
struct ExactInputSingleParams {
address tokenIn;
address tokenOut;
uint24 fee;
address recipient;
uint amountIn;
uint amountOutMinimum;
uint160 sqrtPriceLimitX96;
}
/// @notice Swaps `amountIn` of one token for as much as possible of another token
/// @dev Setting `amountIn` to 0 will cause the contract to look up its own balance,
/// and swap the entire amount, enabling contracts to send tokens before calling this function.
/// @param params The parameters necessary for the swap, encoded as `ExactInputSingleParams` in calldata
/// @return amountOut The amount of the received token
function exactInputSingle(ExactInputSingleParams calldata params) external payable returns (uint amountOut);
struct ExactInputParams {
bytes path;
address recipient;
uint amountIn;
uint amountOutMinimum;
}
/// @notice Swaps `amountIn` of one token for as much as possible of another along the specified path
/// @dev Setting `amountIn` to 0 will cause the contract to look up its own balance,
/// and swap the entire amount, enabling contracts to send tokens before calling this function.
/// @param params The parameters necessary for the multi-hop swap, encoded as `ExactInputParams` in calldata
/// @return amountOut The amount of the received token
function exactInput(ExactInputParams calldata params) external payable returns (uint amountOut);
struct ExactOutputSingleParams {
address tokenIn;
address tokenOut;
uint24 fee;
address recipient;
uint amountOut;
uint amountInMaximum;
uint160 sqrtPriceLimitX96;
}
/// @notice Swaps as little as possible of one token for `amountOut` of another token
/// that may remain in the router after the swap.
/// @param params The parameters necessary for the swap, encoded as `ExactOutputSingleParams` in calldata
/// @return amountIn The amount of the input token
function exactOutputSingle(ExactOutputSingleParams calldata params) external payable returns (uint amountIn);
struct ExactOutputParams {
bytes path;
address recipient;
uint amountOut;
uint amountInMaximum;
}
/// @notice Swaps as little as possible of one token for `amountOut` of another along the specified path (reversed)
/// that may remain in the router after the swap.
/// @param params The parameters necessary for the multi-hop swap, encoded as `ExactOutputParams` in calldata
/// @return amountIn The amount of the input token
function exactOutput(ExactOutputParams calldata params) external payable returns (uint amountIn);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {BitMath} from "./BitMath.sol";
import {CustomRevert} from "./CustomRevert.sol";
/// @title Math library for computing sqrt prices from ticks and vice versa
/// @notice Computes sqrt price for ticks of size 1.0001, i.e. sqrt(1.0001^tick) as fixed point Q64.96 numbers. Supports
/// prices between 2**-128 and 2**128
library TickMath {
using CustomRevert for bytes4;
/// @notice Thrown when the tick passed to #getSqrtPriceAtTick is not between MIN_TICK and MAX_TICK
error InvalidTick(int24 tick);
/// @notice Thrown when the price passed to #getTickAtSqrtPrice does not correspond to a price between MIN_TICK and MAX_TICK
error InvalidSqrtPrice(uint160 sqrtPriceX96);
/// @dev The minimum tick that may be passed to #getSqrtPriceAtTick computed from log base 1.0001 of 2**-128
/// @dev If ever MIN_TICK and MAX_TICK are not centered around 0, the absTick logic in getSqrtPriceAtTick cannot be used
int24 internal constant MIN_TICK = -887272;
/// @dev The maximum tick that may be passed to #getSqrtPriceAtTick computed from log base 1.0001 of 2**128
/// @dev If ever MIN_TICK and MAX_TICK are not centered around 0, the absTick logic in getSqrtPriceAtTick cannot be used
int24 internal constant MAX_TICK = 887272;
/// @dev The minimum tick spacing value drawn from the range of type int16 that is greater than 0, i.e. min from the range [1, 32767]
int24 internal constant MIN_TICK_SPACING = 1;
/// @dev The maximum tick spacing value drawn from the range of type int16, i.e. max from the range [1, 32767]
int24 internal constant MAX_TICK_SPACING = type(int16).max;
/// @dev The minimum value that can be returned from #getSqrtPriceAtTick. Equivalent to getSqrtPriceAtTick(MIN_TICK)
uint160 internal constant MIN_SQRT_PRICE = 4295128739;
/// @dev The maximum value that can be returned from #getSqrtPriceAtTick. Equivalent to getSqrtPriceAtTick(MAX_TICK)
uint160 internal constant MAX_SQRT_PRICE = 1461446703485210103287273052203988822378723970342;
/// @dev A threshold used for optimized bounds check, equals `MAX_SQRT_PRICE - MIN_SQRT_PRICE - 1`
uint160 internal constant MAX_SQRT_PRICE_MINUS_MIN_SQRT_PRICE_MINUS_ONE =
1461446703485210103287273052203988822378723970342 - 4295128739 - 1;
/// @notice Given a tickSpacing, compute the maximum usable tick
function maxUsableTick(int24 tickSpacing) internal pure returns (int24) {
unchecked {
return (MAX_TICK / tickSpacing) * tickSpacing;
}
}
/// @notice Given a tickSpacing, compute the minimum usable tick
function minUsableTick(int24 tickSpacing) internal pure returns (int24) {
unchecked {
return (MIN_TICK / tickSpacing) * tickSpacing;
}
}
/// @notice Calculates sqrt(1.0001^tick) * 2^96
/// @dev Throws if |tick| > max tick
/// @param tick The input tick for the above formula
/// @return sqrtPriceX96 A Fixed point Q64.96 number representing the sqrt of the price of the two assets (currency1/currency0)
/// at the given tick
function getSqrtPriceAtTick(int24 tick) internal pure returns (uint160 sqrtPriceX96) {
unchecked {
uint256 absTick;
assembly ("memory-safe") {
tick := signextend(2, tick)
// mask = 0 if tick >= 0 else -1 (all 1s)
let mask := sar(255, tick)
// if tick >= 0, |tick| = tick = 0 ^ tick
// if tick < 0, |tick| = ~~|tick| = ~(-|tick| - 1) = ~(tick - 1) = (-1) ^ (tick - 1)
// either way, |tick| = mask ^ (tick + mask)
absTick := xor(mask, add(mask, tick))
}
if (absTick > uint256(int256(MAX_TICK))) InvalidTick.selector.revertWith(tick);
// The tick is decomposed into bits, and for each bit with index i that is set, the product of 1/sqrt(1.0001^(2^i))
// is calculated (using Q128.128). The constants used for this calculation are rounded to the nearest integer
// Equivalent to:
// price = absTick & 0x1 != 0 ? 0xfffcb933bd6fad37aa2d162d1a594001 : 0x100000000000000000000000000000000;
// or price = int(2**128 / sqrt(1.0001)) if (absTick & 0x1) else 1 << 128
uint256 price;
assembly ("memory-safe") {
price := xor(shl(128, 1), mul(xor(shl(128, 1), 0xfffcb933bd6fad37aa2d162d1a594001), and(absTick, 0x1)))
}
if (absTick & 0x2 != 0) price = (price * 0xfff97272373d413259a46990580e213a) >> 128;
if (absTick & 0x4 != 0) price = (price * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128;
if (absTick & 0x8 != 0) price = (price * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128;
if (absTick & 0x10 != 0) price = (price * 0xffcb9843d60f6159c9db58835c926644) >> 128;
if (absTick & 0x20 != 0) price = (price * 0xff973b41fa98c081472e6896dfb254c0) >> 128;
if (absTick & 0x40 != 0) price = (price * 0xff2ea16466c96a3843ec78b326b52861) >> 128;
if (absTick & 0x80 != 0) price = (price * 0xfe5dee046a99a2a811c461f1969c3053) >> 128;
if (absTick & 0x100 != 0) price = (price * 0xfcbe86c7900a88aedcffc83b479aa3a4) >> 128;
if (absTick & 0x200 != 0) price = (price * 0xf987a7253ac413176f2b074cf7815e54) >> 128;
if (absTick & 0x400 != 0) price = (price * 0xf3392b0822b70005940c7a398e4b70f3) >> 128;
if (absTick & 0x800 != 0) price = (price * 0xe7159475a2c29b7443b29c7fa6e889d9) >> 128;
if (absTick & 0x1000 != 0) price = (price * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128;
if (absTick & 0x2000 != 0) price = (price * 0xa9f746462d870fdf8a65dc1f90e061e5) >> 128;
if (absTick & 0x4000 != 0) price = (price * 0x70d869a156d2a1b890bb3df62baf32f7) >> 128;
if (absTick & 0x8000 != 0) price = (price * 0x31be135f97d08fd981231505542fcfa6) >> 128;
if (absTick & 0x10000 != 0) price = (price * 0x9aa508b5b7a84e1c677de54f3e99bc9) >> 128;
if (absTick & 0x20000 != 0) price = (price * 0x5d6af8dedb81196699c329225ee604) >> 128;
if (absTick & 0x40000 != 0) price = (price * 0x2216e584f5fa1ea926041bedfe98) >> 128;
if (absTick & 0x80000 != 0) price = (price * 0x48a170391f7dc42444e8fa2) >> 128;
assembly ("memory-safe") {
// if (tick > 0) price = type(uint256).max / price;
if sgt(tick, 0) { price := div(not(0), price) }
// this divides by 1<<32 rounding up to go from a Q128.128 to a Q128.96.
// we then downcast because we know the result always fits within 160 bits due to our tick input constraint
// we round up in the division so getTickAtSqrtPrice of the output price is always consistent
// `sub(shl(32, 1), 1)` is `type(uint32).max`
// `price + type(uint32).max` will not overflow because `price` fits in 192 bits
sqrtPriceX96 := shr(32, add(price, sub(shl(32, 1), 1)))
}
}
}
/// @notice Calculates the greatest tick value such that getSqrtPriceAtTick(tick) <= sqrtPriceX96
/// @dev Throws in case sqrtPriceX96 < MIN_SQRT_PRICE, as MIN_SQRT_PRICE is the lowest value getSqrtPriceAtTick may
/// ever return.
/// @param sqrtPriceX96 The sqrt price for which to compute the tick as a Q64.96
/// @return tick The greatest tick for which the getSqrtPriceAtTick(tick) is less than or equal to the input sqrtPriceX96
function getTickAtSqrtPrice(uint160 sqrtPriceX96) internal pure returns (int24 tick) {
unchecked {
// Equivalent: if (sqrtPriceX96 < MIN_SQRT_PRICE || sqrtPriceX96 >= MAX_SQRT_PRICE) revert InvalidSqrtPrice();
// second inequality must be >= because the price can never reach the price at the max tick
// if sqrtPriceX96 < MIN_SQRT_PRICE, the `sub` underflows and `gt` is true
// if sqrtPriceX96 >= MAX_SQRT_PRICE, sqrtPriceX96 - MIN_SQRT_PRICE > MAX_SQRT_PRICE - MIN_SQRT_PRICE - 1
if ((sqrtPriceX96 - MIN_SQRT_PRICE) > MAX_SQRT_PRICE_MINUS_MIN_SQRT_PRICE_MINUS_ONE) {
InvalidSqrtPrice.selector.revertWith(sqrtPriceX96);
}
uint256 price = uint256(sqrtPriceX96) << 32;
uint256 r = price;
uint256 msb = BitMath.mostSignificantBit(r);
if (msb >= 128) r = price >> (msb - 127);
else r = price << (127 - msb);
int256 log_2 = (int256(msb) - 128) << 64;
assembly ("memory-safe") {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(63, f))
r := shr(f, r)
}
assembly ("memory-safe") {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(62, f))
r := shr(f, r)
}
assembly ("memory-safe") {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(61, f))
r := shr(f, r)
}
assembly ("memory-safe") {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(60, f))
r := shr(f, r)
}
assembly ("memory-safe") {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(59, f))
r := shr(f, r)
}
assembly ("memory-safe") {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(58, f))
r := shr(f, r)
}
assembly ("memory-safe") {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(57, f))
r := shr(f, r)
}
assembly ("memory-safe") {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(56, f))
r := shr(f, r)
}
assembly ("memory-safe") {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(55, f))
r := shr(f, r)
}
assembly ("memory-safe") {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(54, f))
r := shr(f, r)
}
assembly ("memory-safe") {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(53, f))
r := shr(f, r)
}
assembly ("memory-safe") {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(52, f))
r := shr(f, r)
}
assembly ("memory-safe") {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(51, f))
r := shr(f, r)
}
assembly ("memory-safe") {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(50, f))
}
int256 log_sqrt10001 = log_2 * 255738958999603826347141; // Q22.128 number
// Magic number represents the ceiling of the maximum value of the error when approximating log_sqrt10001(x)
int24 tickLow = int24((log_sqrt10001 - 3402992956809132418596140100660247210) >> 128);
// Magic number represents the minimum value of the error when approximating log_sqrt10001(x), when
// sqrtPrice is from the range (2^-64, 2^64). This is safe as MIN_SQRT_PRICE is more than 2^-64. If MIN_SQRT_PRICE
// is changed, this may need to be changed too
int24 tickHi = int24((log_sqrt10001 + 291339464771989622907027621153398088495) >> 128);
tick = tickLow == tickHi ? tickLow : getSqrtPriceAtTick(tickHi) <= sqrtPriceX96 ? tickHi : tickLow;
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
import {IERC20Permit} from "../extensions/IERC20Permit.sol";
import {Address} from "../../../utils/Address.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using Address for address;
/**
* @dev An operation with an ERC20 token failed.
*/
error SafeERC20FailedOperation(address token);
/**
* @dev Indicates a failed `decreaseAllowance` request.
*/
error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
forceApprove(token, spender, oldAllowance + value);
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
* value, non-reverting calls are assumed to be successful.
*/
function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
unchecked {
uint256 currentAllowance = token.allowance(address(this), spender);
if (currentAllowance < requestedDecrease) {
revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
}
forceApprove(token, spender, currentAllowance - requestedDecrease);
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
* to be set to zero before setting it to a non-zero value, such as USDT.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(data);
if (returndata.length != 0 && !abi.decode(returndata, (bool))) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
// and not revert is the subcall reverts.
(bool success, bytes memory returndata) = address(token).call(data);
return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0;
}
}// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
import { IMessageLibManager } from "./IMessageLibManager.sol";
import { IMessagingComposer } from "./IMessagingComposer.sol";
import { IMessagingChannel } from "./IMessagingChannel.sol";
import { IMessagingContext } from "./IMessagingContext.sol";
struct MessagingParams {
uint32 dstEid;
bytes32 receiver;
bytes message;
bytes options;
bool payInLzToken;
}
struct MessagingReceipt {
bytes32 guid;
uint64 nonce;
MessagingFee fee;
}
struct MessagingFee {
uint256 nativeFee;
uint256 lzTokenFee;
}
struct Origin {
uint32 srcEid;
bytes32 sender;
uint64 nonce;
}
interface ILayerZeroEndpointV2 is IMessageLibManager, IMessagingComposer, IMessagingChannel, IMessagingContext {
event PacketSent(bytes encodedPayload, bytes options, address sendLibrary);
event PacketVerified(Origin origin, address receiver, bytes32 payloadHash);
event PacketDelivered(Origin origin, address receiver);
event LzReceiveAlert(
address indexed receiver,
address indexed executor,
Origin origin,
bytes32 guid,
uint256 gas,
uint256 value,
bytes message,
bytes extraData,
bytes reason
);
event LzTokenSet(address token);
event DelegateSet(address sender, address delegate);
function quote(MessagingParams calldata _params, address _sender) external view returns (MessagingFee memory);
function send(
MessagingParams calldata _params,
address _refundAddress
) external payable returns (MessagingReceipt memory);
function verify(Origin calldata _origin, address _receiver, bytes32 _payloadHash) external;
function verifiable(Origin calldata _origin, address _receiver) external view returns (bool);
function initializable(Origin calldata _origin, address _receiver) external view returns (bool);
function lzReceive(
Origin calldata _origin,
address _receiver,
bytes32 _guid,
bytes calldata _message,
bytes calldata _extraData
) external payable;
// oapp can burn messages partially by calling this function with its own business logic if messages are verified in order
function clear(address _oapp, Origin calldata _origin, bytes32 _guid, bytes calldata _message) external;
function setLzToken(address _lzToken) external;
function lzToken() external view returns (address);
function nativeToken() external view returns (address);
function setDelegate(address _delegate) external;
}// SPDX-License-Identifier: MIT
pragma solidity >=0.6.2 <0.9.0;
library stdMath {
int256 private constant INT256_MIN = -57896044618658097711785492504343953926634992332820282019728792003956564819968;
function abs(int256 a) internal pure returns (uint256) {
// Required or it will fail when `a = type(int256).min`
if (a == INT256_MIN) {
return 57896044618658097711785492504343953926634992332820282019728792003956564819968;
}
return uint256(a > 0 ? a : -a);
}
function delta(uint256 a, uint256 b) internal pure returns (uint256) {
return a > b ? a - b : b - a;
}
function delta(int256 a, int256 b) internal pure returns (uint256) {
// a and b are of the same sign
// this works thanks to two's complement, the left-most bit is the sign bit
if ((a ^ b) > -1) {
return delta(abs(a), abs(b));
}
// a and b are of opposite signs
return abs(a) + abs(b);
}
function percentDelta(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 absDelta = delta(a, b);
return absDelta * 1e18 / b;
}
function percentDelta(int256 a, int256 b) internal pure returns (uint256) {
uint256 absDelta = delta(a, b);
uint256 absB = abs(b);
return absDelta * 1e18 / absB;
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IPoolAddressesProvider} from './IPoolAddressesProvider.sol';
import {DataTypes} from '../protocol/libraries/types/DataTypes.sol';
/**
* @title IPool
* @author Aave
* @notice Defines the basic interface for an Aave Pool.
*/
interface IPool {
/**
* @dev Emitted on mintUnbacked()
* @param reserve The address of the underlying asset of the reserve
* @param user The address initiating the supply
* @param onBehalfOf The beneficiary of the supplied assets, receiving the aTokens
* @param amount The amount of supplied assets
* @param referralCode The referral code used
*/
event MintUnbacked(
address indexed reserve,
address user,
address indexed onBehalfOf,
uint256 amount,
uint16 indexed referralCode
);
/**
* @dev Emitted on backUnbacked()
* @param reserve The address of the underlying asset of the reserve
* @param backer The address paying for the backing
* @param amount The amount added as backing
* @param fee The amount paid in fees
*/
event BackUnbacked(address indexed reserve, address indexed backer, uint256 amount, uint256 fee);
/**
* @dev Emitted on supply()
* @param reserve The address of the underlying asset of the reserve
* @param user The address initiating the supply
* @param onBehalfOf The beneficiary of the supply, receiving the aTokens
* @param amount The amount supplied
* @param referralCode The referral code used
*/
event Supply(
address indexed reserve,
address user,
address indexed onBehalfOf,
uint256 amount,
uint16 indexed referralCode
);
/**
* @dev Emitted on withdraw()
* @param reserve The address of the underlying asset being withdrawn
* @param user The address initiating the withdrawal, owner of aTokens
* @param to The address that will receive the underlying
* @param amount The amount to be withdrawn
*/
event Withdraw(address indexed reserve, address indexed user, address indexed to, uint256 amount);
/**
* @dev Emitted on borrow() and flashLoan() when debt needs to be opened
* @param reserve The address of the underlying asset being borrowed
* @param user The address of the user initiating the borrow(), receiving the funds on borrow() or just
* initiator of the transaction on flashLoan()
* @param onBehalfOf The address that will be getting the debt
* @param amount The amount borrowed out
* @param interestRateMode The rate mode: 2 for Variable, 1 is deprecated (changed on v3.2.0)
* @param borrowRate The numeric rate at which the user has borrowed, expressed in ray
* @param referralCode The referral code used
*/
event Borrow(
address indexed reserve,
address user,
address indexed onBehalfOf,
uint256 amount,
DataTypes.InterestRateMode interestRateMode,
uint256 borrowRate,
uint16 indexed referralCode
);
/**
* @dev Emitted on repay()
* @param reserve The address of the underlying asset of the reserve
* @param user The beneficiary of the repayment, getting his debt reduced
* @param repayer The address of the user initiating the repay(), providing the funds
* @param amount The amount repaid
* @param useATokens True if the repayment is done using aTokens, `false` if done with underlying asset directly
*/
event Repay(
address indexed reserve,
address indexed user,
address indexed repayer,
uint256 amount,
bool useATokens
);
/**
* @dev Emitted on borrow(), repay() and liquidationCall() when using isolated assets
* @param asset The address of the underlying asset of the reserve
* @param totalDebt The total isolation mode debt for the reserve
*/
event IsolationModeTotalDebtUpdated(address indexed asset, uint256 totalDebt);
/**
* @dev Emitted when the user selects a certain asset category for eMode
* @param user The address of the user
* @param categoryId The category id
*/
event UserEModeSet(address indexed user, uint8 categoryId);
/**
* @dev Emitted on setUserUseReserveAsCollateral()
* @param reserve The address of the underlying asset of the reserve
* @param user The address of the user enabling the usage as collateral
*/
event ReserveUsedAsCollateralEnabled(address indexed reserve, address indexed user);
/**
* @dev Emitted on setUserUseReserveAsCollateral()
* @param reserve The address of the underlying asset of the reserve
* @param user The address of the user enabling the usage as collateral
*/
event ReserveUsedAsCollateralDisabled(address indexed reserve, address indexed user);
/**
* @dev Emitted on flashLoan()
* @param target The address of the flash loan receiver contract
* @param initiator The address initiating the flash loan
* @param asset The address of the asset being flash borrowed
* @param amount The amount flash borrowed
* @param interestRateMode The flashloan mode: 0 for regular flashloan,
* 1 for Stable (Deprecated on v3.2.0), 2 for Variable
* @param premium The fee flash borrowed
* @param referralCode The referral code used
*/
event FlashLoan(
address indexed target,
address initiator,
address indexed asset,
uint256 amount,
DataTypes.InterestRateMode interestRateMode,
uint256 premium,
uint16 indexed referralCode
);
/**
* @dev Emitted when a borrower is liquidated.
* @param collateralAsset The address of the underlying asset used as collateral, to receive as result of the liquidation
* @param debtAsset The address of the underlying borrowed asset to be repaid with the liquidation
* @param user The address of the borrower getting liquidated
* @param debtToCover The debt amount of borrowed `asset` the liquidator wants to cover
* @param liquidatedCollateralAmount The amount of collateral received by the liquidator
* @param liquidator The address of the liquidator
* @param receiveAToken True if the liquidators wants to receive the collateral aTokens, `false` if he wants
* to receive the underlying collateral asset directly
*/
event LiquidationCall(
address indexed collateralAsset,
address indexed debtAsset,
address indexed user,
uint256 debtToCover,
uint256 liquidatedCollateralAmount,
address liquidator,
bool receiveAToken
);
/**
* @dev Emitted when the state of a reserve is updated.
* @param reserve The address of the underlying asset of the reserve
* @param liquidityRate The next liquidity rate
* @param stableBorrowRate The next stable borrow rate @note deprecated on v3.2.0
* @param variableBorrowRate The next variable borrow rate
* @param liquidityIndex The next liquidity index
* @param variableBorrowIndex The next variable borrow index
*/
event ReserveDataUpdated(
address indexed reserve,
uint256 liquidityRate,
uint256 stableBorrowRate,
uint256 variableBorrowRate,
uint256 liquidityIndex,
uint256 variableBorrowIndex
);
/**
* @dev Emitted when the deficit of a reserve is covered.
* @param reserve The address of the underlying asset of the reserve
* @param caller The caller that triggered the DeficitCovered event
* @param amountCovered The amount of deficit covered
*/
event DeficitCovered(address indexed reserve, address caller, uint256 amountCovered);
/**
* @dev Emitted when the protocol treasury receives minted aTokens from the accrued interest.
* @param reserve The address of the reserve
* @param amountMinted The amount minted to the treasury
*/
event MintedToTreasury(address indexed reserve, uint256 amountMinted);
/**
* @dev Emitted when deficit is realized on a liquidation.
* @param user The user address where the bad debt will be burned
* @param debtAsset The address of the underlying borrowed asset to be burned
* @param amountCreated The amount of deficit created
*/
event DeficitCreated(address indexed user, address indexed debtAsset, uint256 amountCreated);
/**
* @notice Mints an `amount` of aTokens to the `onBehalfOf`
* @param asset The address of the underlying asset to mint
* @param amount The amount to mint
* @param onBehalfOf The address that will receive the aTokens
* @param referralCode Code used to register the integrator originating the operation, for potential rewards.
* 0 if the action is executed directly by the user, without any middle-man
*/
function mintUnbacked(
address asset,
uint256 amount,
address onBehalfOf,
uint16 referralCode
) external;
/**
* @notice Back the current unbacked underlying with `amount` and pay `fee`.
* @param asset The address of the underlying asset to back
* @param amount The amount to back
* @param fee The amount paid in fees
* @return The backed amount
*/
function backUnbacked(address asset, uint256 amount, uint256 fee) external returns (uint256);
/**
* @notice Supplies an `amount` of underlying asset into the reserve, receiving in return overlying aTokens.
* - E.g. User supplies 100 USDC and gets in return 100 aUSDC
* @param asset The address of the underlying asset to supply
* @param amount The amount to be supplied
* @param onBehalfOf The address that will receive the aTokens, same as msg.sender if the user
* wants to receive them on his own wallet, or a different address if the beneficiary of aTokens
* is a different wallet
* @param referralCode Code used to register the integrator originating the operation, for potential rewards.
* 0 if the action is executed directly by the user, without any middle-man
*/
function supply(address asset, uint256 amount, address onBehalfOf, uint16 referralCode) external;
/**
* @notice Supply with transfer approval of asset to be supplied done via permit function
* see: https://eips.ethereum.org/EIPS/eip-2612 and https://eips.ethereum.org/EIPS/eip-713
* @param asset The address of the underlying asset to supply
* @param amount The amount to be supplied
* @param onBehalfOf The address that will receive the aTokens, same as msg.sender if the user
* wants to receive them on his own wallet, or a different address if the beneficiary of aTokens
* is a different wallet
* @param deadline The deadline timestamp that the permit is valid
* @param referralCode Code used to register the integrator originating the operation, for potential rewards.
* 0 if the action is executed directly by the user, without any middle-man
* @param permitV The V parameter of ERC712 permit sig
* @param permitR The R parameter of ERC712 permit sig
* @param permitS The S parameter of ERC712 permit sig
*/
function supplyWithPermit(
address asset,
uint256 amount,
address onBehalfOf,
uint16 referralCode,
uint256 deadline,
uint8 permitV,
bytes32 permitR,
bytes32 permitS
) external;
/**
* @notice Withdraws an `amount` of underlying asset from the reserve, burning the equivalent aTokens owned
* E.g. User has 100 aUSDC, calls withdraw() and receives 100 USDC, burning the 100 aUSDC
* @param asset The address of the underlying asset to withdraw
* @param amount The underlying amount to be withdrawn
* - Send the value type(uint256).max in order to withdraw the whole aToken balance
* @param to The address that will receive the underlying, same as msg.sender if the user
* wants to receive it on his own wallet, or a different address if the beneficiary is a
* different wallet
* @return The final amount withdrawn
*/
function withdraw(address asset, uint256 amount, address to) external returns (uint256);
/**
* @notice Allows users to borrow a specific `amount` of the reserve underlying asset, provided that the borrower
* already supplied enough collateral, or he was given enough allowance by a credit delegator on the VariableDebtToken
* - E.g. User borrows 100 USDC passing as `onBehalfOf` his own address, receiving the 100 USDC in his wallet
* and 100 variable debt tokens
* @param asset The address of the underlying asset to borrow
* @param amount The amount to be borrowed
* @param interestRateMode 2 for Variable, 1 is deprecated on v3.2.0
* @param referralCode The code used to register the integrator originating the operation, for potential rewards.
* 0 if the action is executed directly by the user, without any middle-man
* @param onBehalfOf The address of the user who will receive the debt. Should be the address of the borrower itself
* calling the function if he wants to borrow against his own collateral, or the address of the credit delegator
* if he has been given credit delegation allowance
*/
function borrow(
address asset,
uint256 amount,
uint256 interestRateMode,
uint16 referralCode,
address onBehalfOf
) external;
/**
* @notice Repays a borrowed `amount` on a specific reserve, burning the equivalent debt tokens owned
* - E.g. User repays 100 USDC, burning 100 variable debt tokens of the `onBehalfOf` address
* @param asset The address of the borrowed underlying asset previously borrowed
* @param amount The amount to repay
* - Send the value type(uint256).max in order to repay the whole debt for `asset` on the specific `debtMode`
* @param interestRateMode 2 for Variable, 1 is deprecated on v3.2.0
* @param onBehalfOf The address of the user who will get his debt reduced/removed. Should be the address of the
* user calling the function if he wants to reduce/remove his own debt, or the address of any other
* other borrower whose debt should be removed
* @return The final amount repaid
*/
function repay(
address asset,
uint256 amount,
uint256 interestRateMode,
address onBehalfOf
) external returns (uint256);
/**
* @notice Repay with transfer approval of asset to be repaid done via permit function
* see: https://eips.ethereum.org/EIPS/eip-2612 and https://eips.ethereum.org/EIPS/eip-713
* @param asset The address of the borrowed underlying asset previously borrowed
* @param amount The amount to repay
* - Send the value type(uint256).max in order to repay the whole debt for `asset` on the specific `debtMode`
* @param interestRateMode 2 for Variable, 1 is deprecated on v3.2.0
* @param onBehalfOf Address of the user who will get his debt reduced/removed. Should be the address of the
* user calling the function if he wants to reduce/remove his own debt, or the address of any other
* other borrower whose debt should be removed
* @param deadline The deadline timestamp that the permit is valid
* @param permitV The V parameter of ERC712 permit sig
* @param permitR The R parameter of ERC712 permit sig
* @param permitS The S parameter of ERC712 permit sig
* @return The final amount repaid
*/
function repayWithPermit(
address asset,
uint256 amount,
uint256 interestRateMode,
address onBehalfOf,
uint256 deadline,
uint8 permitV,
bytes32 permitR,
bytes32 permitS
) external returns (uint256);
/**
* @notice Repays a borrowed `amount` on a specific reserve using the reserve aTokens, burning the
* equivalent debt tokens
* - E.g. User repays 100 USDC using 100 aUSDC, burning 100 variable debt tokens
* @dev Passing uint256.max as amount will clean up any residual aToken dust balance, if the user aToken
* balance is not enough to cover the whole debt
* @param asset The address of the borrowed underlying asset previously borrowed
* @param amount The amount to repay
* - Send the value type(uint256).max in order to repay the whole debt for `asset` on the specific `debtMode`
* @param interestRateMode DEPRECATED in v3.2.0
* @return The final amount repaid
*/
function repayWithATokens(
address asset,
uint256 amount,
uint256 interestRateMode
) external returns (uint256);
/**
* @notice Allows suppliers to enable/disable a specific supplied asset as collateral
* @param asset The address of the underlying asset supplied
* @param useAsCollateral True if the user wants to use the supply as collateral, false otherwise
*/
function setUserUseReserveAsCollateral(address asset, bool useAsCollateral) external;
/**
* @notice Function to liquidate a non-healthy position collateral-wise, with Health Factor below 1
* - The caller (liquidator) covers `debtToCover` amount of debt of the user getting liquidated, and receives
* a proportionally amount of the `collateralAsset` plus a bonus to cover market risk
* @param collateralAsset The address of the underlying asset used as collateral, to receive as result of the liquidation
* @param debtAsset The address of the underlying borrowed asset to be repaid with the liquidation
* @param user The address of the borrower getting liquidated
* @param debtToCover The debt amount of borrowed `asset` the liquidator wants to cover
* @param receiveAToken True if the liquidators wants to receive the collateral aTokens, `false` if he wants
* to receive the underlying collateral asset directly
*/
function liquidationCall(
address collateralAsset,
address debtAsset,
address user,
uint256 debtToCover,
bool receiveAToken
) external;
/**
* @notice Allows smartcontracts to access the liquidity of the pool within one transaction,
* as long as the amount taken plus a fee is returned.
* @dev IMPORTANT There are security concerns for developers of flashloan receiver contracts that must be kept
* into consideration. For further details please visit https://docs.aave.com/developers/
* @param receiverAddress The address of the contract receiving the funds, implementing IFlashLoanReceiver interface
* @param assets The addresses of the assets being flash-borrowed
* @param amounts The amounts of the assets being flash-borrowed
* @param interestRateModes Types of the debt to open if the flash loan is not returned:
* 0 -> Don't open any debt, just revert if funds can't be transferred from the receiver
* 1 -> Deprecated on v3.2.0
* 2 -> Open debt at variable rate for the value of the amount flash-borrowed to the `onBehalfOf` address
* @param onBehalfOf The address that will receive the debt in the case of using 2 on `modes`
* @param params Variadic packed params to pass to the receiver as extra information
* @param referralCode The code used to register the integrator originating the operation, for potential rewards.
* 0 if the action is executed directly by the user, without any middle-man
*/
function flashLoan(
address receiverAddress,
address[] calldata assets,
uint256[] calldata amounts,
uint256[] calldata interestRateModes,
address onBehalfOf,
bytes calldata params,
uint16 referralCode
) external;
/**
* @notice Allows smartcontracts to access the liquidity of the pool within one transaction,
* as long as the amount taken plus a fee is returned.
* @dev IMPORTANT There are security concerns for developers of flashloan receiver contracts that must be kept
* into consideration. For further details please visit https://docs.aave.com/developers/
* @param receiverAddress The address of the contract receiving the funds, implementing IFlashLoanSimpleReceiver interface
* @param asset The address of the asset being flash-borrowed
* @param amount The amount of the asset being flash-borrowed
* @param params Variadic packed params to pass to the receiver as extra information
* @param referralCode The code used to register the integrator originating the operation, for potential rewards.
* 0 if the action is executed directly by the user, without any middle-man
*/
function flashLoanSimple(
address receiverAddress,
address asset,
uint256 amount,
bytes calldata params,
uint16 referralCode
) external;
/**
* @notice Returns the user account data across all the reserves
* @param user The address of the user
* @return totalCollateralBase The total collateral of the user in the base currency used by the price feed
* @return totalDebtBase The total debt of the user in the base currency used by the price feed
* @return availableBorrowsBase The borrowing power left of the user in the base currency used by the price feed
* @return currentLiquidationThreshold The liquidation threshold of the user
* @return ltv The loan to value of The user
* @return healthFactor The current health factor of the user
*/
function getUserAccountData(
address user
)
external
view
returns (
uint256 totalCollateralBase,
uint256 totalDebtBase,
uint256 availableBorrowsBase,
uint256 currentLiquidationThreshold,
uint256 ltv,
uint256 healthFactor
);
/**
* @notice Initializes a reserve, activating it, assigning an aToken and debt tokens and an
* interest rate strategy
* @dev Only callable by the PoolConfigurator contract
* @param asset The address of the underlying asset of the reserve
* @param aTokenAddress The address of the aToken that will be assigned to the reserve
* @param variableDebtAddress The address of the VariableDebtToken that will be assigned to the reserve
* @param interestRateStrategyAddress The address of the interest rate strategy contract
*/
function initReserve(
address asset,
address aTokenAddress,
address variableDebtAddress,
address interestRateStrategyAddress
) external;
/**
* @notice Drop a reserve
* @dev Only callable by the PoolConfigurator contract
* @dev Does not reset eMode flags, which must be considered when reusing the same reserve id for a different reserve.
* @param asset The address of the underlying asset of the reserve
*/
function dropReserve(address asset) external;
/**
* @notice Updates the address of the interest rate strategy contract
* @dev Only callable by the PoolConfigurator contract
* @param asset The address of the underlying asset of the reserve
* @param rateStrategyAddress The address of the interest rate strategy contract
*/
function setReserveInterestRateStrategyAddress(
address asset,
address rateStrategyAddress
) external;
/**
* @notice Accumulates interest to all indexes of the reserve
* @dev Only callable by the PoolConfigurator contract
* @dev To be used when required by the configurator, for example when updating interest rates strategy data
* @param asset The address of the underlying asset of the reserve
*/
function syncIndexesState(address asset) external;
/**
* @notice Updates interest rates on the reserve data
* @dev Only callable by the PoolConfigurator contract
* @dev To be used when required by the configurator, for example when updating interest rates strategy data
* @param asset The address of the underlying asset of the reserve
*/
function syncRatesState(address asset) external;
/**
* @notice Sets the configuration bitmap of the reserve as a whole
* @dev Only callable by the PoolConfigurator contract
* @param asset The address of the underlying asset of the reserve
* @param configuration The new configuration bitmap
*/
function setConfiguration(
address asset,
DataTypes.ReserveConfigurationMap calldata configuration
) external;
/**
* @notice Returns the configuration of the reserve
* @param asset The address of the underlying asset of the reserve
* @return The configuration of the reserve
*/
function getConfiguration(
address asset
) external view returns (DataTypes.ReserveConfigurationMap memory);
/**
* @notice Returns the configuration of the user across all the reserves
* @param user The user address
* @return The configuration of the user
*/
function getUserConfiguration(
address user
) external view returns (DataTypes.UserConfigurationMap memory);
/**
* @notice Returns the normalized income of the reserve
* @param asset The address of the underlying asset of the reserve
* @return The reserve's normalized income
*/
function getReserveNormalizedIncome(address asset) external view returns (uint256);
/**
* @notice Returns the normalized variable debt per unit of asset
* @dev WARNING: This function is intended to be used primarily by the protocol itself to get a
* "dynamic" variable index based on time, current stored index and virtual rate at the current
* moment (approx. a borrower would get if opening a position). This means that is always used in
* combination with variable debt supply/balances.
* If using this function externally, consider that is possible to have an increasing normalized
* variable debt that is not equivalent to how the variable debt index would be updated in storage
* (e.g. only updates with non-zero variable debt supply)
* @param asset The address of the underlying asset of the reserve
* @return The reserve normalized variable debt
*/
function getReserveNormalizedVariableDebt(address asset) external view returns (uint256);
/**
* @notice Returns the state and configuration of the reserve
* @param asset The address of the underlying asset of the reserve
* @return The state and configuration data of the reserve
*/
function getReserveData(address asset) external view returns (DataTypes.ReserveDataLegacy memory);
/**
* @notice Returns the virtual underlying balance of the reserve
* @param asset The address of the underlying asset of the reserve
* @return The reserve virtual underlying balance
*/
function getVirtualUnderlyingBalance(address asset) external view returns (uint128);
/**
* @notice Validates and finalizes an aToken transfer
* @dev Only callable by the overlying aToken of the `asset`
* @param asset The address of the underlying asset of the aToken
* @param from The user from which the aTokens are transferred
* @param to The user receiving the aTokens
* @param amount The amount being transferred/withdrawn
* @param balanceFromBefore The aToken balance of the `from` user before the transfer
* @param balanceToBefore The aToken balance of the `to` user before the transfer
*/
function finalizeTransfer(
address asset,
address from,
address to,
uint256 amount,
uint256 balanceFromBefore,
uint256 balanceToBefore
) external;
/**
* @notice Returns the list of the underlying assets of all the initialized reserves
* @dev It does not include dropped reserves
* @return The addresses of the underlying assets of the initialized reserves
*/
function getReservesList() external view returns (address[] memory);
/**
* @notice Returns the number of initialized reserves
* @dev It includes dropped reserves
* @return The count
*/
function getReservesCount() external view returns (uint256);
/**
* @notice Returns the address of the underlying asset of a reserve by the reserve id as stored in the DataTypes.ReserveData struct
* @param id The id of the reserve as stored in the DataTypes.ReserveData struct
* @return The address of the reserve associated with id
*/
function getReserveAddressById(uint16 id) external view returns (address);
/**
* @notice Returns the PoolAddressesProvider connected to this contract
* @return The address of the PoolAddressesProvider
*/
function ADDRESSES_PROVIDER() external view returns (IPoolAddressesProvider);
/**
* @notice Updates the protocol fee on the bridging
* @param bridgeProtocolFee The part of the premium sent to the protocol treasury
*/
function updateBridgeProtocolFee(uint256 bridgeProtocolFee) external;
/**
* @notice Updates flash loan premiums. Flash loan premium consists of two parts:
* - A part is sent to aToken holders as extra, one time accumulated interest
* - A part is collected by the protocol treasury
* @dev The total premium is calculated on the total borrowed amount
* @dev The premium to protocol is calculated on the total premium, being a percentage of `flashLoanPremiumTotal`
* @dev Only callable by the PoolConfigurator contract
* @param flashLoanPremiumTotal The total premium, expressed in bps
* @param flashLoanPremiumToProtocol The part of the premium sent to the protocol treasury, expressed in bps
*/
function updateFlashloanPremiums(
uint128 flashLoanPremiumTotal,
uint128 flashLoanPremiumToProtocol
) external;
/**
* @notice Configures a new or alters an existing collateral configuration of an eMode.
* @dev In eMode, the protocol allows very high borrowing power to borrow assets of the same category.
* The category 0 is reserved as it's the default for volatile assets
* @param id The id of the category
* @param config The configuration of the category
*/
function configureEModeCategory(
uint8 id,
DataTypes.EModeCategoryBaseConfiguration memory config
) external;
/**
* @notice Replaces the current eMode collateralBitmap.
* @param id The id of the category
* @param collateralBitmap The collateralBitmap of the category
*/
function configureEModeCategoryCollateralBitmap(uint8 id, uint128 collateralBitmap) external;
/**
* @notice Replaces the current eMode borrowableBitmap.
* @param id The id of the category
* @param borrowableBitmap The borrowableBitmap of the category
*/
function configureEModeCategoryBorrowableBitmap(uint8 id, uint128 borrowableBitmap) external;
/**
* @notice Returns the data of an eMode category
* @dev DEPRECATED use independent getters instead
* @param id The id of the category
* @return The configuration data of the category
*/
function getEModeCategoryData(
uint8 id
) external view returns (DataTypes.EModeCategoryLegacy memory);
/**
* @notice Returns the label of an eMode category
* @param id The id of the category
* @return The label of the category
*/
function getEModeCategoryLabel(uint8 id) external view returns (string memory);
/**
* @notice Returns the collateral config of an eMode category
* @param id The id of the category
* @return The ltv,lt,lb of the category
*/
function getEModeCategoryCollateralConfig(
uint8 id
) external view returns (DataTypes.CollateralConfig memory);
/**
* @notice Returns the collateralBitmap of an eMode category
* @param id The id of the category
* @return The collateralBitmap of the category
*/
function getEModeCategoryCollateralBitmap(uint8 id) external view returns (uint128);
/**
* @notice Returns the borrowableBitmap of an eMode category
* @param id The id of the category
* @return The borrowableBitmap of the category
*/
function getEModeCategoryBorrowableBitmap(uint8 id) external view returns (uint128);
/**
* @notice Allows a user to use the protocol in eMode
* @param categoryId The id of the category
*/
function setUserEMode(uint8 categoryId) external;
/**
* @notice Returns the eMode the user is using
* @param user The address of the user
* @return The eMode id
*/
function getUserEMode(address user) external view returns (uint256);
/**
* @notice Resets the isolation mode total debt of the given asset to zero
* @dev It requires the given asset has zero debt ceiling
* @param asset The address of the underlying asset to reset the isolationModeTotalDebt
*/
function resetIsolationModeTotalDebt(address asset) external;
/**
* @notice Sets the liquidation grace period of the given asset
* @dev To enable a liquidation grace period, a timestamp in the future should be set,
* To disable a liquidation grace period, any timestamp in the past works, like 0
* @param asset The address of the underlying asset to set the liquidationGracePeriod
* @param until Timestamp when the liquidation grace period will end
**/
function setLiquidationGracePeriod(address asset, uint40 until) external;
/**
* @notice Returns the liquidation grace period of the given asset
* @param asset The address of the underlying asset
* @return Timestamp when the liquidation grace period will end
**/
function getLiquidationGracePeriod(address asset) external view returns (uint40);
/**
* @notice Returns the total fee on flash loans
* @return The total fee on flashloans
*/
function FLASHLOAN_PREMIUM_TOTAL() external view returns (uint128);
/**
* @notice Returns the part of the bridge fees sent to protocol
* @return The bridge fee sent to the protocol treasury
*/
function BRIDGE_PROTOCOL_FEE() external view returns (uint256);
/**
* @notice Returns the part of the flashloan fees sent to protocol
* @return The flashloan fee sent to the protocol treasury
*/
function FLASHLOAN_PREMIUM_TO_PROTOCOL() external view returns (uint128);
/**
* @notice Returns the maximum number of reserves supported to be listed in this Pool
* @return The maximum number of reserves supported
*/
function MAX_NUMBER_RESERVES() external view returns (uint16);
/**
* @notice Mints the assets accrued through the reserve factor to the treasury in the form of aTokens
* @param assets The list of reserves for which the minting needs to be executed
*/
function mintToTreasury(address[] calldata assets) external;
/**
* @notice Rescue and transfer tokens locked in this contract
* @param token The address of the token
* @param to The address of the recipient
* @param amount The amount of token to transfer
*/
function rescueTokens(address token, address to, uint256 amount) external;
/**
* @notice Supplies an `amount` of underlying asset into the reserve, receiving in return overlying aTokens.
* - E.g. User supplies 100 USDC and gets in return 100 aUSDC
* @dev Deprecated: Use the `supply` function instead
* @param asset The address of the underlying asset to supply
* @param amount The amount to be supplied
* @param onBehalfOf The address that will receive the aTokens, same as msg.sender if the user
* wants to receive them on his own wallet, or a different address if the beneficiary of aTokens
* is a different wallet
* @param referralCode Code used to register the integrator originating the operation, for potential rewards.
* 0 if the action is executed directly by the user, without any middle-man
*/
function deposit(address asset, uint256 amount, address onBehalfOf, uint16 referralCode) external;
/**
* @notice It covers the deficit of a specified reserve by burning:
* - the equivalent aToken `amount` for assets with virtual accounting enabled
* - the equivalent `amount` of underlying for assets with virtual accounting disabled (e.g. GHO)
* @dev The deficit of a reserve can occur due to situations where borrowed assets are not repaid, leading to bad debt.
* @param asset The address of the underlying asset to cover the deficit.
* @param amount The amount to be covered, in aToken or underlying on non-virtual accounted assets
*/
function eliminateReserveDeficit(address asset, uint256 amount) external;
/**
* @notice Returns the current deficit of a reserve.
* @param asset The address of the underlying asset of the reserve
* @return The current deficit of the reserve
*/
function getReserveDeficit(address asset) external view returns (uint256);
/**
* @notice Returns the aToken address of a reserve.
* @param asset The address of the underlying asset of the reserve
* @return The address of the aToken
*/
function getReserveAToken(address asset) external view returns (address);
/**
* @notice Returns the variableDebtToken address of a reserve.
* @param asset The address of the underlying asset of the reserve
* @return The address of the variableDebtToken
*/
function getReserveVariableDebtToken(address asset) external view returns (address);
/**
* @notice Gets the address of the external FlashLoanLogic
*/
function getFlashLoanLogic() external view returns (address);
/**
* @notice Gets the address of the external BorrowLogic
*/
function getBorrowLogic() external view returns (address);
/**
* @notice Gets the address of the external BridgeLogic
*/
function getBridgeLogic() external view returns (address);
/**
* @notice Gets the address of the external EModeLogic
*/
function getEModeLogic() external view returns (address);
/**
* @notice Gets the address of the external LiquidationLogic
*/
function getLiquidationLogic() external view returns (address);
/**
* @notice Gets the address of the external PoolLogic
*/
function getPoolLogic() external view returns (address);
/**
* @notice Gets the address of the external SupplyLogic
*/
function getSupplyLogic() external view returns (address);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;
// Corrected IUiPoolDataProviderV3 interface for Aave v3.2.
//
// The aave-v3 npm package ships a v3.0/v3.1 struct that is missing the two
// fields added in v3.2. Using the package interface causes the Solidity ABI
// decoder to revert when decoding the return value of getReservesData().
//
// Correct struct confirmed from UiPoolDataProviderV3.sol source:
// reserveData.deficit = uint128(pool.getReserveDeficit(...));
// reserveData.virtualUnderlyingBalance = pool.getVirtualUnderlyingBalance(...);
// Both are appended after borrowableInIsolation. No virtualAccActive field exists.
//
// Usage: place in src/imports/ and import from there instead of aave-v3 package:
// import {IUiPoolDataProviderV3} from "./imports/IUiPoolDataProviderV3.sol";
// import {IPoolAddressesProvider} from "./imports/IUiPoolDataProviderV3.sol";
interface IPoolAddressesProvider {
function getPool() external view returns (address);
function getPriceOracle() external view returns (address);
function getPoolDataProvider() external view returns (address);
}
interface IUiPoolDataProviderV3 {
struct AggregatedReserveData {
address underlyingAsset;
string name;
string symbol;
uint256 decimals;
uint256 baseLTVasCollateral;
uint256 reserveLiquidationThreshold;
uint256 reserveLiquidationBonus;
uint256 reserveFactor;
bool usageAsCollateralEnabled;
bool borrowingEnabled;
bool isActive;
bool isFrozen;
// v3 state
uint128 liquidityIndex;
uint128 variableBorrowIndex;
uint128 liquidityRate;
uint128 variableBorrowRate;
uint40 lastUpdateTimestamp;
address aTokenAddress;
address variableDebtTokenAddress;
address interestRateStrategyAddress;
uint256 availableLiquidity;
uint256 totalScaledVariableDebt;
uint256 priceInMarketReferenceCurrency;
address priceOracle;
uint256 variableRateSlope1;
uint256 variableRateSlope2;
uint256 baseVariableBorrowRate;
uint256 optimalUsageRatio;
// v3 flags
bool isPaused;
bool isSiloedBorrowing;
uint128 accruedToTreasury;
uint128 unbacked;
uint128 isolationModeTotalDebt;
bool flashLoanEnabled;
uint256 debtCeiling;
uint256 debtCeilingDecimals;
uint256 borrowCap;
uint256 supplyCap;
bool borrowableInIsolation;
// v3.2 additions (confirmed from UiPoolDataProviderV3.sol source)
uint128 virtualUnderlyingBalance;
uint128 deficit;
}
struct UserReserveData {
address underlyingAsset;
uint256 scaledATokenBalance;
bool usageAsCollateralEnabledOnUser;
uint256 scaledVariableDebt;
}
struct BaseCurrencyInfo {
uint256 marketReferenceCurrencyUnit;
int256 marketReferenceCurrencyPriceInUsd;
int256 networkBaseTokenPriceInUsd;
uint8 networkBaseTokenPriceDecimals;
}
function getReservesData(IPoolAddressesProvider provider)
external view
returns (AggregatedReserveData[] memory, BaseCurrencyInfo memory);
function getUserReservesData(IPoolAddressesProvider provider, address user)
external view
returns (UserReserveData[] memory, uint8);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {FullMath} from "@uniswap/v4-core/src/libraries/FullMath.sol";
import {FixedPoint96} from "@uniswap/v4-core/src/libraries/FixedPoint96.sol";
import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol";
/// @notice Provides functions for computing liquidity amounts from token amounts and prices
library LiquidityAmounts {
using SafeCast for uint256;
/// @notice Computes the amount of liquidity received for a given amount of token0 and price range
/// @dev Calculates amount0 * (sqrt(upper) * sqrt(lower)) / (sqrt(upper) - sqrt(lower))
/// @param sqrtPriceAX96 A sqrt price representing the first tick boundary
/// @param sqrtPriceBX96 A sqrt price representing the second tick boundary
/// @param amount0 The amount0 being sent in
/// @return liquidity The amount of returned liquidity
function getLiquidityForAmount0(uint160 sqrtPriceAX96, uint160 sqrtPriceBX96, uint256 amount0)
internal
pure
returns (uint128 liquidity)
{
unchecked {
if (sqrtPriceAX96 > sqrtPriceBX96) (sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96);
uint256 intermediate = FullMath.mulDiv(sqrtPriceAX96, sqrtPriceBX96, FixedPoint96.Q96);
return FullMath.mulDiv(amount0, intermediate, sqrtPriceBX96 - sqrtPriceAX96).toUint128();
}
}
/// @notice Computes the amount of liquidity received for a given amount of token1 and price range
/// @dev Calculates amount1 / (sqrt(upper) - sqrt(lower)).
/// @param sqrtPriceAX96 A sqrt price representing the first tick boundary
/// @param sqrtPriceBX96 A sqrt price representing the second tick boundary
/// @param amount1 The amount1 being sent in
/// @return liquidity The amount of returned liquidity
function getLiquidityForAmount1(uint160 sqrtPriceAX96, uint160 sqrtPriceBX96, uint256 amount1)
internal
pure
returns (uint128 liquidity)
{
unchecked {
if (sqrtPriceAX96 > sqrtPriceBX96) (sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96);
return FullMath.mulDiv(amount1, FixedPoint96.Q96, sqrtPriceBX96 - sqrtPriceAX96).toUint128();
}
}
/// @notice Computes the maximum amount of liquidity received for a given amount of token0, token1, the current
/// pool prices and the prices at the tick boundaries
/// @param sqrtPriceX96 A sqrt price representing the current pool prices
/// @param sqrtPriceAX96 A sqrt price representing the first tick boundary
/// @param sqrtPriceBX96 A sqrt price representing the second tick boundary
/// @param amount0 The amount of token0 being sent in
/// @param amount1 The amount of token1 being sent in
/// @return liquidity The maximum amount of liquidity received
function getLiquidityForAmounts(
uint160 sqrtPriceX96,
uint160 sqrtPriceAX96,
uint160 sqrtPriceBX96,
uint256 amount0,
uint256 amount1
) internal pure returns (uint128 liquidity) {
if (sqrtPriceAX96 > sqrtPriceBX96) (sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96);
if (sqrtPriceX96 <= sqrtPriceAX96) {
liquidity = getLiquidityForAmount0(sqrtPriceAX96, sqrtPriceBX96, amount0);
} else if (sqrtPriceX96 < sqrtPriceBX96) {
uint128 liquidity0 = getLiquidityForAmount0(sqrtPriceX96, sqrtPriceBX96, amount0);
uint128 liquidity1 = getLiquidityForAmount1(sqrtPriceAX96, sqrtPriceX96, amount1);
liquidity = liquidity0 < liquidity1 ? liquidity0 : liquidity1;
} else {
liquidity = getLiquidityForAmount1(sqrtPriceAX96, sqrtPriceBX96, amount1);
}
}
}// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Arithmetic library with operations for fixed-point numbers.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/FixedPointMathLib.sol)
/// @author Inspired by USM (https://github.com/usmfum/USM/blob/master/contracts/WadMath.sol)
library FixedPointMathLib {
/*//////////////////////////////////////////////////////////////
SIMPLIFIED FIXED POINT OPERATIONS
//////////////////////////////////////////////////////////////*/
uint256 internal constant MAX_UINT256 = 2**256 - 1;
uint256 internal constant WAD = 1e18; // The scalar of ETH and most ERC20s.
function mulWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivDown(x, y, WAD); // Equivalent to (x * y) / WAD rounded down.
}
function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivUp(x, y, WAD); // Equivalent to (x * y) / WAD rounded up.
}
function divWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivDown(x, WAD, y); // Equivalent to (x * WAD) / y rounded down.
}
function divWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivUp(x, WAD, y); // Equivalent to (x * WAD) / y rounded up.
}
/*//////////////////////////////////////////////////////////////
LOW LEVEL FIXED POINT OPERATIONS
//////////////////////////////////////////////////////////////*/
function mulDivDown(
uint256 x,
uint256 y,
uint256 denominator
) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))
if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
revert(0, 0)
}
// Divide x * y by the denominator.
z := div(mul(x, y), denominator)
}
}
function mulDivUp(
uint256 x,
uint256 y,
uint256 denominator
) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))
if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
revert(0, 0)
}
// If x * y modulo the denominator is strictly greater than 0,
// 1 is added to round up the division of x * y by the denominator.
z := add(gt(mod(mul(x, y), denominator), 0), div(mul(x, y), denominator))
}
}
function rpow(
uint256 x,
uint256 n,
uint256 scalar
) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
switch x
case 0 {
switch n
case 0 {
// 0 ** 0 = 1
z := scalar
}
default {
// 0 ** n = 0
z := 0
}
}
default {
switch mod(n, 2)
case 0 {
// If n is even, store scalar in z for now.
z := scalar
}
default {
// If n is odd, store x in z for now.
z := x
}
// Shifting right by 1 is like dividing by 2.
let half := shr(1, scalar)
for {
// Shift n right by 1 before looping to halve it.
n := shr(1, n)
} n {
// Shift n right by 1 each iteration to halve it.
n := shr(1, n)
} {
// Revert immediately if x ** 2 would overflow.
// Equivalent to iszero(eq(div(xx, x), x)) here.
if shr(128, x) {
revert(0, 0)
}
// Store x squared.
let xx := mul(x, x)
// Round to the nearest number.
let xxRound := add(xx, half)
// Revert if xx + half overflowed.
if lt(xxRound, xx) {
revert(0, 0)
}
// Set x to scaled xxRound.
x := div(xxRound, scalar)
// If n is even:
if mod(n, 2) {
// Compute z * x.
let zx := mul(z, x)
// If z * x overflowed:
if iszero(eq(div(zx, x), z)) {
// Revert if x is non-zero.
if iszero(iszero(x)) {
revert(0, 0)
}
}
// Round to the nearest number.
let zxRound := add(zx, half)
// Revert if zx + half overflowed.
if lt(zxRound, zx) {
revert(0, 0)
}
// Return properly scaled zxRound.
z := div(zxRound, scalar)
}
}
}
}
}
/*//////////////////////////////////////////////////////////////
GENERAL NUMBER UTILITIES
//////////////////////////////////////////////////////////////*/
function sqrt(uint256 x) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
let y := x // We start y at x, which will help us make our initial estimate.
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.
// We check y >= 2^(k + 8) but shift right by k bits
// each branch to ensure that if x >= 256, then y >= 256.
if iszero(lt(y, 0x10000000000000000000000000000000000)) {
y := shr(128, y)
z := shl(64, z)
}
if iszero(lt(y, 0x1000000000000000000)) {
y := shr(64, y)
z := shl(32, z)
}
if iszero(lt(y, 0x10000000000)) {
y := shr(32, y)
z := shl(16, z)
}
if iszero(lt(y, 0x1000000)) {
y := shr(16, y)
z := shl(8, 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(y, 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
// Since the ceil is rare, we save gas on the assignment and repeat division in the rare case.
// If you don't care whether the floor or ceil square root is returned, you can remove this statement.
z := sub(z, lt(div(x, z), z))
}
}
function unsafeMod(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Mod x by y. Note this will return
// 0 instead of reverting if y is zero.
z := mod(x, y)
}
}
function unsafeDiv(uint256 x, uint256 y) internal pure returns (uint256 r) {
/// @solidity memory-safe-assembly
assembly {
// Divide x by y. Note this will return
// 0 instead of reverting if y is zero.
r := div(x, y)
}
}
function unsafeDivUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Add 1 to x * y if x % y > 0. Note this will
// return 0 instead of reverting if y is zero.
z := add(gt(mod(x, y), 0), div(x, y))
}
}
}// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)
/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.
abstract contract ERC20 {
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event Transfer(address indexed from, address indexed to, uint256 amount);
event Approval(address indexed owner, address indexed spender, uint256 amount);
/*//////////////////////////////////////////////////////////////
METADATA STORAGE
//////////////////////////////////////////////////////////////*/
string public name;
string public symbol;
uint8 public immutable decimals;
/*//////////////////////////////////////////////////////////////
ERC20 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
/*//////////////////////////////////////////////////////////////
EIP-2612 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 internal immutable INITIAL_CHAIN_ID;
bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;
mapping(address => uint256) public nonces;
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
constructor(
string memory _name,
string memory _symbol,
uint8 _decimals
) {
name = _name;
symbol = _symbol;
decimals = _decimals;
INITIAL_CHAIN_ID = block.chainid;
INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
}
/*//////////////////////////////////////////////////////////////
ERC20 LOGIC
//////////////////////////////////////////////////////////////*/
function approve(address spender, uint256 amount) public virtual returns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function transfer(address to, uint256 amount) public virtual returns (bool) {
balanceOf[msg.sender] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(msg.sender, to, amount);
return true;
}
function transferFrom(
address from,
address to,
uint256 amount
) public virtual returns (bool) {
uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.
if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;
balanceOf[from] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(from, to, amount);
return true;
}
/*//////////////////////////////////////////////////////////////
EIP-2612 LOGIC
//////////////////////////////////////////////////////////////*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public virtual {
require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");
// Unchecked because the only math done is incrementing
// the owner's nonce which cannot realistically overflow.
unchecked {
address recoveredAddress = ecrecover(
keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR(),
keccak256(
abi.encode(
keccak256(
"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
),
owner,
spender,
value,
nonces[owner]++,
deadline
)
)
)
),
v,
r,
s
);
require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");
allowance[recoveredAddress][spender] = value;
}
emit Approval(owner, spender, value);
}
function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
}
function computeDomainSeparator() internal view virtual returns (bytes32) {
return
keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes(name)),
keccak256("1"),
block.chainid,
address(this)
)
);
}
/*//////////////////////////////////////////////////////////////
INTERNAL MINT/BURN LOGIC
//////////////////////////////////////////////////////////////*/
function _mint(address to, uint256 amount) internal virtual {
totalSupply += amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(address(0), to, amount);
}
function _burn(address from, uint256 amount) internal virtual {
balanceOf[from] -= amount;
// Cannot underflow because a user's balance
// will never be larger than the total supply.
unchecked {
totalSupply -= amount;
}
emit Transfer(from, address(0), amount);
}
}// SPDX-License-Identifier: MIT
pragma solidity >=0.8.4 <0.9.0;
/// @title Math library for computing sqrt prices from ticks and vice versa
/// @notice Computes sqrt price for ticks of size 1.0001, i.e. sqrt(1.0001^tick) as fixed point Q64.96 numbers. Supports
/// prices between 2**-128 and 2**128
library TickMath {
/// @notice Thrown when the tick passed to #getSqrtPriceAtTick is not between MIN_TICK and MAX_TICK
error InvalidTick();
/// @notice Thrown when the price passed to #getTickAtSqrtPrice does not correspond to a price between MIN_TICK and MAX_TICK
error InvalidSqrtPrice();
/// @dev The minimum tick that may be passed to #getSqrtPriceAtTick computed from log base 1.0001 of 2**-128
int24 internal constant MIN_TICK = -887272;
/// @dev The maximum tick that may be passed to #getSqrtPriceAtTick computed from log base 1.0001 of 2**128
int24 internal constant MAX_TICK = 887272;
/// @dev The minimum tick spacing value drawn from the range of type int16 that is greater than 0, i.e. min from the range [1, 32767]
int24 internal constant MIN_TICK_SPACING = 1;
/// @dev The maximum tick spacing value drawn from the range of type int16, i.e. max from the range [1, 32767]
int24 internal constant MAX_TICK_SPACING = type(int16).max;
/// @dev The minimum value that can be returned from #getSqrtPriceAtTick. Equivalent to getSqrtPriceAtTick(MIN_TICK)
uint160 internal constant MIN_SQRT_PRICE = 4295128739;
/// @dev The maximum value that can be returned from #getSqrtPriceAtTick. Equivalent to getSqrtPriceAtTick(MAX_TICK)
uint160 internal constant MAX_SQRT_PRICE = 1461446703485210103287273052203988822378723970342;
/// @dev A threshold used for optimized bounds check, equals `MAX_SQRT_PRICE - MIN_SQRT_PRICE - 1`
uint160 internal constant MAX_SQRT_PRICE_MINUS_MIN_SQRT_PRICE_MINUS_ONE =
1461446703485210103287273052203988822378723970342 - 4295128739 - 1;
/// @notice Given a tickSpacing, compute the maximum usable tick
function maxUsableTick(int24 tickSpacing) internal pure returns (int24) {
unchecked {
return (MAX_TICK / tickSpacing) * tickSpacing;
}
}
/// @notice Given a tickSpacing, compute the minimum usable tick
function minUsableTick(int24 tickSpacing) internal pure returns (int24) {
unchecked {
return (MIN_TICK / tickSpacing) * tickSpacing;
}
}
/// @notice Calculates sqrt(1.0001^tick) * 2^96
/// @dev Throws if |tick| > max tick
/// @param tick The input tick for the above formula
/// @return sqrtPriceX96 A Fixed point Q64.96 number representing the sqrt of the price of the two assets (currency1/currency0)
/// at the given tick
function getSqrtPriceAtTick(int24 tick) internal pure returns (uint160 sqrtPriceX96) {
unchecked {
uint256 absTick;
assembly {
// mask = 0 if tick >= 0 else -1 (all 1s)
let mask := sar(255, tick)
// if tick >= 0, |tick| = tick = 0 ^ tick
// if tick < 0, |tick| = ~~|tick| = ~(-|tick| - 1) = ~(tick - 1) = (-1) ^ (tick - 1)
// either way, |tick| = mask ^ (tick + mask)
absTick := xor(mask, add(mask, tick))
}
// Equivalent: if (absTick > MAX_TICK) revert InvalidTick();
assembly {
if gt(absTick, MAX_TICK) {
// store 4-byte selector of "InvalidTick()" at memory [0x1c, 0x20)
mstore(0, 0xce8ef7fc)
revert(0x1c, 0x04)
}
}
// Equivalent to:
// price = absTick & 0x1 != 0 ? 0xfffcb933bd6fad37aa2d162d1a594001 : 0x100000000000000000000000000000000;
// or price = int(2**128 / sqrt(1.0001)) if (absTick & 0x1) else 1 << 128
uint256 price;
assembly {
price := xor(shl(128, 1), mul(xor(shl(128, 1), 0xfffcb933bd6fad37aa2d162d1a594001), and(absTick, 0x1)))
}
if (absTick & 0x2 != 0) price = (price * 0xfff97272373d413259a46990580e213a) >> 128;
if (absTick & 0x4 != 0) price = (price * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128;
if (absTick & 0x8 != 0) price = (price * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128;
if (absTick & 0x10 != 0) price = (price * 0xffcb9843d60f6159c9db58835c926644) >> 128;
if (absTick & 0x20 != 0) price = (price * 0xff973b41fa98c081472e6896dfb254c0) >> 128;
if (absTick & 0x40 != 0) price = (price * 0xff2ea16466c96a3843ec78b326b52861) >> 128;
if (absTick & 0x80 != 0) price = (price * 0xfe5dee046a99a2a811c461f1969c3053) >> 128;
if (absTick & 0x100 != 0) price = (price * 0xfcbe86c7900a88aedcffc83b479aa3a4) >> 128;
if (absTick & 0x200 != 0) price = (price * 0xf987a7253ac413176f2b074cf7815e54) >> 128;
if (absTick & 0x400 != 0) price = (price * 0xf3392b0822b70005940c7a398e4b70f3) >> 128;
if (absTick & 0x800 != 0) price = (price * 0xe7159475a2c29b7443b29c7fa6e889d9) >> 128;
if (absTick & 0x1000 != 0) price = (price * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128;
if (absTick & 0x2000 != 0) price = (price * 0xa9f746462d870fdf8a65dc1f90e061e5) >> 128;
if (absTick & 0x4000 != 0) price = (price * 0x70d869a156d2a1b890bb3df62baf32f7) >> 128;
if (absTick & 0x8000 != 0) price = (price * 0x31be135f97d08fd981231505542fcfa6) >> 128;
if (absTick & 0x10000 != 0) price = (price * 0x9aa508b5b7a84e1c677de54f3e99bc9) >> 128;
if (absTick & 0x20000 != 0) price = (price * 0x5d6af8dedb81196699c329225ee604) >> 128;
if (absTick & 0x40000 != 0) price = (price * 0x2216e584f5fa1ea926041bedfe98) >> 128;
if (absTick & 0x80000 != 0) price = (price * 0x48a170391f7dc42444e8fa2) >> 128;
assembly {
// if (tick > 0) price = type(uint256).max / price;
if sgt(tick, 0) { price := div(not(0), price) }
// this divides by 1<<32 rounding up to go from a Q128.128 to a Q128.96.
// we then downcast because we know the result always fits within 160 bits due to our tick input constraint
// we round up in the division so getTickAtSqrtPrice of the output price is always consistent
// `sub(shl(32, 1), 1)` is `type(uint32).max`
// `price + type(uint32).max` will not overflow because `price` fits in 192 bits
sqrtPriceX96 := shr(32, add(price, sub(shl(32, 1), 1)))
}
}
}
/// @notice Calculates the greatest tick value such that getPriceAtTick(tick) <= price
/// @dev Throws in case sqrtPriceX96 < MIN_SQRT_PRICE, as MIN_SQRT_PRICE is the lowest value getPriceAtTick may
/// ever return.
/// @param sqrtPriceX96 The sqrt price for which to compute the tick as a Q64.96
/// @return tick The greatest tick for which the price is less than or equal to the input price
function getTickAtSqrtPrice(uint160 sqrtPriceX96) internal pure returns (int24 tick) {
unchecked {
// Equivalent: if (sqrtPriceX96 < MIN_SQRT_PRICE || sqrtPriceX96 >= MAX_SQRT_PRICE) revert InvalidSqrtPrice();
// second inequality must be < because the price can never reach the price at the max tick
assembly {
// if sqrtPriceX96 < MIN_SQRT_PRICE, the `sub` underflows and `gt` is true
// if sqrtPriceX96 >= MAX_SQRT_PRICE, sqrtPriceX96 - MIN_SQRT_PRICE > MAX_SQRT_PRICE - MIN_SQRT_PRICE - 1
if gt(sub(sqrtPriceX96, MIN_SQRT_PRICE), MAX_SQRT_PRICE_MINUS_MIN_SQRT_PRICE_MINUS_ONE) {
// store 4-byte selector of "InvalidSqrtPrice()" at memory [0x1c, 0x20)
mstore(0, 0x31efafe8)
revert(0x1c, 0x04)
}
}
uint256 price = uint256(sqrtPriceX96) << 32;
uint256 r = price;
uint256 msb = 0;
assembly {
let f := shl(7, gt(r, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF))
msb := or(msb, f)
r := shr(f, r)
}
assembly {
let f := shl(6, gt(r, 0xFFFFFFFFFFFFFFFF))
msb := or(msb, f)
r := shr(f, r)
}
assembly {
let f := shl(5, gt(r, 0xFFFFFFFF))
msb := or(msb, f)
r := shr(f, r)
}
assembly {
let f := shl(4, gt(r, 0xFFFF))
msb := or(msb, f)
r := shr(f, r)
}
assembly {
let f := shl(3, gt(r, 0xFF))
msb := or(msb, f)
r := shr(f, r)
}
assembly {
let f := shl(2, gt(r, 0xF))
msb := or(msb, f)
r := shr(f, r)
}
assembly {
let f := shl(1, gt(r, 0x3))
msb := or(msb, f)
r := shr(f, r)
}
assembly {
let f := gt(r, 0x1)
msb := or(msb, f)
}
if (msb >= 128) r = price >> (msb - 127);
else r = price << (127 - msb);
int256 log_2 = (int256(msb) - 128) << 64;
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(63, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(62, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(61, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(60, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(59, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(58, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(57, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(56, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(55, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(54, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(53, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(52, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(51, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(50, f))
}
int256 log_sqrt10001 = log_2 * 255738958999603826347141; // 128.128 number
int24 tickLow = int24((log_sqrt10001 - 3402992956809132418596140100660247210) >> 128);
int24 tickHi = int24((log_sqrt10001 + 291339464771989622907027621153398088495) >> 128);
tick = tickLow == tickHi ? tickLow : getSqrtPriceAtTick(tickHi) <= sqrtPriceX96 ? tickHi : tickLow;
}
}
}// SPDX-License-Identifier: MIT
pragma solidity >=0.8.4 <0.9.0;
/// @title Contains 512-bit math functions
library FullMath {
uint256 constant FIXED_1 = 0x080000000000000000000000000000000;
uint256 constant FIXED_2 = 0x100000000000000000000000000000000;
uint256 constant LOG_E_2 = 6931471806;
uint256 constant BASE = 1e10;
/// @notice Calculates ceil(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
/// @param a The multiplicand
/// @param b The multiplier
/// @param denominator The divisor
/// @return result The 256-bit result
function mulDivRoundingUp(uint256 a, uint256 b, uint256 denominator) internal pure returns (uint256 result) {
unchecked {
result = mulDiv(a, b, denominator);
if (mulmod(a, b, denominator) != 0) {
require(++result > 0);
}
}
}
/// @notice Facilitates multiplication and division that can have overflow of an intermediate value without any loss of precision
/// calculates floor(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
/// @param a The multiplicand
/// @param b The multiplier
/// @param denominator The divisor
/// @return result The 256-bit result
/// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv
// Handles "phantom overflow" i.e., allows multiplication and division where an intermediate value overflows 256 bits
function mulDiv(uint256 a, uint256 b, uint256 denominator) internal pure returns (uint256 result) {
unchecked {
// 512-bit multiply [prod1 prod0] = a * b
// 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 = prod1 * 2**256 + prod0
uint256 prod0 = a * b; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly {
let mm := mulmod(a, b, not(0))
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
// Make sure the result is less than 2**256.
// Also prevents denominator == 0
require(denominator > prod1);
// Handle non-overflow cases, 256 by 256 division
if (prod1 == 0) {
assembly {
result := div(prod0, denominator)
}
return result;
}
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from [prod1 prod0]
// Compute remainder using mulmod
uint256 remainder;
assembly {
remainder := mulmod(a, b, denominator)
}
// Subtract 256 bit number from 512 bit number
assembly {
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
// Factor powers of two out of denominator
// Compute largest power of two divisor of denominator.
// Always >= 1.
uint256 twos = (0 - denominator) & denominator;
// Divide denominator by power of two
assembly {
denominator := div(denominator, twos)
}
// Divide [prod1 prod0] by the factors of two
assembly {
prod0 := div(prod0, twos)
}
// Shift in bits from prod1 into prod0. For this we need
// to flip `twos` such that it is 2**256 / twos.
// If twos is zero, then it becomes one
assembly {
twos := add(div(sub(0, twos), twos), 1)
}
prod0 |= prod1 * twos;
// Invert denominator mod 2**256
// Now that denominator is an odd number, it has an inverse
// modulo 2**256 such that denominator * inv = 1 mod 2**256.
// Compute the inverse by starting with a seed that is correct
// correct for four bits. That is, denominator * inv = 1 mod 2**4
uint256 inv = (3 * denominator) ^ 2;
// 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 *= 2 - denominator * inv; // inverse mod 2**8
inv *= 2 - denominator * inv; // inverse mod 2**16
inv *= 2 - denominator * inv; // inverse mod 2**32
inv *= 2 - denominator * inv; // inverse mod 2**64
inv *= 2 - denominator * inv; // inverse mod 2**128
inv *= 2 - denominator * inv; // inverse mod 2**256
// Because the division is now exact we can divide by multiplying
// with the modular inverse of denominator. This will give us the
// correct result modulo 2**256. Since the preconditions guarantee
// that the outcome is less than 2**256, this is the final result.
// We don't need to compute the high bits of the result and prod1
// is no longer required.
result = prod0 * inv;
return result;
}
}
/// @dev Credit to Paul Razvan Berg https://github.com/hifi-finance/prb-math/blob/main/contracts/PRBMath.sol
function sqrt(uint256 x) internal
pure returns (uint256 result) {
if (x == 0) { return 0; }
// Set the initial guess to the closest
// power of two that is higher than x.
uint256 xAux = uint256(x); result = 1;
if (xAux >= 0x100000000000000000000000000000000) {
xAux >>= 128; result <<= 64;
}
if (xAux >= 0x10000000000000000) {
xAux >>= 64; result <<= 32;
}
if (xAux >= 0x100000000) {
xAux >>= 32; result <<= 16;
}
if (xAux >= 0x10000) {
xAux >>= 16; result <<= 8;
}
if (xAux >= 0x100) {
xAux >>= 8; result <<= 4;
}
if (xAux >= 0x10) {
xAux >>= 4; result <<= 2;
}
if (xAux >= 0x8) {
result <<= 1;
}
// The operations can never overflow
// because the result is max 2^127
// when it enters this block...
result = (result + x / result) >> 1;
result = (result + x / result) >> 1;
result = (result + x / result) >> 1;
result = (result + x / result) >> 1;
result = (result + x / result) >> 1;
result = (result + x / result) >> 1;
result = (result + x / result) >> 1;
// Seven iterations should be enough
uint256 roundedDownResult = x / result;
return result >= roundedDownResult ?
roundedDownResult : result;
}
function floorLog2(uint256 _n)
internal pure returns (uint8) {
uint8 res = 0;
if (_n < 256) {
// At most 8 iterations
while (_n > 1) {
_n >>= 1;
res += 1;
}
} else {
// Exactly 8 iterations
for (uint8 s = 128; s > 0; s >>= 1) {
if (_n >= (uint256(1) << s)) {
_n >>= s;
res |= s;
}
}
}
return res;
}
/// @dev credit github.com/ribbon-finance/rvol/blob/master/contracts/libraries/Math.sol
function ln(uint256 x) internal
pure returns (uint256) {
uint256 res = 0;
// If x >= 2, then we compute the integer part
// of log2(x), which is larger than 0.
if (x >= FIXED_2) {
uint8 count = floorLog2(x / FIXED_1);
x >>= count; // now x < 2
res = count * FIXED_1;
}
// If x > 1, then we compute the fraction part
// of log2(x), which is larger than 0.
if (x > FIXED_1) {
for (uint8 i = 127; i > 0; --i) {
x = (x * x) / FIXED_1; // now 1 < x < 4
if (x >= FIXED_2) {
x >>= 1; // now 1 < x < 2
res += uint256(1) << (i - 1);
}
}
}
return (res * LOG_E_2) / BASE;
}
function max(uint _a, uint _b) internal
pure returns (uint) { return (_a > _b) ?
_a : _b;
}
function min(uint _a, uint _b) internal
pure returns (uint) { return (_a < _b) ?
_a : _b;
}
}// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
import {ERC20} from "../tokens/ERC20.sol";
/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer.
/// @dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller.
library SafeTransferLib {
/*//////////////////////////////////////////////////////////////
ETH OPERATIONS
//////////////////////////////////////////////////////////////*/
function safeTransferETH(address to, uint256 amount) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
// Transfer the ETH and store if it succeeded or not.
success := call(gas(), to, amount, 0, 0, 0, 0)
}
require(success, "ETH_TRANSFER_FAILED");
}
/*//////////////////////////////////////////////////////////////
ERC20 OPERATIONS
//////////////////////////////////////////////////////////////*/
function safeTransferFrom(
ERC20 token,
address from,
address to,
uint256 amount
) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), and(from, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "from" argument.
mstore(add(freeMemoryPointer, 36), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 100 because the length of our calldata totals up like so: 4 + 32 * 3.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 100, 0, 32)
)
}
require(success, "TRANSFER_FROM_FAILED");
}
function safeTransfer(
ERC20 token,
address to,
uint256 amount
) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
)
}
require(success, "TRANSFER_FAILED");
}
function safeApprove(
ERC20 token,
address to,
uint256 amount
) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
)
}
require(success, "APPROVE_FAILED");
}
}// SPDX-License-Identifier: MIT
pragma solidity >=0.8.4 <0.9.0;
import {FullMath} from "./FullMath.sol";
/// @title Liquidity amount functions
/// @notice Provides functions for computing liquidity amounts from token amounts and prices
library LiquidityAmounts {
uint8 public constant RESOLUTION = 96;
uint128 public constant Q96 = 0x1000000000000000000000000;
// see https://en.wikipedia.org/wiki/Q_(number_format)
/// @notice Downcasts uint256 to uint128
/// @param x The uint258 to be downcasted
/// @return y The passed value, downcasted to uint128
function toUint128(uint256 x) private pure returns (uint128 y) {
require((y = uint128(x)) == x);
}
/// @notice Computes the amount of liquidity received for a given amount of token0 and price range
/// @dev Calculates amount0 * (sqrt(upper) * sqrt(lower)) / (sqrt(upper) - sqrt(lower))
/// @param sqrtRatioAX96 A sqrt price representing the first tick boundary
/// @param sqrtRatioBX96 A sqrt price representing the second tick boundary
/// @param amount0 The amount0 being sent in
/// @return liquidity The amount of returned liquidity
function getLiquidityForAmount0(
uint160 sqrtRatioAX96,
uint160 sqrtRatioBX96,
uint256 amount0
) internal pure returns (uint128 liquidity) {
if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);
uint256 intermediate = FullMath.mulDiv(sqrtRatioAX96, sqrtRatioBX96, Q96);
return toUint128(FullMath.mulDiv(amount0, intermediate, sqrtRatioBX96 - sqrtRatioAX96));
}
/// @notice Computes the amount of liquidity received for a given amount of token1 and price range
/// @dev Calculates amount1 / (sqrt(upper) - sqrt(lower)).
/// @param sqrtRatioAX96 A sqrt price representing the first tick boundary
/// @param sqrtRatioBX96 A sqrt price representing the second tick boundary
/// @param amount1 The amount1 being sent in
/// @return liquidity The amount of returned liquidity
function getLiquidityForAmount1(
uint160 sqrtRatioAX96,
uint160 sqrtRatioBX96,
uint256 amount1
) internal pure returns (uint128 liquidity) {
if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);
return toUint128(FullMath.mulDiv(amount1, Q96, sqrtRatioBX96 - sqrtRatioAX96));
}
/// @notice Computes the maximum amount of liquidity received for a given amount of token0, token1, the current
/// pool prices and the prices at the tick boundaries
/// @param sqrtRatioX96 A sqrt price representing the current pool prices
/// @param sqrtRatioAX96 A sqrt price representing the first tick boundary
/// @param sqrtRatioBX96 A sqrt price representing the second tick boundary
/// @param amount0 The amount of token0 being sent in
/// @param amount1 The amount of token1 being sent in
/// @return liquidity The maximum amount of liquidity received
function getLiquidityForAmounts(
uint160 sqrtRatioX96,
uint160 sqrtRatioAX96,
uint160 sqrtRatioBX96,
uint256 amount0,
uint256 amount1
) internal pure returns (uint128 liquidity) {
if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);
if (sqrtRatioX96 <= sqrtRatioAX96) {
liquidity = getLiquidityForAmount0(sqrtRatioAX96, sqrtRatioBX96, amount0);
} else if (sqrtRatioX96 < sqrtRatioBX96) {
uint128 liquidity0 = getLiquidityForAmount0(sqrtRatioX96, sqrtRatioBX96, amount0);
uint128 liquidity1 = getLiquidityForAmount1(sqrtRatioAX96, sqrtRatioX96, amount1);
liquidity = liquidity0 < liquidity1 ? liquidity0 : liquidity1;
} else {
liquidity = getLiquidityForAmount1(sqrtRatioAX96, sqrtRatioBX96, amount1);
}
}
/// @notice Computes the amount of token0 for a given amount of liquidity and a price range
/// @param sqrtRatioAX96 A sqrt price representing the first tick boundary
/// @param sqrtRatioBX96 A sqrt price representing the second tick boundary
/// @param liquidity The liquidity being valued
/// @return amount0 The amount of token0
function getAmount0ForLiquidity(
uint160 sqrtRatioAX96,
uint160 sqrtRatioBX96,
uint128 liquidity
) internal pure returns (uint256 amount0) {
if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);
return
FullMath.mulDiv(
uint256(liquidity) << RESOLUTION,
sqrtRatioBX96 - sqrtRatioAX96,
sqrtRatioBX96
) / sqrtRatioAX96;
}
/// @notice Computes the amount of token1 for a given amount of liquidity and a price range
/// @param sqrtRatioAX96 A sqrt price representing the first tick boundary
/// @param sqrtRatioBX96 A sqrt price representing the second tick boundary
/// @param liquidity The liquidity being valued
/// @return amount1 The amount of token1
function getAmount1ForLiquidity(
uint160 sqrtRatioAX96,
uint160 sqrtRatioBX96,
uint128 liquidity
) internal pure returns (uint256 amount1) {
if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);
return FullMath.mulDiv(liquidity, sqrtRatioBX96 - sqrtRatioAX96, Q96);
}
/// @notice Computes the token0 and token1 value for a given amount of liquidity, the current
/// pool prices and the prices at the tick boundaries
/// @param sqrtRatioX96 A sqrt price representing the current pool prices
/// @param sqrtRatioAX96 A sqrt price representing the first tick boundary
/// @param sqrtRatioBX96 A sqrt price representing the second tick boundary
/// @param liquidity The liquidity being valued
/// @return amount0 The amount of token0
/// @return amount1 The amount of token1
function getAmountsForLiquidity(
uint160 sqrtRatioX96,
uint160 sqrtRatioAX96,
uint160 sqrtRatioBX96,
uint128 liquidity
) internal pure returns (uint256 amount0, uint256 amount1) {
if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);
if (sqrtRatioX96 <= sqrtRatioAX96) {
amount0 = getAmount0ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, liquidity);
} else if (sqrtRatioX96 < sqrtRatioBX96) {
amount0 = getAmount0ForLiquidity(sqrtRatioX96, sqrtRatioBX96, liquidity);
amount1 = getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioX96, liquidity);
} else {
amount1 = getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, liquidity);
}
}
}// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.7.5;
pragma abicoder v2;
/// @title Non-fungible token for positions
/// @notice Wraps Uniswap V3 positions in a non-fungible token interface which allows for them to be transferred
/// and authorized.
interface INonfungiblePositionManager {
/// @notice Emitted when liquidity is increased for a position NFT
/// @dev Also emitted when a token is minted
/// @param tokenId The ID of the token for which liquidity was increased
/// @param liquidity The amount by which liquidity for the NFT position was increased
/// @param amount0 The amount of token0 that was paid for the increase in liquidity
/// @param amount1 The amount of token1 that was paid for the increase in liquidity
event IncreaseLiquidity(uint256 indexed tokenId, uint128 liquidity, uint256 amount0, uint256 amount1);
/// @notice Emitted when liquidity is decreased for a position NFT
/// @param tokenId The ID of the token for which liquidity was decreased
/// @param liquidity The amount by which liquidity for the NFT position was decreased
/// @param amount0 The amount of token0 that was accounted for the decrease in liquidity
/// @param amount1 The amount of token1 that was accounted for the decrease in liquidity
event DecreaseLiquidity(uint256 indexed tokenId, uint128 liquidity, uint256 amount0, uint256 amount1);
/// @notice Emitted when tokens are collected for a position NFT
/// @dev The amounts reported may not be exactly equivalent to the amounts transferred, due to rounding behavior
/// @param tokenId The ID of the token for which underlying tokens were collected
/// @param recipient The address of the account that received the collected tokens
/// @param amount0 The amount of token0 owed to the position that was collected
/// @param amount1 The amount of token1 owed to the position that was collected
event Collect(uint256 indexed tokenId, address recipient, uint256 amount0, uint256 amount1);
/// @notice Returns the position information associated with a given token ID.
/// @dev Throws if the token ID is not valid.
/// @param tokenId The ID of the token that represents the position
/// @return nonce The nonce for permits
/// @return operator The address that is approved for spending
/// @return token0 The address of the token0 for a specific pool
/// @return token1 The address of the token1 for a specific pool
/// @return fee The fee associated with the pool
/// @return tickLower The lower end of the tick range for the position
/// @return tickUpper The higher end of the tick range for the position
/// @return liquidity The liquidity of the position
/// @return feeGrowthInside0LastX128 The fee growth of token0 as of the last action on the individual position
/// @return feeGrowthInside1LastX128 The fee growth of token1 as of the last action on the individual position
/// @return tokensOwed0 The uncollected amount of token0 owed to the position as of the last computation
/// @return tokensOwed1 The uncollected amount of token1 owed to the position as of the last computation
function positions(uint256 tokenId)
external
view
returns (
uint96 nonce,
address operator,
address token0,
address token1,
uint24 fee,
int24 tickLower,
int24 tickUpper,
uint128 liquidity,
uint256 feeGrowthInside0LastX128,
uint256 feeGrowthInside1LastX128,
uint128 tokensOwed0,
uint128 tokensOwed1
);
struct MintParams {
address token0;
address token1;
uint24 fee;
int24 tickLower;
int24 tickUpper;
uint256 amount0Desired;
uint256 amount1Desired;
uint256 amount0Min;
uint256 amount1Min;
address recipient;
uint256 deadline;
}
/// @notice Creates a new position wrapped in a NFT
/// @dev Call this when the pool does exist and is initialized. Note that if the pool is created but not initialized
/// a method does not exist, i.e. the pool is assumed to be initialized.
/// @param params The params necessary to mint a position, encoded as `MintParams` in calldata
/// @return tokenId The ID of the token that represents the minted position
/// @return liquidity The amount of liquidity for this position
/// @return amount0 The amount of token0
/// @return amount1 The amount of token1
function mint(MintParams calldata params)
external
payable
returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1);
struct IncreaseLiquidityParams {
uint256 tokenId;
uint256 amount0Desired;
uint256 amount1Desired;
uint256 amount0Min;
uint256 amount1Min;
uint256 deadline;
}
/// @notice Increases the amount of liquidity in a position, with tokens paid by the `msg.sender`
/// @param params tokenId The ID of the token for which liquidity is being increased,
/// amount0Desired The desired amount of token0 to be spent,
/// amount1Desired The desired amount of token1 to be spent,
/// amount0Min The minimum amount of token0 to spend, which serves as a slippage check,
/// amount1Min The minimum amount of token1 to spend, which serves as a slippage check,
/// deadline The time by which the transaction must be included to effect the change
/// @return liquidity The new liquidity amount as a result of the increase
/// @return amount0 The amount of token0 to acheive resulting liquidity
/// @return amount1 The amount of token1 to acheive resulting liquidity
function increaseLiquidity(IncreaseLiquidityParams calldata params)
external
payable
returns (uint128 liquidity, uint256 amount0, uint256 amount1);
struct DecreaseLiquidityParams {
uint256 tokenId;
uint128 liquidity;
uint256 amount0Min;
uint256 amount1Min;
uint256 deadline;
}
/// @notice Decreases the amount of liquidity in a position and accounts it to the position
/// @param params tokenId The ID of the token for which liquidity is being decreased,
/// amount The amount by which liquidity will be decreased,
/// amount0Min The minimum amount of token0 that should be accounted for the burned liquidity,
/// amount1Min The minimum amount of token1 that should be accounted for the burned liquidity,
/// deadline The time by which the transaction must be included to effect the change
/// @return amount0 The amount of token0 accounted to the position's tokens owed
/// @return amount1 The amount of token1 accounted to the position's tokens owed
function decreaseLiquidity(DecreaseLiquidityParams calldata params)
external
payable
returns (uint256 amount0, uint256 amount1);
struct CollectParams {
uint256 tokenId;
address recipient;
uint128 amount0Max;
uint128 amount1Max;
}
/// @notice Collects up to a maximum amount of fees owed to a specific position to the recipient
/// @param params tokenId The ID of the NFT for which tokens are being collected,
/// recipient The account that should receive the tokens,
/// amount0Max The maximum amount of token0 to collect,
/// amount1Max The maximum amount of token1 to collect
/// @return amount0 The amount of fees collected in token0
/// @return amount1 The amount of fees collected in token1
function collect(CollectParams calldata params) external payable returns (uint256 amount0, uint256 amount1);
/// @notice Burns a token ID, which deletes it from the NFT contract. The token must have 0 liquidity and all tokens
/// must be collected first.
/// @param tokenId The ID of the token that is being burned
function burn(uint256 tokenId) external payable;
}// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity >=0.8.4 <0.9.0;
import {ERC20} from "solmate/src/tokens/ERC20.sol";
contract mock is ERC20 {
address internal rover;
modifier onlyVogue {
require(msg.sender == address(rover), "403"); _;
}
constructor(address _rover, uint8 _decimals)
ERC20("mock", "mock", _decimals) {
rover = _rover; // Vogue range...
}
function mint(uint amount) onlyVogue external {
_mint(msg.sender, amount);
}
function burn(uint amount) onlyVogue external {
_burn(msg.sender, amount);
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IUnlockCallback} from "@uniswap/v4-core/src/interfaces/callback/IUnlockCallback.sol";
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {ImmutableState} from "./ImmutableState.sol";
/// @title Safe Callback
/// @notice A contract that only allows the Uniswap v4 PoolManager to call the unlockCallback
abstract contract SafeCallback is ImmutableState, IUnlockCallback {
constructor(IPoolManager _poolManager) ImmutableState(_poolManager) {}
/// @inheritdoc IUnlockCallback
/// @dev We force the onlyPoolManager modifier by exposing a virtual function after the onlyPoolManager check.
function unlockCallback(bytes calldata data) external onlyPoolManager returns (bytes memory) {
return _unlockCallback(data);
}
/// @dev to be implemented by the child contract, to safely guarantee the logic is only executed by the PoolManager
function _unlockCallback(bytes calldata data) internal virtual returns (bytes memory);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {SafeCast} from "../libraries/SafeCast.sol";
/// @dev Two `int128` values packed into a single `int256` where the upper 128 bits represent the amount0
/// and the lower 128 bits represent the amount1.
type BalanceDelta is int256;
using {add as +, sub as -, eq as ==, neq as !=} for BalanceDelta global;
using BalanceDeltaLibrary for BalanceDelta global;
using SafeCast for int256;
function toBalanceDelta(int128 _amount0, int128 _amount1) pure returns (BalanceDelta balanceDelta) {
assembly ("memory-safe") {
balanceDelta := or(shl(128, _amount0), and(sub(shl(128, 1), 1), _amount1))
}
}
function add(BalanceDelta a, BalanceDelta b) pure returns (BalanceDelta) {
int256 res0;
int256 res1;
assembly ("memory-safe") {
let a0 := sar(128, a)
let a1 := signextend(15, a)
let b0 := sar(128, b)
let b1 := signextend(15, b)
res0 := add(a0, b0)
res1 := add(a1, b1)
}
return toBalanceDelta(res0.toInt128(), res1.toInt128());
}
function sub(BalanceDelta a, BalanceDelta b) pure returns (BalanceDelta) {
int256 res0;
int256 res1;
assembly ("memory-safe") {
let a0 := sar(128, a)
let a1 := signextend(15, a)
let b0 := sar(128, b)
let b1 := signextend(15, b)
res0 := sub(a0, b0)
res1 := sub(a1, b1)
}
return toBalanceDelta(res0.toInt128(), res1.toInt128());
}
function eq(BalanceDelta a, BalanceDelta b) pure returns (bool) {
return BalanceDelta.unwrap(a) == BalanceDelta.unwrap(b);
}
function neq(BalanceDelta a, BalanceDelta b) pure returns (bool) {
return BalanceDelta.unwrap(a) != BalanceDelta.unwrap(b);
}
/// @notice Library for getting the amount0 and amount1 deltas from the BalanceDelta type
library BalanceDeltaLibrary {
/// @notice A BalanceDelta of 0
BalanceDelta public constant ZERO_DELTA = BalanceDelta.wrap(0);
function amount0(BalanceDelta balanceDelta) internal pure returns (int128 _amount0) {
assembly ("memory-safe") {
_amount0 := sar(128, balanceDelta)
}
}
function amount1(BalanceDelta balanceDelta) internal pure returns (int128 _amount1) {
assembly ("memory-safe") {
_amount1 := signextend(15, balanceDelta)
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {IPoolManager} from "../interfaces/IPoolManager.sol";
import {Currency} from "../types/Currency.sol";
import {CurrencyReserves} from "./CurrencyReserves.sol";
import {NonzeroDeltaCount} from "./NonzeroDeltaCount.sol";
import {Lock} from "./Lock.sol";
/// @notice A helper library to provide state getters that use exttload
library TransientStateLibrary {
/// @notice returns the reserves for the synced currency
/// @param manager The pool manager contract.
/// @return uint256 The reserves of the currency.
/// @dev returns 0 if the reserves are not synced or value is 0.
/// Checks the synced currency to only return valid reserve values (after a sync and before a settle).
function getSyncedReserves(IPoolManager manager) internal view returns (uint256) {
if (getSyncedCurrency(manager).isAddressZero()) return 0;
return uint256(manager.exttload(CurrencyReserves.RESERVES_OF_SLOT));
}
function getSyncedCurrency(IPoolManager manager) internal view returns (Currency) {
return Currency.wrap(address(uint160(uint256(manager.exttload(CurrencyReserves.CURRENCY_SLOT)))));
}
/// @notice Returns the number of nonzero deltas open on the PoolManager that must be zeroed out before the contract is locked
function getNonzeroDeltaCount(IPoolManager manager) internal view returns (uint256) {
return uint256(manager.exttload(NonzeroDeltaCount.NONZERO_DELTA_COUNT_SLOT));
}
/// @notice Get the current delta for a caller in the given currency
/// @param target The credited account address
/// @param currency The currency for which to lookup the delta
function currencyDelta(IPoolManager manager, address target, Currency currency) internal view returns (int256) {
bytes32 key;
assembly ("memory-safe") {
mstore(0, and(target, 0xffffffffffffffffffffffffffffffffffffffff))
mstore(32, and(currency, 0xffffffffffffffffffffffffffffffffffffffff))
key := keccak256(0, 64)
}
return int256(uint256(manager.exttload(key)));
}
/// @notice Returns whether the contract is unlocked or not
function isUnlocked(IPoolManager manager) internal view returns (bool) {
return manager.exttload(Lock.IS_UNLOCKED_SLOT) != 0x0;
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IERC20Minimal} from "../interfaces/external/IERC20Minimal.sol";
import {CustomRevert} from "../libraries/CustomRevert.sol";
type Currency is address;
using {greaterThan as >, lessThan as <, greaterThanOrEqualTo as >=, equals as ==} for Currency global;
using CurrencyLibrary for Currency global;
function equals(Currency currency, Currency other) pure returns (bool) {
return Currency.unwrap(currency) == Currency.unwrap(other);
}
function greaterThan(Currency currency, Currency other) pure returns (bool) {
return Currency.unwrap(currency) > Currency.unwrap(other);
}
function lessThan(Currency currency, Currency other) pure returns (bool) {
return Currency.unwrap(currency) < Currency.unwrap(other);
}
function greaterThanOrEqualTo(Currency currency, Currency other) pure returns (bool) {
return Currency.unwrap(currency) >= Currency.unwrap(other);
}
/// @title CurrencyLibrary
/// @dev This library allows for transferring and holding native tokens and ERC20 tokens
library CurrencyLibrary {
/// @notice Additional context for ERC-7751 wrapped error when a native transfer fails
error NativeTransferFailed();
/// @notice Additional context for ERC-7751 wrapped error when an ERC20 transfer fails
error ERC20TransferFailed();
/// @notice A constant to represent the native currency
Currency public constant ADDRESS_ZERO = Currency.wrap(address(0));
function transfer(Currency currency, address to, uint256 amount) internal {
// altered from https://github.com/transmissions11/solmate/blob/44a9963d4c78111f77caa0e65d677b8b46d6f2e6/src/utils/SafeTransferLib.sol
// modified custom error selectors
bool success;
if (currency.isAddressZero()) {
assembly ("memory-safe") {
// Transfer the ETH and revert if it fails.
success := call(gas(), to, amount, 0, 0, 0, 0)
}
// revert with NativeTransferFailed, containing the bubbled up error as an argument
if (!success) {
CustomRevert.bubbleUpAndRevertWith(to, bytes4(0), NativeTransferFailed.selector);
}
} else {
assembly ("memory-safe") {
// Get a pointer to some free memory.
let fmp := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(fmp, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
mstore(add(fmp, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
mstore(add(fmp, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.
success :=
and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), currency, 0, fmp, 68, 0, 32)
)
// Now clean the memory we used
mstore(fmp, 0) // 4 byte `selector` and 28 bytes of `to` were stored here
mstore(add(fmp, 0x20), 0) // 4 bytes of `to` and 28 bytes of `amount` were stored here
mstore(add(fmp, 0x40), 0) // 4 bytes of `amount` were stored here
}
// revert with ERC20TransferFailed, containing the bubbled up error as an argument
if (!success) {
CustomRevert.bubbleUpAndRevertWith(
Currency.unwrap(currency), IERC20Minimal.transfer.selector, ERC20TransferFailed.selector
);
}
}
}
function balanceOfSelf(Currency currency) internal view returns (uint256) {
if (currency.isAddressZero()) {
return address(this).balance;
} else {
return IERC20Minimal(Currency.unwrap(currency)).balanceOf(address(this));
}
}
function balanceOf(Currency currency, address owner) internal view returns (uint256) {
if (currency.isAddressZero()) {
return owner.balance;
} else {
return IERC20Minimal(Currency.unwrap(currency)).balanceOf(owner);
}
}
function isAddressZero(Currency currency) internal pure returns (bool) {
return Currency.unwrap(currency) == Currency.unwrap(ADDRESS_ZERO);
}
function toId(Currency currency) internal pure returns (uint256) {
return uint160(Currency.unwrap(currency));
}
// If the upper 12 bytes are non-zero, they will be zero-ed out
// Therefore, fromId() and toId() are not inverses of each other
function fromId(uint256 id) internal pure returns (Currency) {
return Currency.wrap(address(uint160(id)));
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Currency} from "../../src/types/Currency.sol";
import {IERC20Minimal} from "../../src/interfaces/external/IERC20Minimal.sol";
import {IPoolManager} from "../../src/interfaces/IPoolManager.sol";
/// @notice Library used to interact with PoolManager.sol to settle any open deltas.
/// To settle a positive delta (a credit to the user), a user may take or mint.
/// To settle a negative delta (a debt on the user), a user make transfer or burn to pay off a debt.
/// @dev Note that sync() is called before any erc-20 transfer in `settle`.
library CurrencySettler {
/// @notice Settle (pay) a currency to the PoolManager
/// @param currency Currency to settle
/// @param manager IPoolManager to settle to
/// @param payer Address of the payer, the token sender
/// @param amount Amount to send
/// @param burn If true, burn the ERC-6909 token, otherwise ERC20-transfer to the PoolManager
function settle(Currency currency, IPoolManager manager, address payer, uint256 amount, bool burn) internal {
// for native currencies or burns, calling sync is not required
// short circuit for ERC-6909 burns to support ERC-6909-wrapped native tokens
if (burn) {
manager.burn(payer, currency.toId(), amount);
} else if (currency.isAddressZero()) {
manager.settle{value: amount}();
} else {
manager.sync(currency);
if (payer != address(this)) {
IERC20Minimal(Currency.unwrap(currency)).transferFrom(payer, address(manager), amount);
} else {
IERC20Minimal(Currency.unwrap(currency)).transfer(address(manager), amount);
}
manager.settle();
}
}
/// @notice Take (receive) a currency from the PoolManager
/// @param currency Currency to take
/// @param manager IPoolManager to take from
/// @param recipient Address of the recipient, the token receiver
/// @param amount Amount to receive
/// @param claims If true, mint the ERC-6909 token, otherwise ERC20-transfer from the PoolManager to recipient
function take(Currency currency, IPoolManager manager, address recipient, uint256 amount, bool claims) internal {
claims ? manager.mint(recipient, currency.toId(), amount) : manager.take(currency, recipient, amount);
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {Currency} from "../types/Currency.sol";
import {PoolKey} from "../types/PoolKey.sol";
import {IHooks} from "./IHooks.sol";
import {IERC6909Claims} from "./external/IERC6909Claims.sol";
import {IProtocolFees} from "./IProtocolFees.sol";
import {BalanceDelta} from "../types/BalanceDelta.sol";
import {PoolId} from "../types/PoolId.sol";
import {IExtsload} from "./IExtsload.sol";
import {IExttload} from "./IExttload.sol";
/// @notice Interface for the PoolManager
interface IPoolManager is IProtocolFees, IERC6909Claims, IExtsload, IExttload {
/// @notice Thrown when a currency is not netted out after the contract is unlocked
error CurrencyNotSettled();
/// @notice Thrown when trying to interact with a non-initialized pool
error PoolNotInitialized();
/// @notice Thrown when unlock is called, but the contract is already unlocked
error AlreadyUnlocked();
/// @notice Thrown when a function is called that requires the contract to be unlocked, but it is not
error ManagerLocked();
/// @notice Pools are limited to type(int16).max tickSpacing in #initialize, to prevent overflow
error TickSpacingTooLarge(int24 tickSpacing);
/// @notice Pools must have a positive non-zero tickSpacing passed to #initialize
error TickSpacingTooSmall(int24 tickSpacing);
/// @notice PoolKey must have currencies where address(currency0) < address(currency1)
error CurrenciesOutOfOrderOrEqual(address currency0, address currency1);
/// @notice Thrown when a call to updateDynamicLPFee is made by an address that is not the hook,
/// or on a pool that does not have a dynamic swap fee.
error UnauthorizedDynamicLPFeeUpdate();
/// @notice Thrown when trying to swap amount of 0
error SwapAmountCannotBeZero();
///@notice Thrown when native currency is passed to a non native settlement
error NonzeroNativeValue();
/// @notice Thrown when `clear` is called with an amount that is not exactly equal to the open currency delta.
error MustClearExactPositiveDelta();
/// @notice Emitted when a new pool is initialized
/// @param id The abi encoded hash of the pool key struct for the new pool
/// @param currency0 The first currency of the pool by address sort order
/// @param currency1 The second currency of the pool by address sort order
/// @param fee The fee collected upon every swap in the pool, denominated in hundredths of a bip
/// @param tickSpacing The minimum number of ticks between initialized ticks
/// @param hooks The hooks contract address for the pool, or address(0) if none
/// @param sqrtPriceX96 The price of the pool on initialization
/// @param tick The initial tick of the pool corresponding to the initialized price
event Initialize(
PoolId indexed id,
Currency indexed currency0,
Currency indexed currency1,
uint24 fee,
int24 tickSpacing,
IHooks hooks,
uint160 sqrtPriceX96,
int24 tick
);
/// @notice Emitted when a liquidity position is modified
/// @param id The abi encoded hash of the pool key struct for the pool that was modified
/// @param sender The address that modified the pool
/// @param tickLower The lower tick of the position
/// @param tickUpper The upper tick of the position
/// @param liquidityDelta The amount of liquidity that was added or removed
/// @param salt The extra data to make positions unique
event ModifyLiquidity(
PoolId indexed id, address indexed sender, int24 tickLower, int24 tickUpper, int256 liquidityDelta, bytes32 salt
);
/// @notice Emitted for swaps between currency0 and currency1
/// @param id The abi encoded hash of the pool key struct for the pool that was modified
/// @param sender The address that initiated the swap call, and that received the callback
/// @param amount0 The delta of the currency0 balance of the pool
/// @param amount1 The delta of the currency1 balance of the pool
/// @param sqrtPriceX96 The sqrt(price) of the pool after the swap, as a Q64.96
/// @param liquidity The liquidity of the pool after the swap
/// @param tick The log base 1.0001 of the price of the pool after the swap
/// @param fee The swap fee in hundredths of a bip
event Swap(
PoolId indexed id,
address indexed sender,
int128 amount0,
int128 amount1,
uint160 sqrtPriceX96,
uint128 liquidity,
int24 tick,
uint24 fee
);
/// @notice Emitted for donations
/// @param id The abi encoded hash of the pool key struct for the pool that was donated to
/// @param sender The address that initiated the donate call
/// @param amount0 The amount donated in currency0
/// @param amount1 The amount donated in currency1
event Donate(PoolId indexed id, address indexed sender, uint256 amount0, uint256 amount1);
/// @notice All interactions on the contract that account deltas require unlocking. A caller that calls `unlock` must implement
/// `IUnlockCallback(msg.sender).unlockCallback(data)`, where they interact with the remaining functions on this contract.
/// @dev The only functions callable without an unlocking are `initialize` and `updateDynamicLPFee`
/// @param data Any data to pass to the callback, via `IUnlockCallback(msg.sender).unlockCallback(data)`
/// @return The data returned by the call to `IUnlockCallback(msg.sender).unlockCallback(data)`
function unlock(bytes calldata data) external returns (bytes memory);
/// @notice Initialize the state for a given pool ID
/// @dev A swap fee totaling MAX_SWAP_FEE (100%) makes exact output swaps impossible since the input is entirely consumed by the fee
/// @param key The pool key for the pool to initialize
/// @param sqrtPriceX96 The initial square root price
/// @return tick The initial tick of the pool
function initialize(PoolKey memory key, uint160 sqrtPriceX96) external returns (int24 tick);
struct ModifyLiquidityParams {
// the lower and upper tick of the position
int24 tickLower;
int24 tickUpper;
// how to modify the liquidity
int256 liquidityDelta;
// a value to set if you want unique liquidity positions at the same range
bytes32 salt;
}
/// @notice Modify the liquidity for the given pool
/// @dev Poke by calling with a zero liquidityDelta
/// @param key The pool to modify liquidity in
/// @param params The parameters for modifying the liquidity
/// @param hookData The data to pass through to the add/removeLiquidity hooks
/// @return callerDelta The balance delta of the caller of modifyLiquidity. This is the total of both principal, fee deltas, and hook deltas if applicable
/// @return feesAccrued The balance delta of the fees generated in the liquidity range. Returned for informational purposes
/// @dev Note that feesAccrued can be artificially inflated by a malicious actor and integrators should be careful using the value
/// For pools with a single liquidity position, actors can donate to themselves to inflate feeGrowthGlobal (and consequently feesAccrued)
/// atomically donating and collecting fees in the same unlockCallback may make the inflated value more extreme
function modifyLiquidity(PoolKey memory key, ModifyLiquidityParams memory params, bytes calldata hookData)
external
returns (BalanceDelta callerDelta, BalanceDelta feesAccrued);
struct SwapParams {
/// Whether to swap token0 for token1 or vice versa
bool zeroForOne;
/// The desired input amount if negative (exactIn), or the desired output amount if positive (exactOut)
int256 amountSpecified;
/// The sqrt price at which, if reached, the swap will stop executing
uint160 sqrtPriceLimitX96;
}
/// @notice Swap against the given pool
/// @param key The pool to swap in
/// @param params The parameters for swapping
/// @param hookData The data to pass through to the swap hooks
/// @return swapDelta The balance delta of the address swapping
/// @dev Swapping on low liquidity pools may cause unexpected swap amounts when liquidity available is less than amountSpecified.
/// Additionally note that if interacting with hooks that have the BEFORE_SWAP_RETURNS_DELTA_FLAG or AFTER_SWAP_RETURNS_DELTA_FLAG
/// the hook may alter the swap input/output. Integrators should perform checks on the returned swapDelta.
function swap(PoolKey memory key, SwapParams memory params, bytes calldata hookData)
external
returns (BalanceDelta swapDelta);
/// @notice Donate the given currency amounts to the in-range liquidity providers of a pool
/// @dev Calls to donate can be frontrun adding just-in-time liquidity, with the aim of receiving a portion donated funds.
/// Donors should keep this in mind when designing donation mechanisms.
/// @dev This function donates to in-range LPs at slot0.tick. In certain edge-cases of the swap algorithm, the `sqrtPrice` of
/// a pool can be at the lower boundary of tick `n`, but the `slot0.tick` of the pool is already `n - 1`. In this case a call to
/// `donate` would donate to tick `n - 1` (slot0.tick) not tick `n` (getTickAtSqrtPrice(slot0.sqrtPriceX96)).
/// Read the comments in `Pool.swap()` for more information about this.
/// @param key The key of the pool to donate to
/// @param amount0 The amount of currency0 to donate
/// @param amount1 The amount of currency1 to donate
/// @param hookData The data to pass through to the donate hooks
/// @return BalanceDelta The delta of the caller after the donate
function donate(PoolKey memory key, uint256 amount0, uint256 amount1, bytes calldata hookData)
external
returns (BalanceDelta);
/// @notice Writes the current ERC20 balance of the specified currency to transient storage
/// This is used to checkpoint balances for the manager and derive deltas for the caller.
/// @dev This MUST be called before any ERC20 tokens are sent into the contract, but can be skipped
/// for native tokens because the amount to settle is determined by the sent value.
/// However, if an ERC20 token has been synced and not settled, and the caller instead wants to settle
/// native funds, this function can be called with the native currency to then be able to settle the native currency
function sync(Currency currency) external;
/// @notice Called by the user to net out some value owed to the user
/// @dev Will revert if the requested amount is not available, consider using `mint` instead
/// @dev Can also be used as a mechanism for free flash loans
/// @param currency The currency to withdraw from the pool manager
/// @param to The address to withdraw to
/// @param amount The amount of currency to withdraw
function take(Currency currency, address to, uint256 amount) external;
/// @notice Called by the user to pay what is owed
/// @return paid The amount of currency settled
function settle() external payable returns (uint256 paid);
/// @notice Called by the user to pay on behalf of another address
/// @param recipient The address to credit for the payment
/// @return paid The amount of currency settled
function settleFor(address recipient) external payable returns (uint256 paid);
/// @notice WARNING - Any currency that is cleared, will be non-retrievable, and locked in the contract permanently.
/// A call to clear will zero out a positive balance WITHOUT a corresponding transfer.
/// @dev This could be used to clear a balance that is considered dust.
/// Additionally, the amount must be the exact positive balance. This is to enforce that the caller is aware of the amount being cleared.
function clear(Currency currency, uint256 amount) external;
/// @notice Called by the user to move value into ERC6909 balance
/// @param to The address to mint the tokens to
/// @param id The currency address to mint to ERC6909s, as a uint256
/// @param amount The amount of currency to mint
/// @dev The id is converted to a uint160 to correspond to a currency address
/// If the upper 12 bytes are not 0, they will be 0-ed out
function mint(address to, uint256 id, uint256 amount) external;
/// @notice Called by the user to move value from ERC6909 balance
/// @param from The address to burn the tokens from
/// @param id The currency address to burn from ERC6909s, as a uint256
/// @param amount The amount of currency to burn
/// @dev The id is converted to a uint160 to correspond to a currency address
/// If the upper 12 bytes are not 0, they will be 0-ed out
function burn(address from, uint256 id, uint256 amount) external;
/// @notice Updates the pools lp fees for the a pool that has enabled dynamic lp fees.
/// @dev A swap fee totaling MAX_SWAP_FEE (100%) makes exact output swaps impossible since the input is entirely consumed by the fee
/// @param key The key of the pool to update dynamic LP fees for
/// @param newDynamicLPFee The new dynamic pool LP fee
function updateDynamicLPFee(PoolKey memory key, uint24 newDynamicLPFee) external;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {PoolKey} from "./PoolKey.sol";
type PoolId is bytes32;
/// @notice Library for computing the ID of a pool
library PoolIdLibrary {
/// @notice Returns value equal to keccak256(abi.encode(poolKey))
function toId(PoolKey memory poolKey) internal pure returns (PoolId poolId) {
assembly ("memory-safe") {
// 0xa0 represents the total size of the poolKey struct (5 slots of 32 bytes)
poolId := keccak256(poolKey, 0xa0)
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {PoolId} from "../types/PoolId.sol";
import {IPoolManager} from "../interfaces/IPoolManager.sol";
import {Position} from "./Position.sol";
/// @notice A helper library to provide state getters that use extsload
library StateLibrary {
/// @notice index of pools mapping in the PoolManager
bytes32 public constant POOLS_SLOT = bytes32(uint256(6));
/// @notice index of feeGrowthGlobal0X128 in Pool.State
uint256 public constant FEE_GROWTH_GLOBAL0_OFFSET = 1;
// feeGrowthGlobal1X128 offset in Pool.State = 2
/// @notice index of liquidity in Pool.State
uint256 public constant LIQUIDITY_OFFSET = 3;
/// @notice index of TicksInfo mapping in Pool.State: mapping(int24 => TickInfo) ticks;
uint256 public constant TICKS_OFFSET = 4;
/// @notice index of tickBitmap mapping in Pool.State
uint256 public constant TICK_BITMAP_OFFSET = 5;
/// @notice index of Position.State mapping in Pool.State: mapping(bytes32 => Position.State) positions;
uint256 public constant POSITIONS_OFFSET = 6;
/**
* @notice Get Slot0 of the pool: sqrtPriceX96, tick, protocolFee, lpFee
* @dev Corresponds to pools[poolId].slot0
* @param manager The pool manager contract.
* @param poolId The ID of the pool.
* @return sqrtPriceX96 The square root of the price of the pool, in Q96 precision.
* @return tick The current tick of the pool.
* @return protocolFee The protocol fee of the pool.
* @return lpFee The swap fee of the pool.
*/
function getSlot0(IPoolManager manager, PoolId poolId)
internal
view
returns (uint160 sqrtPriceX96, int24 tick, uint24 protocolFee, uint24 lpFee)
{
// slot key of Pool.State value: `pools[poolId]`
bytes32 stateSlot = _getPoolStateSlot(poolId);
bytes32 data = manager.extsload(stateSlot);
// 24 bits |24bits|24bits |24 bits|160 bits
// 0x000000 |000bb8|000000 |ffff75 |0000000000000000fe3aa841ba359daa0ea9eff7
// ---------- | fee |protocolfee | tick | sqrtPriceX96
assembly ("memory-safe") {
// bottom 160 bits of data
sqrtPriceX96 := and(data, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
// next 24 bits of data
tick := signextend(2, shr(160, data))
// next 24 bits of data
protocolFee := and(shr(184, data), 0xFFFFFF)
// last 24 bits of data
lpFee := and(shr(208, data), 0xFFFFFF)
}
}
/**
* @notice Retrieves the tick information of a pool at a specific tick.
* @dev Corresponds to pools[poolId].ticks[tick]
* @param manager The pool manager contract.
* @param poolId The ID of the pool.
* @param tick The tick to retrieve information for.
* @return liquidityGross The total position liquidity that references this tick
* @return liquidityNet The amount of net liquidity added (subtracted) when tick is crossed from left to right (right to left)
* @return feeGrowthOutside0X128 fee growth per unit of liquidity on the _other_ side of this tick (relative to the current tick)
* @return feeGrowthOutside1X128 fee growth per unit of liquidity on the _other_ side of this tick (relative to the current tick)
*/
function getTickInfo(IPoolManager manager, PoolId poolId, int24 tick)
internal
view
returns (
uint128 liquidityGross,
int128 liquidityNet,
uint256 feeGrowthOutside0X128,
uint256 feeGrowthOutside1X128
)
{
bytes32 slot = _getTickInfoSlot(poolId, tick);
// read all 3 words of the TickInfo struct
bytes32[] memory data = manager.extsload(slot, 3);
assembly ("memory-safe") {
let firstWord := mload(add(data, 32))
liquidityNet := sar(128, firstWord)
liquidityGross := and(firstWord, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
feeGrowthOutside0X128 := mload(add(data, 64))
feeGrowthOutside1X128 := mload(add(data, 96))
}
}
/**
* @notice Retrieves the liquidity information of a pool at a specific tick.
* @dev Corresponds to pools[poolId].ticks[tick].liquidityGross and pools[poolId].ticks[tick].liquidityNet. A more gas efficient version of getTickInfo
* @param manager The pool manager contract.
* @param poolId The ID of the pool.
* @param tick The tick to retrieve liquidity for.
* @return liquidityGross The total position liquidity that references this tick
* @return liquidityNet The amount of net liquidity added (subtracted) when tick is crossed from left to right (right to left)
*/
function getTickLiquidity(IPoolManager manager, PoolId poolId, int24 tick)
internal
view
returns (uint128 liquidityGross, int128 liquidityNet)
{
bytes32 slot = _getTickInfoSlot(poolId, tick);
bytes32 value = manager.extsload(slot);
assembly ("memory-safe") {
liquidityNet := sar(128, value)
liquidityGross := and(value, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
}
}
/**
* @notice Retrieves the fee growth outside a tick range of a pool
* @dev Corresponds to pools[poolId].ticks[tick].feeGrowthOutside0X128 and pools[poolId].ticks[tick].feeGrowthOutside1X128. A more gas efficient version of getTickInfo
* @param manager The pool manager contract.
* @param poolId The ID of the pool.
* @param tick The tick to retrieve fee growth for.
* @return feeGrowthOutside0X128 fee growth per unit of liquidity on the _other_ side of this tick (relative to the current tick)
* @return feeGrowthOutside1X128 fee growth per unit of liquidity on the _other_ side of this tick (relative to the current tick)
*/
function getTickFeeGrowthOutside(IPoolManager manager, PoolId poolId, int24 tick)
internal
view
returns (uint256 feeGrowthOutside0X128, uint256 feeGrowthOutside1X128)
{
bytes32 slot = _getTickInfoSlot(poolId, tick);
// offset by 1 word, since the first word is liquidityGross + liquidityNet
bytes32[] memory data = manager.extsload(bytes32(uint256(slot) + 1), 2);
assembly ("memory-safe") {
feeGrowthOutside0X128 := mload(add(data, 32))
feeGrowthOutside1X128 := mload(add(data, 64))
}
}
/**
* @notice Retrieves the global fee growth of a pool.
* @dev Corresponds to pools[poolId].feeGrowthGlobal0X128 and pools[poolId].feeGrowthGlobal1X128
* @param manager The pool manager contract.
* @param poolId The ID of the pool.
* @return feeGrowthGlobal0 The global fee growth for token0.
* @return feeGrowthGlobal1 The global fee growth for token1.
* @dev Note that feeGrowthGlobal can be artificially inflated
* For pools with a single liquidity position, actors can donate to themselves to freely inflate feeGrowthGlobal
* atomically donating and collecting fees in the same unlockCallback may make the inflated value more extreme
*/
function getFeeGrowthGlobals(IPoolManager manager, PoolId poolId)
internal
view
returns (uint256 feeGrowthGlobal0, uint256 feeGrowthGlobal1)
{
// slot key of Pool.State value: `pools[poolId]`
bytes32 stateSlot = _getPoolStateSlot(poolId);
// Pool.State, `uint256 feeGrowthGlobal0X128`
bytes32 slot_feeGrowthGlobal0X128 = bytes32(uint256(stateSlot) + FEE_GROWTH_GLOBAL0_OFFSET);
// read the 2 words of feeGrowthGlobal
bytes32[] memory data = manager.extsload(slot_feeGrowthGlobal0X128, 2);
assembly ("memory-safe") {
feeGrowthGlobal0 := mload(add(data, 32))
feeGrowthGlobal1 := mload(add(data, 64))
}
}
/**
* @notice Retrieves total the liquidity of a pool.
* @dev Corresponds to pools[poolId].liquidity
* @param manager The pool manager contract.
* @param poolId The ID of the pool.
* @return liquidity The liquidity of the pool.
*/
function getLiquidity(IPoolManager manager, PoolId poolId) internal view returns (uint128 liquidity) {
// slot key of Pool.State value: `pools[poolId]`
bytes32 stateSlot = _getPoolStateSlot(poolId);
// Pool.State: `uint128 liquidity`
bytes32 slot = bytes32(uint256(stateSlot) + LIQUIDITY_OFFSET);
liquidity = uint128(uint256(manager.extsload(slot)));
}
/**
* @notice Retrieves the tick bitmap of a pool at a specific tick.
* @dev Corresponds to pools[poolId].tickBitmap[tick]
* @param manager The pool manager contract.
* @param poolId The ID of the pool.
* @param tick The tick to retrieve the bitmap for.
* @return tickBitmap The bitmap of the tick.
*/
function getTickBitmap(IPoolManager manager, PoolId poolId, int16 tick)
internal
view
returns (uint256 tickBitmap)
{
// slot key of Pool.State value: `pools[poolId]`
bytes32 stateSlot = _getPoolStateSlot(poolId);
// Pool.State: `mapping(int16 => uint256) tickBitmap;`
bytes32 tickBitmapMapping = bytes32(uint256(stateSlot) + TICK_BITMAP_OFFSET);
// slot id of the mapping key: `pools[poolId].tickBitmap[tick]
bytes32 slot = keccak256(abi.encodePacked(int256(tick), tickBitmapMapping));
tickBitmap = uint256(manager.extsload(slot));
}
/**
* @notice Retrieves the position information of a pool without needing to calculate the `positionId`.
* @dev Corresponds to pools[poolId].positions[positionId]
* @param poolId The ID of the pool.
* @param owner The owner of the liquidity position.
* @param tickLower The lower tick of the liquidity range.
* @param tickUpper The upper tick of the liquidity range.
* @param salt The bytes32 randomness to further distinguish position state.
* @return liquidity The liquidity of the position.
* @return feeGrowthInside0LastX128 The fee growth inside the position for token0.
* @return feeGrowthInside1LastX128 The fee growth inside the position for token1.
*/
function getPositionInfo(
IPoolManager manager,
PoolId poolId,
address owner,
int24 tickLower,
int24 tickUpper,
bytes32 salt
) internal view returns (uint128 liquidity, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128) {
// positionKey = keccak256(abi.encodePacked(owner, tickLower, tickUpper, salt))
bytes32 positionKey = Position.calculatePositionKey(owner, tickLower, tickUpper, salt);
(liquidity, feeGrowthInside0LastX128, feeGrowthInside1LastX128) = getPositionInfo(manager, poolId, positionKey);
}
/**
* @notice Retrieves the position information of a pool at a specific position ID.
* @dev Corresponds to pools[poolId].positions[positionId]
* @param manager The pool manager contract.
* @param poolId The ID of the pool.
* @param positionId The ID of the position.
* @return liquidity The liquidity of the position.
* @return feeGrowthInside0LastX128 The fee growth inside the position for token0.
* @return feeGrowthInside1LastX128 The fee growth inside the position for token1.
*/
function getPositionInfo(IPoolManager manager, PoolId poolId, bytes32 positionId)
internal
view
returns (uint128 liquidity, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128)
{
bytes32 slot = _getPositionInfoSlot(poolId, positionId);
// read all 3 words of the Position.State struct
bytes32[] memory data = manager.extsload(slot, 3);
assembly ("memory-safe") {
liquidity := mload(add(data, 32))
feeGrowthInside0LastX128 := mload(add(data, 64))
feeGrowthInside1LastX128 := mload(add(data, 96))
}
}
/**
* @notice Retrieves the liquidity of a position.
* @dev Corresponds to pools[poolId].positions[positionId].liquidity. More gas efficient for just retrieiving liquidity as compared to getPositionInfo
* @param manager The pool manager contract.
* @param poolId The ID of the pool.
* @param positionId The ID of the position.
* @return liquidity The liquidity of the position.
*/
function getPositionLiquidity(IPoolManager manager, PoolId poolId, bytes32 positionId)
internal
view
returns (uint128 liquidity)
{
bytes32 slot = _getPositionInfoSlot(poolId, positionId);
liquidity = uint128(uint256(manager.extsload(slot)));
}
/**
* @notice Calculate the fee growth inside a tick range of a pool
* @dev pools[poolId].feeGrowthInside0LastX128 in Position.State is cached and can become stale. This function will calculate the up to date feeGrowthInside
* @param manager The pool manager contract.
* @param poolId The ID of the pool.
* @param tickLower The lower tick of the range.
* @param tickUpper The upper tick of the range.
* @return feeGrowthInside0X128 The fee growth inside the tick range for token0.
* @return feeGrowthInside1X128 The fee growth inside the tick range for token1.
*/
function getFeeGrowthInside(IPoolManager manager, PoolId poolId, int24 tickLower, int24 tickUpper)
internal
view
returns (uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128)
{
(uint256 feeGrowthGlobal0X128, uint256 feeGrowthGlobal1X128) = getFeeGrowthGlobals(manager, poolId);
(uint256 lowerFeeGrowthOutside0X128, uint256 lowerFeeGrowthOutside1X128) =
getTickFeeGrowthOutside(manager, poolId, tickLower);
(uint256 upperFeeGrowthOutside0X128, uint256 upperFeeGrowthOutside1X128) =
getTickFeeGrowthOutside(manager, poolId, tickUpper);
(, int24 tickCurrent,,) = getSlot0(manager, poolId);
unchecked {
if (tickCurrent < tickLower) {
feeGrowthInside0X128 = lowerFeeGrowthOutside0X128 - upperFeeGrowthOutside0X128;
feeGrowthInside1X128 = lowerFeeGrowthOutside1X128 - upperFeeGrowthOutside1X128;
} else if (tickCurrent >= tickUpper) {
feeGrowthInside0X128 = upperFeeGrowthOutside0X128 - lowerFeeGrowthOutside0X128;
feeGrowthInside1X128 = upperFeeGrowthOutside1X128 - lowerFeeGrowthOutside1X128;
} else {
feeGrowthInside0X128 = feeGrowthGlobal0X128 - lowerFeeGrowthOutside0X128 - upperFeeGrowthOutside0X128;
feeGrowthInside1X128 = feeGrowthGlobal1X128 - lowerFeeGrowthOutside1X128 - upperFeeGrowthOutside1X128;
}
}
}
function _getPoolStateSlot(PoolId poolId) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(PoolId.unwrap(poolId), POOLS_SLOT));
}
function _getTickInfoSlot(PoolId poolId, int24 tick) internal pure returns (bytes32) {
// slot key of Pool.State value: `pools[poolId]`
bytes32 stateSlot = _getPoolStateSlot(poolId);
// Pool.State: `mapping(int24 => TickInfo) ticks`
bytes32 ticksMappingSlot = bytes32(uint256(stateSlot) + TICKS_OFFSET);
// slot key of the tick key: `pools[poolId].ticks[tick]
return keccak256(abi.encodePacked(int256(tick), ticksMappingSlot));
}
function _getPositionInfoSlot(PoolId poolId, bytes32 positionId) internal pure returns (bytes32) {
// slot key of Pool.State value: `pools[poolId]`
bytes32 stateSlot = _getPoolStateSlot(poolId);
// Pool.State: `mapping(bytes32 => Position.State) positions;`
bytes32 positionMapping = bytes32(uint256(stateSlot) + POSITIONS_OFFSET);
// slot of the mapping key: `pools[poolId].positions[positionId]
return keccak256(abi.encodePacked(positionId, positionMapping));
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {PoolKey} from "../types/PoolKey.sol";
import {BalanceDelta} from "../types/BalanceDelta.sol";
import {IPoolManager} from "./IPoolManager.sol";
import {BeforeSwapDelta} from "../types/BeforeSwapDelta.sol";
/// @notice V4 decides whether to invoke specific hooks by inspecting the least significant bits
/// of the address that the hooks contract is deployed to.
/// For example, a hooks contract deployed to address: 0x0000000000000000000000000000000000002400
/// has the lowest bits '10 0100 0000 0000' which would cause the 'before initialize' and 'after add liquidity' hooks to be used.
/// See the Hooks library for the full spec.
/// @dev Should only be callable by the v4 PoolManager.
interface IHooks {
/// @notice The hook called before the state of a pool is initialized
/// @param sender The initial msg.sender for the initialize call
/// @param key The key for the pool being initialized
/// @param sqrtPriceX96 The sqrt(price) of the pool as a Q64.96
/// @return bytes4 The function selector for the hook
function beforeInitialize(address sender, PoolKey calldata key, uint160 sqrtPriceX96) external returns (bytes4);
/// @notice The hook called after the state of a pool is initialized
/// @param sender The initial msg.sender for the initialize call
/// @param key The key for the pool being initialized
/// @param sqrtPriceX96 The sqrt(price) of the pool as a Q64.96
/// @param tick The current tick after the state of a pool is initialized
/// @return bytes4 The function selector for the hook
function afterInitialize(address sender, PoolKey calldata key, uint160 sqrtPriceX96, int24 tick)
external
returns (bytes4);
/// @notice The hook called before liquidity is added
/// @param sender The initial msg.sender for the add liquidity call
/// @param key The key for the pool
/// @param params The parameters for adding liquidity
/// @param hookData Arbitrary data handed into the PoolManager by the liquidity provider to be passed on to the hook
/// @return bytes4 The function selector for the hook
function beforeAddLiquidity(
address sender,
PoolKey calldata key,
IPoolManager.ModifyLiquidityParams calldata params,
bytes calldata hookData
) external returns (bytes4);
/// @notice The hook called after liquidity is added
/// @param sender The initial msg.sender for the add liquidity call
/// @param key The key for the pool
/// @param params The parameters for adding liquidity
/// @param delta The caller's balance delta after adding liquidity; the sum of principal delta, fees accrued, and hook delta
/// @param feesAccrued The fees accrued since the last time fees were collected from this position
/// @param hookData Arbitrary data handed into the PoolManager by the liquidity provider to be passed on to the hook
/// @return bytes4 The function selector for the hook
/// @return BalanceDelta The hook's delta in token0 and token1. Positive: the hook is owed/took currency, negative: the hook owes/sent currency
function afterAddLiquidity(
address sender,
PoolKey calldata key,
IPoolManager.ModifyLiquidityParams calldata params,
BalanceDelta delta,
BalanceDelta feesAccrued,
bytes calldata hookData
) external returns (bytes4, BalanceDelta);
/// @notice The hook called before liquidity is removed
/// @param sender The initial msg.sender for the remove liquidity call
/// @param key The key for the pool
/// @param params The parameters for removing liquidity
/// @param hookData Arbitrary data handed into the PoolManager by the liquidity provider to be be passed on to the hook
/// @return bytes4 The function selector for the hook
function beforeRemoveLiquidity(
address sender,
PoolKey calldata key,
IPoolManager.ModifyLiquidityParams calldata params,
bytes calldata hookData
) external returns (bytes4);
/// @notice The hook called after liquidity is removed
/// @param sender The initial msg.sender for the remove liquidity call
/// @param key The key for the pool
/// @param params The parameters for removing liquidity
/// @param delta The caller's balance delta after removing liquidity; the sum of principal delta, fees accrued, and hook delta
/// @param feesAccrued The fees accrued since the last time fees were collected from this position
/// @param hookData Arbitrary data handed into the PoolManager by the liquidity provider to be be passed on to the hook
/// @return bytes4 The function selector for the hook
/// @return BalanceDelta The hook's delta in token0 and token1. Positive: the hook is owed/took currency, negative: the hook owes/sent currency
function afterRemoveLiquidity(
address sender,
PoolKey calldata key,
IPoolManager.ModifyLiquidityParams calldata params,
BalanceDelta delta,
BalanceDelta feesAccrued,
bytes calldata hookData
) external returns (bytes4, BalanceDelta);
/// @notice The hook called before a swap
/// @param sender The initial msg.sender for the swap call
/// @param key The key for the pool
/// @param params The parameters for the swap
/// @param hookData Arbitrary data handed into the PoolManager by the swapper to be be passed on to the hook
/// @return bytes4 The function selector for the hook
/// @return BeforeSwapDelta The hook's delta in specified and unspecified currencies. Positive: the hook is owed/took currency, negative: the hook owes/sent currency
/// @return uint24 Optionally override the lp fee, only used if three conditions are met: 1. the Pool has a dynamic fee, 2. the value's 2nd highest bit is set (23rd bit, 0x400000), and 3. the value is less than or equal to the maximum fee (1 million)
function beforeSwap(
address sender,
PoolKey calldata key,
IPoolManager.SwapParams calldata params,
bytes calldata hookData
) external returns (bytes4, BeforeSwapDelta, uint24);
/// @notice The hook called after a swap
/// @param sender The initial msg.sender for the swap call
/// @param key The key for the pool
/// @param params The parameters for the swap
/// @param delta The amount owed to the caller (positive) or owed to the pool (negative)
/// @param hookData Arbitrary data handed into the PoolManager by the swapper to be be passed on to the hook
/// @return bytes4 The function selector for the hook
/// @return int128 The hook's delta in unspecified currency. Positive: the hook is owed/took currency, negative: the hook owes/sent currency
function afterSwap(
address sender,
PoolKey calldata key,
IPoolManager.SwapParams calldata params,
BalanceDelta delta,
bytes calldata hookData
) external returns (bytes4, int128);
/// @notice The hook called before donate
/// @param sender The initial msg.sender for the donate call
/// @param key The key for the pool
/// @param amount0 The amount of token0 being donated
/// @param amount1 The amount of token1 being donated
/// @param hookData Arbitrary data handed into the PoolManager by the donor to be be passed on to the hook
/// @return bytes4 The function selector for the hook
function beforeDonate(
address sender,
PoolKey calldata key,
uint256 amount0,
uint256 amount1,
bytes calldata hookData
) external returns (bytes4);
/// @notice The hook called after donate
/// @param sender The initial msg.sender for the donate call
/// @param key The key for the pool
/// @param amount0 The amount of token0 being donated
/// @param amount1 The amount of token1 being donated
/// @param hookData Arbitrary data handed into the PoolManager by the donor to be be passed on to the hook
/// @return bytes4 The function selector for the hook
function afterDonate(
address sender,
PoolKey calldata key,
uint256 amount0,
uint256 amount1,
bytes calldata hookData
) external returns (bytes4);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {Currency} from "./Currency.sol";
import {IHooks} from "../interfaces/IHooks.sol";
import {PoolIdLibrary} from "./PoolId.sol";
using PoolIdLibrary for PoolKey global;
/// @notice Returns the key for identifying a pool
struct PoolKey {
/// @notice The lower currency of the pool, sorted numerically
Currency currency0;
/// @notice The higher currency of the pool, sorted numerically
Currency currency1;
/// @notice The pool LP fee, capped at 1_000_000. If the highest bit is 1, the pool has a dynamic fee and must be exactly equal to 0x800000
uint24 fee;
/// @notice Ticks that involve positions must be a multiple of tick spacing
int24 tickSpacing;
/// @notice The hooks of the pool
IHooks hooks;
}// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.5.0;
/// @title Pool state that never changes
/// @notice These parameters are fixed for a pool forever, i.e., the methods will always return the same values
interface IUniswapV3PoolImmutables {
/// @notice The contract that deployed the pool, which must adhere to the IUniswapV3Factory interface
/// @return The contract address
function factory() external view returns (address);
/// @notice The first of the two tokens of the pool, sorted by address
/// @return The token contract address
function token0() external view returns (address);
/// @notice The second of the two tokens of the pool, sorted by address
/// @return The token contract address
function token1() external view returns (address);
/// @notice The pool's fee in hundredths of a bip, i.e. 1e-6
/// @return The fee
function fee() external view returns (uint24);
/// @notice The pool tick spacing
/// @dev Ticks can only be used at multiples of this value, minimum of 1 and always positive
/// e.g.: a tickSpacing of 3 means ticks can be initialized every 3rd tick, i.e., ..., -6, -3, 0, 3, 6, ...
/// This value is an int24 to avoid casting even though it is always positive.
/// @return The tick spacing
function tickSpacing() external view returns (int24);
/// @notice The maximum amount of position liquidity that can use any tick in the range
/// @dev This parameter is enforced per tick to prevent liquidity from overflowing a uint128 at any point, and
/// also prevents out-of-range liquidity from being used to prevent adding in-range liquidity to a pool
/// @return The max amount of liquidity per tick
function maxLiquidityPerTick() external view returns (uint128);
}// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.5.0;
/// @title Pool state that can change
/// @notice These methods compose the pool's state, and can change with any frequency including multiple times
/// per transaction
interface IUniswapV3PoolState {
/// @notice The 0th storage slot in the pool stores many values, and is exposed as a single method to save gas
/// when accessed externally.
/// @return sqrtPriceX96 The current price of the pool as a sqrt(token1/token0) Q64.96 value
/// tick The current tick of the pool, i.e. according to the last tick transition that was run.
/// This value may not always be equal to SqrtTickMath.getTickAtSqrtRatio(sqrtPriceX96) if the price is on a tick
/// boundary.
/// observationIndex The index of the last oracle observation that was written,
/// observationCardinality The current maximum number of observations stored in the pool,
/// observationCardinalityNext The next maximum number of observations, to be updated when the observation.
/// feeProtocol The protocol fee for both tokens of the pool.
/// Encoded as two 4 bit values, where the protocol fee of token1 is shifted 4 bits and the protocol fee of token0
/// is the lower 4 bits. Used as the denominator of a fraction of the swap fee, e.g. 4 means 1/4th of the swap fee.
/// unlocked Whether the pool is currently locked to reentrancy
function slot0()
external
view
returns (
uint160 sqrtPriceX96,
int24 tick,
uint16 observationIndex,
uint16 observationCardinality,
uint16 observationCardinalityNext,
uint8 feeProtocol,
bool unlocked
);
/// @notice The fee growth as a Q128.128 fees of token0 collected per unit of liquidity for the entire life of the pool
/// @dev This value can overflow the uint
function feeGrowthGlobal0X128() external view returns (uint);
/// @notice The fee growth as a Q128.128 fees of token1 collected per unit of liquidity for the entire life of the pool
/// @dev This value can overflow the uint
function feeGrowthGlobal1X128() external view returns (uint);
/// @notice The amounts of token0 and token1 that are owed to the protocol
/// @dev Protocol fees will never exceed uint128 max in either token
function protocolFees() external view returns (uint128 token0, uint128 token1);
/// @notice The currently in range liquidity available to the pool
/// @dev This value has no relationship to the total liquidity across all ticks
function liquidity() external view returns (uint128);
/// @notice Look up information about a specific tick in the pool
/// @param tick The tick to look up
/// @return liquidityGross the total amount of position liquidity that uses the pool either as tick lower or
/// tick upper,
/// liquidityNet how much liquidity changes when the pool price crosses the tick,
/// feeGrowthOutside0X128 the fee growth on the other side of the tick from the current tick in token0,
/// feeGrowthOutside1X128 the fee growth on the other side of the tick from the current tick in token1,
/// tickCumulativeOutside the cumulative tick value on the other side of the tick from the current tick
/// secondsPerLiquidityOutsideX128 the seconds spent per liquidity on the other side of the tick from the current tick,
/// secondsOutside the seconds spent on the other side of the tick from the current tick,
/// initialized Set to true if the tick is initialized, i.e. liquidityGross is greater than 0, otherwise equal to false.
/// Outside values can only be used if the tick is initialized, i.e. if liquidityGross is greater than 0.
/// In addition, these values are only relative and must be used only in comparison to previous snapshots for
/// a specific position.
function ticks(int24 tick)
external
view
returns (
uint128 liquidityGross,
int128 liquidityNet,
uint feeGrowthOutside0X128,
uint feeGrowthOutside1X128,
int56 tickCumulativeOutside,
uint160 secondsPerLiquidityOutsideX128,
uint32 secondsOutside,
bool initialized
);
/// @notice Returns 256 packed tick initialized boolean values. See TickBitmap for more information
function tickBitmap(int16 wordPosition) external view returns (uint);
/// @notice Returns the information about a position by the position's key
/// @param key The position's key is a hash of a preimage composed by the owner, tickLower and tickUpper
/// @return _liquidity The amount of liquidity in the position,
/// Returns feeGrowthInside0LastX128 fee growth of token0 inside the tick range as of the last mint/burn/poke,
/// Returns feeGrowthInside1LastX128 fee growth of token1 inside the tick range as of the last mint/burn/poke,
/// Returns tokensOwed0 the computed amount of token0 owed to the position as of the last mint/burn/poke,
/// Returns tokensOwed1 the computed amount of token1 owed to the position as of the last mint/burn/poke
function positions(bytes32 key)
external
view
returns (
uint128 _liquidity,
uint feeGrowthInside0LastX128,
uint feeGrowthInside1LastX128,
uint128 tokensOwed0,
uint128 tokensOwed1
);
/// @notice Returns data about a specific observation index
/// @param index The element of the observations array to fetch
/// @dev You most likely want to use #observe() instead of this method to get an observation as of some amount of time
/// ago, rather than at a specific index in the array.
/// @return blockTimestamp The timestamp of the observation,
/// Returns tickCumulative the tick multiplied by seconds elapsed for the life of the pool as of the observation timestamp,
/// Returns secondsPerLiquidityCumulativeX128 the seconds per in range liquidity for the life of the pool as of the observation timestamp,
/// Returns initialized whether the observation has been initialized and the values are safe to use
function observations(uint index)
external
view
returns (
uint32 blockTimestamp,
int56 tickCumulative,
uint160 secondsPerLiquidityCumulativeX128,
bool initialized
);
}// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.5.0;
/// @title Pool state that is not stored
/// @notice Contains view functions to provide information about the pool that is computed rather than stored on the
/// blockchain. The functions here may have variable gas costs.
interface IUniswapV3PoolDerivedState {
/// @notice Returns the cumulative tick and liquidity as of each timestamp `secondsAgo` from the current block timestamp
/// @dev To get a time weighted average tick or liquidity-in-range, you must call this with two values, one representing
/// the beginning of the period and another for the end of the period. E.g., to get the last hour time-weighted average tick,
/// you must call it with secondsAgos = [3600, 0].
/// @dev The time weighted average tick represents the geometric time weighted average price of the pool, in
/// log base sqrt(1.0001) of token1 / token0. The TickMath library can be used to go from a tick value to a ratio.
/// @param secondsAgos From how long ago each cumulative tick and liquidity value should be returned
/// @return tickCumulatives Cumulative tick values as of each `secondsAgos` from the current block timestamp
/// @return secondsPerLiquidityCumulativeX128s Cumulative seconds per liquidity-in-range value as of each `secondsAgos` from the current block
/// timestamp
function observe(uint32[] calldata secondsAgos)
external
view
returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s);
/// @notice Returns a snapshot of the tick cumulative, seconds per liquidity and seconds inside a tick range
/// @dev Snapshots must only be compared to other snapshots, taken over a period for which a position existed.
/// I.e., snapshots cannot be compared if a position is not held for the entire period between when the first
/// snapshot is taken and the second snapshot is taken.
/// @param tickLower The lower tick of the range
/// @param tickUpper The upper tick of the range
/// @return tickCumulativeInside The snapshot of the tick accumulator for the range
/// @return secondsPerLiquidityInsideX128 The snapshot of seconds per liquidity for the range
/// @return secondsInside The snapshot of seconds per liquidity for the range
function snapshotCumulativesInside(int24 tickLower, int24 tickUpper)
external
view
returns (int56 tickCumulativeInside, uint160 secondsPerLiquidityInsideX128, uint32 secondsInside);
}// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.5.0;
/// @title Permissionless pool actions
/// @notice Contains pool methods that can be called by anyone
interface IUniswapV3PoolActions {
/// @notice Sets the initial price for the pool
/// @dev Price is represented as a sqrt(amountToken1/amountToken0) Q64.96 value
/// @param sqrtPriceX96 the initial sqrt price of the pool as a Q64.96
function initialize(uint160 sqrtPriceX96) external;
/// @notice Adds liquidity for the given recipient/tickLower/tickUpper position
/// @dev The caller of this method receives a callback in the form of IUniswapV3MintCallback#uniswapV3MintCallback
/// in which they must pay any token0 or token1 owed for the liquidity. The amount of token0/token1 due depends
/// on tickLower, tickUpper, the amount of liquidity, and the current price.
/// @param recipient The address for which the liquidity will be created
/// @param tickLower The lower tick of the position in which to add liquidity
/// @param tickUpper The upper tick of the position in which to add liquidity
/// @param amount The amount of liquidity to mint
/// @param data Any data that should be passed through to the callback
/// @return amount0 The amount of token0 that was paid to mint the given amount of liquidity. Matches the value in the callback
/// @return amount1 The amount of token1 that was paid to mint the given amount of liquidity. Matches the value in the callback
function mint(address recipient, int24 tickLower, int24 tickUpper, uint128 amount, bytes calldata data)
external
returns (uint amount0, uint amount1);
/// @notice Collects tokens owed to a position
/// @dev Does not recompute fees earned, which must be done either via mint or burn of any amount of liquidity.
/// Collect must be called by the position owner. To withdraw only token0 or only token1, amount0Requested or
/// amount1Requested may be set to zero. To withdraw all tokens owed, caller may pass any value greater than the
/// actual tokens owed, e.g. type(uint128).max. Tokens owed may be from accumulated swap fees or burned liquidity.
/// @param recipient The address which should receive the fees collected
/// @param tickLower The lower tick of the position for which to collect fees
/// @param tickUpper The upper tick of the position for which to collect fees
/// @param amount0Requested How much token0 should be withdrawn from the fees owed
/// @param amount1Requested How much token1 should be withdrawn from the fees owed
/// @return amount0 The amount of fees collected in token0
/// @return amount1 The amount of fees collected in token1
function collect(
address recipient,
int24 tickLower,
int24 tickUpper,
uint128 amount0Requested,
uint128 amount1Requested
) external returns (uint128 amount0, uint128 amount1);
/// @notice Burn liquidity from the sender and account tokens owed for the liquidity to the position
/// @dev Can be used to trigger a recalculation of fees owed to a position by calling with an amount of 0
/// @dev Fees must be collected separately via a call to #collect
/// @param tickLower The lower tick of the position for which to burn liquidity
/// @param tickUpper The upper tick of the position for which to burn liquidity
/// @param amount How much liquidity to burn
/// @return amount0 The amount of token0 sent to the recipient
/// @return amount1 The amount of token1 sent to the recipient
function burn(int24 tickLower, int24 tickUpper, uint128 amount)
external
returns (uint amount0, uint amount1);
/// @notice Swap token0 for token1, or token1 for token0
/// @dev The caller of this method receives a callback in the form of IUniswapV3SwapCallback#uniswapV3SwapCallback
/// @param recipient The address to receive the output of the swap
/// @param zeroForOne The direction of the swap, true for token0 to token1, false for token1 to token0
/// @param amountSpecified The amount of the swap, which implicitly configures the swap as exact input (positive), or exact output (negative)
/// @param sqrtPriceLimitX96 The Q64.96 sqrt price limit. If zero for one, the price cannot be less than this
/// value after the swap. If one for zero, the price cannot be greater than this value after the swap
/// @param data Any data to be passed through to the callback
/// @return amount0 The delta of the balance of token0 of the pool, exact when negative, minimum when positive
/// @return amount1 The delta of the balance of token1 of the pool, exact when negative, minimum when positive
function swap(
address recipient,
bool zeroForOne,
int256 amountSpecified,
uint160 sqrtPriceLimitX96,
bytes calldata data
) external returns (int256 amount0, int256 amount1);
/// @notice Receive token0 and/or token1 and pay it back, plus a fee, in the callback
/// @dev The caller of this method receives a callback in the form of IUniswapV3FlashCallback#uniswapV3FlashCallback
/// @dev Can be used to donate underlying tokens pro-rata to currently in-range liquidity providers by calling
/// with 0 amount{0,1} and sending the donation amount(s) from the callback
/// @param recipient The address which will receive the token0 and token1 amounts
/// @param amount0 The amount of token0 to send
/// @param amount1 The amount of token1 to send
/// @param data Any data to be passed through to the callback
function flash(address recipient, uint amount0, uint amount1, bytes calldata data) external;
/// @notice Increase the maximum number of price and liquidity observations that this pool will store
/// @dev This method is no-op if the pool already has an observationCardinalityNext greater than or equal to
/// the input observationCardinalityNext.
/// @param observationCardinalityNext The desired minimum number of observations for the pool to store
function increaseObservationCardinalityNext(uint16 observationCardinalityNext) external;
}// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.5.0;
/// @title Permissioned pool actions
/// @notice Contains pool methods that may only be called by the factory owner
interface IUniswapV3PoolOwnerActions {
/// @notice Set the denominator of the protocol's % share of the fees
/// @param feeProtocol0 new protocol fee for token0 of the pool
/// @param feeProtocol1 new protocol fee for token1 of the pool
function setFeeProtocol(uint8 feeProtocol0, uint8 feeProtocol1) external;
/// @notice Collect the protocol fee accrued to the pool
/// @param recipient The address to which collected protocol fees should be sent
/// @param amount0Requested The maximum amount of token0 to send, can be 0 to collect fees in only token1
/// @param amount1Requested The maximum amount of token1 to send, can be 0 to collect fees in only token0
/// @return amount0 The protocol fee collected in token0
/// @return amount1 The protocol fee collected in token1
function collectProtocol(address recipient, uint128 amount0Requested, uint128 amount1Requested)
external
returns (uint128 amount0, uint128 amount1);
}// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.5.0;
/// @title Events emitted by a pool
/// @notice Contains all events emitted by the pool
interface IUniswapV3PoolEvents {
/// @notice Emitted exactly once by a pool when #initialize is first called on the pool
/// @dev Mint/Burn/Swap cannot be emitted by the pool before Initialize
/// @param sqrtPriceX96 The initial sqrt price of the pool, as a Q64.96
/// @param tick The initial tick of the pool, i.e. log base 1.0001 of the starting price of the pool
event Initialize(uint160 sqrtPriceX96, int24 tick);
/// @notice Emitted when liquidity is minted for a given position
/// @param sender The address that minted the liquidity
/// @param owner The owner of the position and recipient of any minted liquidity
/// @param tickLower The lower tick of the position
/// @param tickUpper The upper tick of the position
/// @param amount The amount of liquidity minted to the position range
/// @param amount0 How much token0 was required for the minted liquidity
/// @param amount1 How much token1 was required for the minted liquidity
event Mint(
address sender,
address indexed owner,
int24 indexed tickLower,
int24 indexed tickUpper,
uint128 amount,
uint amount0,
uint amount1
);
/// @notice Emitted when fees are collected by the owner of a position
/// @dev Collect events may be emitted with zero amount0 and amount1 when the caller chooses not to collect fees
/// @param owner The owner of the position for which fees are collected
/// @param tickLower The lower tick of the position
/// @param tickUpper The upper tick of the position
/// @param amount0 The amount of token0 fees collected
/// @param amount1 The amount of token1 fees collected
event Collect(
address indexed owner,
address recipient,
int24 indexed tickLower,
int24 indexed tickUpper,
uint128 amount0,
uint128 amount1
);
/// @notice Emitted when a position's liquidity is removed
/// @dev Does not withdraw any fees earned by the liquidity position, which must be withdrawn via #collect
/// @param owner The owner of the position for which liquidity is removed
/// @param tickLower The lower tick of the position
/// @param tickUpper The upper tick of the position
/// @param amount The amount of liquidity to remove
/// @param amount0 The amount of token0 withdrawn
/// @param amount1 The amount of token1 withdrawn
event Burn(
address indexed owner,
int24 indexed tickLower,
int24 indexed tickUpper,
uint128 amount,
uint amount0,
uint amount1
);
/// @notice Emitted by the pool for any swaps between token0 and token1
/// @param sender The address that initiated the swap call, and that received the callback
/// @param recipient The address that received the output of the swap
/// @param amount0 The delta of the token0 balance of the pool
/// @param amount1 The delta of the token1 balance of the pool
/// @param sqrtPriceX96 The sqrt(price) of the pool after the swap, as a Q64.96
/// @param liquidity The liquidity of the pool after the swap
/// @param tick The log base 1.0001 of price of the pool after the swap
event Swap(
address indexed sender,
address indexed recipient,
int256 amount0,
int256 amount1,
uint160 sqrtPriceX96,
uint128 liquidity,
int24 tick
);
/// @notice Emitted by the pool for any flashes of token0/token1
/// @param sender The address that initiated the swap call, and that received the callback
/// @param recipient The address that received the tokens from flash
/// @param amount0 The amount of token0 that was flashed
/// @param amount1 The amount of token1 that was flashed
/// @param paid0 The amount of token0 paid for the flash, which can exceed the amount0 plus the fee
/// @param paid1 The amount of token1 paid for the flash, which can exceed the amount1 plus the fee
event Flash(
address indexed sender,
address indexed recipient,
uint amount0,
uint amount1,
uint paid0,
uint paid1
);
/// @notice Emitted by the pool for increases to the number of observations that can be stored
/// @dev observationCardinalityNext is not the observation cardinality until an observation is written at the index
/// just before a mint/swap/burn.
/// @param observationCardinalityNextOld The previous value of the next observation cardinality
/// @param observationCardinalityNextNew The updated value of the next observation cardinality
event IncreaseObservationCardinalityNext(
uint16 observationCardinalityNextOld, uint16 observationCardinalityNextNew
);
/// @notice Emitted when the protocol fee is changed by the pool
/// @param feeProtocol0Old The previous value of the token0 protocol fee
/// @param feeProtocol1Old The previous value of the token1 protocol fee
/// @param feeProtocol0New The updated value of the token0 protocol fee
/// @param feeProtocol1New The updated value of the token1 protocol fee
event SetFeeProtocol(uint8 feeProtocol0Old, uint8 feeProtocol1Old, uint8 feeProtocol0New, uint8 feeProtocol1New);
/// @notice Emitted when the collected protocol fees are withdrawn by the factory owner
/// @param sender The address that collects the protocol fees
/// @param recipient The address that receives the collected protocol fees
/// @param amount0 The amount of token0 protocol fees that is withdrawn
/// @param amount0 The amount of token1 protocol fees that is withdrawn
event CollectProtocol(address indexed sender, address indexed recipient, uint128 amount0, uint128 amount1);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;
// Original Author: https://github.com/hamdiallam/Solidity-RLP/blob/master/contracts/RLPReader.sol
library RLPDecoder {
uint8 constant STRING_SHORT_START = 0x80;
uint8 constant STRING_LONG_START = 0xb8;
uint8 constant LIST_SHORT_START = 0xc0;
uint8 constant LIST_LONG_START = 0xf8;
uint8 constant WORD_SIZE = 32;
struct RLPItem {
uint len;
uint memPtr;
}
struct Iterator {
RLPItem item; // Item that's being iterated over.
uint nextPtr; // Position of the next item in the list.
}
/*
* @dev Returns the next element in the iteration. Reverts if it has not next element.
* @param self The iterator.
* @return The next element in the iteration.
*/
function next(Iterator memory self) internal pure returns (RLPItem memory) {
require(hasNext(self));
uint ptr = self.nextPtr;
uint itemLength = _itemLength(ptr);
self.nextPtr = ptr + itemLength;
return RLPItem(itemLength, ptr);
}
/*
* @dev Returns true if the iteration has more elements.
* @param self The iterator.
* @return true if the iteration has more elements.
*/
function hasNext(Iterator memory self) internal pure returns (bool) {
RLPItem memory item = self.item;
return self.nextPtr < item.memPtr + item.len;
}
/*
* @param item RLP encoded bytes
*/
function toRlpItem(bytes memory item) internal pure returns (RLPItem memory) {
uint memPtr;
assembly {
memPtr := add(item, 0x20)
}
return RLPItem(item.length, memPtr);
}
/*
* @dev Create an iterator. Reverts if item is not a list.
* @param self The RLP item.
* @return An 'Iterator' over the item.
*/
function iterator(RLPItem memory self) internal pure returns (Iterator memory) {
require(isList(self));
uint ptr = self.memPtr + _payloadOffset(self.memPtr);
return Iterator(self, ptr);
}
/*
* @param the RLP item.
*/
function rlpLen(RLPItem memory item) internal pure returns (uint) {
return item.len;
}
/*
* @param the RLP item.
* @return (memPtr, len) pair: location of the item's payload in memory.
*/
function payloadLocation(RLPItem memory item) internal pure returns (uint, uint) {
uint offset = _payloadOffset(item.memPtr);
uint memPtr = item.memPtr + offset;
uint len = item.len - offset; // data length
return (memPtr, len);
}
/*
* @param the RLP item.
*/
function payloadLen(RLPItem memory item) internal pure returns (uint) {
(, uint len) = payloadLocation(item);
return len;
}
/*
* @param the RLP item containing the encoded list.
*/
function toList(RLPItem memory item) internal pure returns (RLPItem[] memory) {
require(isList(item));
uint items = numItems(item);
RLPItem[] memory result = new RLPItem[](items);
uint memPtr = item.memPtr + _payloadOffset(item.memPtr);
uint dataLen;
for (uint i = 0; i < items; i++) {
dataLen = _itemLength(memPtr);
result[i] = RLPItem(dataLen, memPtr);
memPtr = memPtr + dataLen;
}
return result;
}
// @return indicator whether encoded payload is a list. negate this function call for isData.
function isList(RLPItem memory item) internal pure returns (bool) {
if (item.len == 0) return false;
uint8 byte0;
uint memPtr = item.memPtr;
assembly {
byte0 := byte(0, mload(memPtr))
}
if (byte0 < LIST_SHORT_START)
return false;
return true;
}
/*
* @dev A cheaper version of keccak256(toRlpBytes(item)) that avoids copying memory.
* @return keccak256 hash of RLP encoded bytes.
*/
function rlpBytesKeccak256(RLPItem memory item) internal pure returns (bytes32) {
uint ptr = item.memPtr;
uint len = item.len;
bytes32 result;
assembly {
result := keccak256(ptr, len)
}
return result;
}
/*
* @dev A cheaper version of keccak256(toBytes(item)) that avoids copying memory.
* @return keccak256 hash of the item payload.
*/
function payloadKeccak256(RLPItem memory item) internal pure returns (bytes32) {
(uint memPtr, uint len) = payloadLocation(item);
bytes32 result;
assembly {
result := keccak256(memPtr, len)
}
return result;
}
/** RLPItem conversions into data types **/
// @returns raw rlp encoding in bytes
function toRlpBytes(RLPItem memory item) internal pure returns (bytes memory) {
bytes memory result = new bytes(item.len);
if (result.length == 0) return result;
uint ptr;
assembly {
ptr := add(0x20, result)
}
copy(item.memPtr, ptr, item.len);
return result;
}
// any non-zero byte except "0x80" is considered true
function toBoolean(RLPItem memory item) internal pure returns (bool) {
require(item.len == 1);
uint result;
uint memPtr = item.memPtr;
assembly {
result := byte(0, mload(memPtr))
}
// SEE Github Issue #5.
// Summary: Most commonly used RLP libraries (i.e Geth) will encode
// "0" as "0x80" instead of as "0". We handle this edge case explicitly
// here.
if (result == 0 || result == STRING_SHORT_START) {
return false;
} else {
return true;
}
}
function toAddress(RLPItem memory item) internal pure returns (address) {
// 1 byte for the length prefix
require(item.len == 21);
return address(uint160(toUint(item)));
}
function toUint(RLPItem memory item) internal pure returns (uint) {
require(item.len > 0 && item.len <= 33);
(uint memPtr, uint len) = payloadLocation(item);
uint result;
assembly {
result := mload(memPtr)
// shift to the correct location if necessary
if lt(len, 32) {
result := div(result, exp(256, sub(32, len)))
}
}
return result;
}
// enforces 32 byte length
function toUintStrict(RLPItem memory item) internal pure returns (uint) {
// one byte prefix
require(item.len == 33);
uint result;
uint memPtr = item.memPtr + 1;
assembly {
result := mload(memPtr)
}
return result;
}
function toBytes(RLPItem memory item) internal pure returns (bytes memory) {
require(item.len > 0);
(uint memPtr, uint len) = payloadLocation(item);
bytes memory result = new bytes(len);
uint destPtr;
assembly {
destPtr := add(0x20, result)
}
copy(memPtr, destPtr, len);
return result;
}
/*
* Private Helpers
*/
// @return number of payload items inside an encoded list.
function numItems(RLPItem memory item) private pure returns (uint) {
if (item.len == 0) return 0;
uint count = 0;
uint currPtr = item.memPtr + _payloadOffset(item.memPtr);
uint endPtr = item.memPtr + item.len;
while (currPtr < endPtr) {
currPtr = currPtr + _itemLength(currPtr); // skip over an item
count++;
}
return count;
}
// @return entire rlp item byte length
function _itemLength(uint memPtr) private pure returns (uint) {
uint itemLen;
uint byte0;
assembly {
byte0 := byte(0, mload(memPtr))
}
if (byte0 < STRING_SHORT_START)
itemLen = 1;
else if (byte0 < STRING_LONG_START)
itemLen = byte0 - STRING_SHORT_START + 1;
else if (byte0 < LIST_SHORT_START) {
assembly {
let byteLen := sub(byte0, 0xb7) // # of bytes the actual length is
memPtr := add(memPtr, 1) // skip over the first byte
/* 32 byte word size */
let dataLen := div(mload(memPtr), exp(256, sub(32, byteLen))) // right shifting to get the len
itemLen := add(dataLen, add(byteLen, 1))
}
}
else if (byte0 < LIST_LONG_START) {
itemLen = byte0 - LIST_SHORT_START + 1;
}
else {
assembly {
let byteLen := sub(byte0, 0xf7)
memPtr := add(memPtr, 1)
let dataLen := div(mload(memPtr), exp(256, sub(32, byteLen))) // right shifting to the correct length
itemLen := add(dataLen, add(byteLen, 1))
}
}
return itemLen;
}
// @return number of bytes until the data
function _payloadOffset(uint memPtr) private pure returns (uint) {
uint byte0;
assembly {
byte0 := byte(0, mload(memPtr))
}
if (byte0 < STRING_SHORT_START)
return 0;
else if (byte0 < STRING_LONG_START || (byte0 >= LIST_SHORT_START && byte0 < LIST_LONG_START))
return 1;
else if (byte0 < LIST_SHORT_START) // being explicit
return byte0 - (STRING_LONG_START - 1) + 1;
else
return byte0 - (LIST_LONG_START - 1) + 1;
}
/*
* @param src Pointer to source
* @param dest Pointer to destination
* @param len Amount of memory to copy from the source
*/
function copy(uint src, uint dest, uint len) private pure {
if (len == 0) return;
// copy as many word sizes as possible
for (; len >= WORD_SIZE; len -= WORD_SIZE) {
assembly {
mstore(dest, mload(src))
}
src += WORD_SIZE;
dest += WORD_SIZE;
}
if (len > 0) {
// left over bytes. Mask is used to remove unwanted bytes from the word
uint mask = 256 ** (WORD_SIZE - len) - 1;
assembly {
let srcpart := and(mload(src), not(mask)) // zero out src
let destpart := and(mload(dest), mask) // retrieve the bytes
mstore(dest, or(destpart, srcpart))
}
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Metadata.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
/**
* @dev Interface for the optional metadata functions from the ERC20 standard.
*/
interface IERC20Metadata is IERC20 {
/**
* @dev Returns the name of the token.
*/
function name() external view returns (string memory);
/**
* @dev Returns the symbol of the token.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the decimals places of the token.
*/
function decimals() external view returns (uint8);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/draft-IERC6093.sol)
pragma solidity ^0.8.20;
/**
* @dev Standard ERC20 Errors
* Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC20 tokens.
*/
interface IERC20Errors {
/**
* @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
* @param balance Current balance for the interacting account.
* @param needed Minimum amount required to perform a transfer.
*/
error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);
/**
* @dev Indicates a failure with the token `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
*/
error ERC20InvalidSender(address sender);
/**
* @dev Indicates a failure with the token `receiver`. Used in transfers.
* @param receiver Address to which tokens are being transferred.
*/
error ERC20InvalidReceiver(address receiver);
/**
* @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers.
* @param spender Address that may be allowed to operate on tokens without being their owner.
* @param allowance Amount of tokens a `spender` is allowed to operate with.
* @param needed Minimum amount required to perform a transfer.
*/
error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);
/**
* @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
* @param approver Address initiating an approval operation.
*/
error ERC20InvalidApprover(address approver);
/**
* @dev Indicates a failure with the `spender` to be approved. Used in approvals.
* @param spender Address that may be allowed to operate on tokens without being their owner.
*/
error ERC20InvalidSpender(address spender);
}
/**
* @dev Standard ERC721 Errors
* Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC721 tokens.
*/
interface IERC721Errors {
/**
* @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in EIP-20.
* Used in balance queries.
* @param owner Address of the current owner of a token.
*/
error ERC721InvalidOwner(address owner);
/**
* @dev Indicates a `tokenId` whose `owner` is the zero address.
* @param tokenId Identifier number of a token.
*/
error ERC721NonexistentToken(uint256 tokenId);
/**
* @dev Indicates an error related to the ownership over a particular token. Used in transfers.
* @param sender Address whose tokens are being transferred.
* @param tokenId Identifier number of a token.
* @param owner Address of the current owner of a token.
*/
error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner);
/**
* @dev Indicates a failure with the token `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
*/
error ERC721InvalidSender(address sender);
/**
* @dev Indicates a failure with the token `receiver`. Used in transfers.
* @param receiver Address to which tokens are being transferred.
*/
error ERC721InvalidReceiver(address receiver);
/**
* @dev Indicates a failure with the `operator`’s approval. Used in transfers.
* @param operator Address that may be allowed to operate on tokens without being their owner.
* @param tokenId Identifier number of a token.
*/
error ERC721InsufficientApproval(address operator, uint256 tokenId);
/**
* @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
* @param approver Address initiating an approval operation.
*/
error ERC721InvalidApprover(address approver);
/**
* @dev Indicates a failure with the `operator` to be approved. Used in approvals.
* @param operator Address that may be allowed to operate on tokens without being their owner.
*/
error ERC721InvalidOperator(address operator);
}
/**
* @dev Standard ERC1155 Errors
* Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC1155 tokens.
*/
interface IERC1155Errors {
/**
* @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
* @param balance Current balance for the interacting account.
* @param needed Minimum amount required to perform a transfer.
* @param tokenId Identifier number of a token.
*/
error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId);
/**
* @dev Indicates a failure with the token `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
*/
error ERC1155InvalidSender(address sender);
/**
* @dev Indicates a failure with the token `receiver`. Used in transfers.
* @param receiver Address to which tokens are being transferred.
*/
error ERC1155InvalidReceiver(address receiver);
/**
* @dev Indicates a failure with the `operator`’s approval. Used in transfers.
* @param operator Address that may be allowed to operate on tokens without being their owner.
* @param owner Address of the current owner of a token.
*/
error ERC1155MissingApprovalForAll(address operator, address owner);
/**
* @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
* @param approver Address initiating an approval operation.
*/
error ERC1155InvalidApprover(address approver);
/**
* @dev Indicates a failure with the `operator` to be approved. Used in approvals.
* @param operator Address that may be allowed to operate on tokens without being their owner.
*/
error ERC1155InvalidOperator(address operator);
/**
* @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation.
* Used in batch transfers.
* @param idsLength Length of the array of token identifiers
* @param valuesLength Length of the array of token amounts
*/
error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { IOAppOptionsType3, EnforcedOptionParam } from "../interfaces/IOAppOptionsType3.sol";
/**
* @title OAppOptionsType3
* @dev Abstract contract implementing the IOAppOptionsType3 interface with type 3 options.
*/
abstract contract OAppOptionsType3 is IOAppOptionsType3, Ownable {
uint16 internal constant OPTION_TYPE_3 = 3;
// @dev The "msgType" should be defined in the child contract.
mapping(uint32 eid => mapping(uint16 msgType => bytes enforcedOption)) public enforcedOptions;
/**
* @dev Sets the enforced options for specific endpoint and message type combinations.
* @param _enforcedOptions An array of EnforcedOptionParam structures specifying enforced options.
*
* @dev Only the owner/admin of the OApp can call this function.
* @dev Provides a way for the OApp to enforce things like paying for PreCrime, AND/OR minimum dst lzReceive gas amounts etc.
* @dev These enforced options can vary as the potential options/execution on the remote may differ as per the msgType.
* eg. Amount of lzReceive() gas necessary to deliver a lzCompose() message adds overhead you dont want to pay
* if you are only making a standard LayerZero message ie. lzReceive() WITHOUT sendCompose().
*/
function setEnforcedOptions(EnforcedOptionParam[] calldata _enforcedOptions) public virtual onlyOwner {
for (uint256 i = 0; i < _enforcedOptions.length; i++) {
// @dev Enforced options are only available for optionType 3, as type 1 and 2 dont support combining.
_assertOptionsType3(_enforcedOptions[i].options);
enforcedOptions[_enforcedOptions[i].eid][_enforcedOptions[i].msgType] = _enforcedOptions[i].options;
}
emit EnforcedOptionSet(_enforcedOptions);
}
/**
* @notice Combines options for a given endpoint and message type.
* @param _eid The endpoint ID.
* @param _msgType The OAPP message type.
* @param _extraOptions Additional options passed by the caller.
* @return options The combination of caller specified options AND enforced options.
*
* @dev If there is an enforced lzReceive option:
* - {gasLimit: 200k, msg.value: 1 ether} AND a caller supplies a lzReceive option: {gasLimit: 100k, msg.value: 0.5 ether}
* - The resulting options will be {gasLimit: 300k, msg.value: 1.5 ether} when the message is executed on the remote lzReceive() function.
* @dev This presence of duplicated options is handled off-chain in the verifier/executor.
*/
function combineOptions(
uint32 _eid,
uint16 _msgType,
bytes calldata _extraOptions
) public view virtual returns (bytes memory) {
bytes memory enforced = enforcedOptions[_eid][_msgType];
// No enforced options, pass whatever the caller supplied, even if it's empty or legacy type 1/2 options.
if (enforced.length == 0) return _extraOptions;
// No caller options, return enforced
if (_extraOptions.length == 0) return enforced;
// @dev If caller provided _extraOptions, must be type 3 as its the ONLY type that can be combined.
if (_extraOptions.length >= 2) {
_assertOptionsType3(_extraOptions);
// @dev Remove the first 2 bytes containing the type from the _extraOptions and combine with enforced.
return bytes.concat(enforced, _extraOptions[2:]);
}
// No valid set of options was found.
revert InvalidOptions(_extraOptions);
}
/**
* @dev Internal function to assert that options are of type 3.
* @param _options The options to be checked.
*/
function _assertOptionsType3(bytes calldata _options) internal pure virtual {
uint16 optionsType = uint16(bytes2(_options[0:2]));
if (optionsType != OPTION_TYPE_3) revert InvalidOptions(_options);
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/**
* @title IOAppMsgInspector
* @dev Interface for the OApp Message Inspector, allowing examination of message and options contents.
*/
interface IOAppMsgInspector {
// Custom error message for inspection failure
error InspectionFailed(bytes message, bytes options);
/**
* @notice Allows the inspector to examine LayerZero message contents and optionally throw a revert if invalid.
* @param _message The message payload to be inspected.
* @param _options Additional options or parameters for inspection.
* @return valid A boolean indicating whether the inspection passed (true) or failed (false).
*
* @dev Optionally done as a revert, OR use the boolean provided to handle the failure.
*/
function inspect(bytes calldata _message, bytes calldata _options) external view returns (bool valid);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { IPreCrime } from "./interfaces/IPreCrime.sol";
import { IOAppPreCrimeSimulator, InboundPacket, Origin } from "./interfaces/IOAppPreCrimeSimulator.sol";
/**
* @title OAppPreCrimeSimulator
* @dev Abstract contract serving as the base for preCrime simulation functionality in an OApp.
*/
abstract contract OAppPreCrimeSimulator is IOAppPreCrimeSimulator, Ownable {
// The address of the preCrime implementation.
address public preCrime;
/**
* @dev Retrieves the address of the OApp contract.
* @return The address of the OApp contract.
*
* @dev The simulator contract is the base contract for the OApp by default.
* @dev If the simulator is a separate contract, override this function.
*/
function oApp() external view virtual returns (address) {
return address(this);
}
/**
* @dev Sets the preCrime contract address.
* @param _preCrime The address of the preCrime contract.
*/
function setPreCrime(address _preCrime) public virtual onlyOwner {
preCrime = _preCrime;
emit PreCrimeSet(_preCrime);
}
/**
* @dev Interface for pre-crime simulations. Always reverts at the end with the simulation results.
* @param _packets An array of InboundPacket objects representing received packets to be delivered.
*
* @dev WARNING: MUST revert at the end with the simulation results.
* @dev Gives the preCrime implementation the ability to mock sending packets to the lzReceive function,
* WITHOUT actually executing them.
*/
function lzReceiveAndRevert(InboundPacket[] calldata _packets) public payable virtual {
for (uint256 i = 0; i < _packets.length; i++) {
InboundPacket calldata packet = _packets[i];
// Ignore packets that are not from trusted peers.
if (!isPeer(packet.origin.srcEid, packet.origin.sender)) continue;
// @dev Because a verifier is calling this function, it doesnt have access to executor params:
// - address _executor
// - bytes calldata _extraData
// preCrime will NOT work for OApps that rely on these two parameters inside of their _lzReceive().
// They are instead stubbed to default values, address(0) and bytes("")
// @dev Calling this.lzReceiveSimulate removes ability for assembly return 0 callstack exit,
// which would cause the revert to be ignored.
this.lzReceiveSimulate{ value: packet.value }(
packet.origin,
packet.guid,
packet.message,
packet.executor,
packet.extraData
);
}
// @dev Revert with the simulation results. msg.sender must implement IPreCrime.buildSimulationResult().
revert SimulationResult(IPreCrime(msg.sender).buildSimulationResult());
}
/**
* @dev Is effectively an internal function because msg.sender must be address(this).
* Allows resetting the call stack for 'internal' calls.
* @param _origin The origin information containing the source endpoint and sender address.
* - srcEid: The source chain endpoint ID.
* - sender: The sender address on the src chain.
* - nonce: The nonce of the message.
* @param _guid The unique identifier of the packet.
* @param _message The message payload of the packet.
* @param _executor The executor address for the packet.
* @param _extraData Additional data for the packet.
*/
function lzReceiveSimulate(
Origin calldata _origin,
bytes32 _guid,
bytes calldata _message,
address _executor,
bytes calldata _extraData
) external payable virtual {
// @dev Ensure ONLY can be called 'internally'.
if (msg.sender != address(this)) revert OnlySelf();
_lzReceiveSimulate(_origin, _guid, _message, _executor, _extraData);
}
/**
* @dev Internal function to handle the OAppPreCrimeSimulator simulated receive.
* @param _origin The origin information.
* - srcEid: The source chain endpoint ID.
* - sender: The sender address from the src chain.
* - nonce: The nonce of the LayerZero message.
* @param _guid The GUID of the LayerZero message.
* @param _message The LayerZero message.
* @param _executor The address of the off-chain executor.
* @param _extraData Arbitrary data passed by the msg executor.
*
* @dev Enables the preCrime simulator to mock sending lzReceive() messages,
* routes the msg down from the OAppPreCrimeSimulator, and back up to the OAppReceiver.
*/
function _lzReceiveSimulate(
Origin calldata _origin,
bytes32 _guid,
bytes calldata _message,
address _executor,
bytes calldata _extraData
) internal virtual;
/**
* @dev checks if the specified peer is considered 'trusted' by the OApp.
* @param _eid The endpoint Id to check.
* @param _peer The peer to check.
* @return Whether the peer passed is considered 'trusted' by the OApp.
*/
function isPeer(uint32 _eid, bytes32 _peer) public view virtual returns (bool);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
library OFTComposeMsgCodec {
// Offset constants for decoding composed messages
uint8 private constant NONCE_OFFSET = 8;
uint8 private constant SRC_EID_OFFSET = 12;
uint8 private constant AMOUNT_LD_OFFSET = 44;
uint8 private constant COMPOSE_FROM_OFFSET = 76;
/**
* @dev Encodes a OFT composed message.
* @param _nonce The nonce value.
* @param _srcEid The source endpoint ID.
* @param _amountLD The amount in local decimals.
* @param _composeMsg The composed message.
* @return _msg The encoded Composed message.
*/
function encode(
uint64 _nonce,
uint32 _srcEid,
uint256 _amountLD,
bytes memory _composeMsg // 0x[composeFrom][composeMsg]
) internal pure returns (bytes memory _msg) {
_msg = abi.encodePacked(_nonce, _srcEid, _amountLD, _composeMsg);
}
/**
* @dev Retrieves the nonce from the composed message.
* @param _msg The message.
* @return The nonce value.
*/
function nonce(bytes calldata _msg) internal pure returns (uint64) {
return uint64(bytes8(_msg[:NONCE_OFFSET]));
}
/**
* @dev Retrieves the source endpoint ID from the composed message.
* @param _msg The message.
* @return The source endpoint ID.
*/
function srcEid(bytes calldata _msg) internal pure returns (uint32) {
return uint32(bytes4(_msg[NONCE_OFFSET:SRC_EID_OFFSET]));
}
/**
* @dev Retrieves the amount in local decimals from the composed message.
* @param _msg The message.
* @return The amount in local decimals.
*/
function amountLD(bytes calldata _msg) internal pure returns (uint256) {
return uint256(bytes32(_msg[SRC_EID_OFFSET:AMOUNT_LD_OFFSET]));
}
/**
* @dev Retrieves the composeFrom value from the composed message.
* @param _msg The message.
* @return The composeFrom value.
*/
function composeFrom(bytes calldata _msg) internal pure returns (bytes32) {
return bytes32(_msg[AMOUNT_LD_OFFSET:COMPOSE_FROM_OFFSET]);
}
/**
* @dev Retrieves the composed message.
* @param _msg The message.
* @return The composed message.
*/
function composeMsg(bytes calldata _msg) internal pure returns (bytes memory) {
return _msg[COMPOSE_FROM_OFFSET:];
}
/**
* @dev Converts an address to bytes32.
* @param _addr The address to convert.
* @return The bytes32 representation of the address.
*/
function addressToBytes32(address _addr) internal pure returns (bytes32) {
return bytes32(uint256(uint160(_addr)));
}
/**
* @dev Converts bytes32 to an address.
* @param _b The bytes32 value to convert.
* @return The address representation of bytes32.
*/
function bytes32ToAddress(bytes32 _b) internal pure returns (address) {
return address(uint160(uint256(_b)));
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import { ILayerZeroReceiver, Origin } from "./ILayerZeroReceiver.sol";
interface IOAppReceiver is ILayerZeroReceiver {
/**
* @notice Retrieves the address responsible for 'sending' composeMsg's to the Endpoint.
* @return sender The address responsible for 'sending' composeMsg's to the Endpoint.
*
* @dev Applications can optionally choose to implement a separate composeMsg sender that is NOT the bridging layer.
* @dev The default sender IS the OApp implementer.
*/
function composeMsgSender() external view returns (address sender);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import { ILayerZeroEndpointV2 } from "./ILayerZeroEndpointV2.sol";
/**
* @title IOAppCore
*/
interface IOAppCore {
// Custom error messages
error OnlyPeer(uint32 eid, bytes32 sender);
error NoPeer(uint32 eid);
error InvalidEndpointCall();
error InvalidDelegate();
// Event emitted when a peer (OApp) is set for a corresponding endpoint
event PeerSet(uint32 eid, bytes32 peer);
/**
* @notice Retrieves the OApp version information.
* @return senderVersion The version of the OAppSender.sol contract.
* @return receiverVersion The version of the OAppReceiver.sol contract.
*/
function oAppVersion() external view returns (uint64 senderVersion, uint64 receiverVersion);
/**
* @notice Retrieves the LayerZero endpoint associated with the OApp.
* @return iEndpoint The LayerZero endpoint as an interface.
*/
function endpoint() external view returns (ILayerZeroEndpointV2 iEndpoint);
/**
* @notice Retrieves the peer (OApp) associated with a corresponding endpoint.
* @param _eid The endpoint ID.
* @return peer The peer address (OApp instance) associated with the corresponding endpoint.
*/
function peers(uint32 _eid) external view returns (bytes32 peer);
/**
* @notice Sets the peer address (OApp instance) for a corresponding endpoint.
* @param _eid The endpoint ID.
* @param _peer The address of the peer to be associated with the corresponding endpoint.
*/
function setPeer(uint32 _eid, bytes32 _peer) external;
/**
* @notice Sets the delegate address for the OApp Core.
* @param _delegate The address of the delegate to be set.
*/
function setDelegate(address _delegate) external;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @title BitMath
/// @dev This library provides functionality for computing bit properties of an unsigned integer
/// @author Solady (https://github.com/Vectorized/solady/blob/8200a70e8dc2a77ecb074fc2e99a2a0d36547522/src/utils/LibBit.sol)
library BitMath {
/// @notice Returns the index of the most significant bit of the number,
/// where the least significant bit is at index 0 and the most significant bit is at index 255
/// @param x the value for which to compute the most significant bit, must be greater than 0
/// @return r the index of the most significant bit
function mostSignificantBit(uint256 x) internal pure returns (uint8 r) {
require(x > 0);
assembly ("memory-safe") {
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)),
0x0706060506020500060203020504000106050205030304010505030400000000))
}
}
/// @notice Returns the index of the least significant bit of the number,
/// where the least significant bit is at index 0 and the most significant bit is at index 255
/// @param x the value for which to compute the least significant bit, must be greater than 0
/// @return r the index of the least significant bit
function leastSignificantBit(uint256 x) internal pure returns (uint8 r) {
require(x > 0);
assembly ("memory-safe") {
// Isolate the least significant bit.
x := and(x, sub(0, x))
// For the upper 3 bits of the result, use a De Bruijn-like lookup.
// Credit to adhusson: https://blog.adhusson.com/cheap-find-first-set-evm/
// forgefmt: disable-next-item
r := shl(5, shr(252, shl(shl(2, shr(250, mul(x,
0xb6db6db6ddddddddd34d34d349249249210842108c6318c639ce739cffffffff))),
0x8040405543005266443200005020610674053026020000107506200176117077)))
// For the lower 5 bits of the result, use a De Bruijn lookup.
// forgefmt: disable-next-item
r := or(r, byte(and(div(0xd76453e0, shr(r, x)), 0x1f),
0x001f0d1e100c1d070f090b19131c1706010e11080a1a141802121b1503160405))
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @title Library for reverting with custom errors efficiently
/// @notice Contains functions for reverting with custom errors with different argument types efficiently
/// @dev To use this library, declare `using CustomRevert for bytes4;` and replace `revert CustomError()` with
/// `CustomError.selector.revertWith()`
/// @dev The functions may tamper with the free memory pointer but it is fine since the call context is exited immediately
library CustomRevert {
/// @dev ERC-7751 error for wrapping bubbled up reverts
error WrappedError(address target, bytes4 selector, bytes reason, bytes details);
/// @dev Reverts with the selector of a custom error in the scratch space
function revertWith(bytes4 selector) internal pure {
assembly ("memory-safe") {
mstore(0, selector)
revert(0, 0x04)
}
}
/// @dev Reverts with a custom error with an address argument in the scratch space
function revertWith(bytes4 selector, address addr) internal pure {
assembly ("memory-safe") {
mstore(0, selector)
mstore(0x04, and(addr, 0xffffffffffffffffffffffffffffffffffffffff))
revert(0, 0x24)
}
}
/// @dev Reverts with a custom error with an int24 argument in the scratch space
function revertWith(bytes4 selector, int24 value) internal pure {
assembly ("memory-safe") {
mstore(0, selector)
mstore(0x04, signextend(2, value))
revert(0, 0x24)
}
}
/// @dev Reverts with a custom error with a uint160 argument in the scratch space
function revertWith(bytes4 selector, uint160 value) internal pure {
assembly ("memory-safe") {
mstore(0, selector)
mstore(0x04, and(value, 0xffffffffffffffffffffffffffffffffffffffff))
revert(0, 0x24)
}
}
/// @dev Reverts with a custom error with two int24 arguments
function revertWith(bytes4 selector, int24 value1, int24 value2) internal pure {
assembly ("memory-safe") {
let fmp := mload(0x40)
mstore(fmp, selector)
mstore(add(fmp, 0x04), signextend(2, value1))
mstore(add(fmp, 0x24), signextend(2, value2))
revert(fmp, 0x44)
}
}
/// @dev Reverts with a custom error with two uint160 arguments
function revertWith(bytes4 selector, uint160 value1, uint160 value2) internal pure {
assembly ("memory-safe") {
let fmp := mload(0x40)
mstore(fmp, selector)
mstore(add(fmp, 0x04), and(value1, 0xffffffffffffffffffffffffffffffffffffffff))
mstore(add(fmp, 0x24), and(value2, 0xffffffffffffffffffffffffffffffffffffffff))
revert(fmp, 0x44)
}
}
/// @dev Reverts with a custom error with two address arguments
function revertWith(bytes4 selector, address value1, address value2) internal pure {
assembly ("memory-safe") {
let fmp := mload(0x40)
mstore(fmp, selector)
mstore(add(fmp, 0x04), and(value1, 0xffffffffffffffffffffffffffffffffffffffff))
mstore(add(fmp, 0x24), and(value2, 0xffffffffffffffffffffffffffffffffffffffff))
revert(fmp, 0x44)
}
}
/// @notice bubble up the revert message returned by a call and revert with a wrapped ERC-7751 error
/// @dev this method can be vulnerable to revert data bombs
function bubbleUpAndRevertWith(
address revertingContract,
bytes4 revertingFunctionSelector,
bytes4 additionalContext
) internal pure {
bytes4 wrappedErrorSelector = WrappedError.selector;
assembly ("memory-safe") {
// Ensure the size of the revert data is a multiple of 32 bytes
let encodedDataSize := mul(div(add(returndatasize(), 31), 32), 32)
let fmp := mload(0x40)
// Encode wrapped error selector, address, function selector, offset, additional context, size, revert reason
mstore(fmp, wrappedErrorSelector)
mstore(add(fmp, 0x04), and(revertingContract, 0xffffffffffffffffffffffffffffffffffffffff))
mstore(
add(fmp, 0x24),
and(revertingFunctionSelector, 0xffffffff00000000000000000000000000000000000000000000000000000000)
)
// offset revert reason
mstore(add(fmp, 0x44), 0x80)
// offset additional context
mstore(add(fmp, 0x64), add(0xa0, encodedDataSize))
// size revert reason
mstore(add(fmp, 0x84), returndatasize())
// revert reason
returndatacopy(add(fmp, 0xa4), 0, returndatasize())
// size additional context
mstore(add(fmp, add(0xa4, encodedDataSize)), 0x04)
// additional context
mstore(
add(fmp, add(0xc4, encodedDataSize)),
and(additionalContext, 0xffffffff00000000000000000000000000000000000000000000000000000000)
)
revert(fmp, add(0xe4, encodedDataSize))
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Permit.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*
* ==== Security Considerations
*
* There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
* expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
* considered as an intention to spend the allowance in any specific way. The second is that because permits have
* built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
* take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
* generally recommended is:
*
* ```solidity
* function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
* try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
* doThing(..., value);
* }
*
* function doThing(..., uint256 value) public {
* token.safeTransferFrom(msg.sender, address(this), value);
* ...
* }
* ```
*
* Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
* `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
* {SafeERC20-safeTransferFrom}).
*
* Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
* contracts should have entry points that don't rely on permit.
*/
interface IERC20Permit {
/**
* @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
* given ``owner``'s signed approval.
*
* IMPORTANT: The same issues {IERC20-approve} has related to transaction
* ordering also apply here.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `deadline` must be a timestamp in the future.
* - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
* over the EIP712-formatted function arguments.
* - the signature must use ``owner``'s current nonce (see {nonces}).
*
* For more information on the signature format, see the
* https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
* section].
*
* CAUTION: See Security Considerations above.
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
/**
* @dev Returns the current nonce for `owner`. This value must be
* included whenever a signature is generated for {permit}.
*
* Every successful call to {permit} increases ``owner``'s nonce by one. This
* prevents a signature from being used multiple times.
*/
function nonces(address owner) external view returns (uint256);
/**
* @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
*/
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view returns (bytes32);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol)
pragma solidity ^0.8.20;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev The ETH balance of the account is not enough to perform the operation.
*/
error AddressInsufficientBalance(address account);
/**
* @dev There's no code at `target` (it is not a contract).
*/
error AddressEmptyCode(address target);
/**
* @dev A call to an address target failed. The target may have reverted.
*/
error FailedInnerCall();
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
if (address(this).balance < amount) {
revert AddressInsufficientBalance(address(this));
}
(bool success, ) = recipient.call{value: amount}("");
if (!success) {
revert FailedInnerCall();
}
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason or custom error, it is bubbled
* up by this function (like regular Solidity function calls). However, if
* the call reverted with no returned reason, this function reverts with a
* {FailedInnerCall} error.
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
if (address(this).balance < value) {
revert AddressInsufficientBalance(address(this));
}
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
* was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an
* unsuccessful call.
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata
) internal view returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
// only check if target is a contract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
if (returndata.length == 0 && target.code.length == 0) {
revert AddressEmptyCode(target);
}
return returndata;
}
}
/**
* @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
* revert reason or with a default {FailedInnerCall} error.
*/
function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
return returndata;
}
}
/**
* @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}.
*/
function _revert(bytes memory returndata) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert FailedInnerCall();
}
}
}// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
struct SetConfigParam {
uint32 eid;
uint32 configType;
bytes config;
}
interface IMessageLibManager {
struct Timeout {
address lib;
uint256 expiry;
}
event LibraryRegistered(address newLib);
event DefaultSendLibrarySet(uint32 eid, address newLib);
event DefaultReceiveLibrarySet(uint32 eid, address newLib);
event DefaultReceiveLibraryTimeoutSet(uint32 eid, address oldLib, uint256 expiry);
event SendLibrarySet(address sender, uint32 eid, address newLib);
event ReceiveLibrarySet(address receiver, uint32 eid, address newLib);
event ReceiveLibraryTimeoutSet(address receiver, uint32 eid, address oldLib, uint256 timeout);
function registerLibrary(address _lib) external;
function isRegisteredLibrary(address _lib) external view returns (bool);
function getRegisteredLibraries() external view returns (address[] memory);
function setDefaultSendLibrary(uint32 _eid, address _newLib) external;
function defaultSendLibrary(uint32 _eid) external view returns (address);
function setDefaultReceiveLibrary(uint32 _eid, address _newLib, uint256 _gracePeriod) external;
function defaultReceiveLibrary(uint32 _eid) external view returns (address);
function setDefaultReceiveLibraryTimeout(uint32 _eid, address _lib, uint256 _expiry) external;
function defaultReceiveLibraryTimeout(uint32 _eid) external view returns (address lib, uint256 expiry);
function isSupportedEid(uint32 _eid) external view returns (bool);
function isValidReceiveLibrary(address _receiver, uint32 _eid, address _lib) external view returns (bool);
/// ------------------- OApp interfaces -------------------
function setSendLibrary(address _oapp, uint32 _eid, address _newLib) external;
function getSendLibrary(address _sender, uint32 _eid) external view returns (address lib);
function isDefaultSendLibrary(address _sender, uint32 _eid) external view returns (bool);
function setReceiveLibrary(address _oapp, uint32 _eid, address _newLib, uint256 _gracePeriod) external;
function getReceiveLibrary(address _receiver, uint32 _eid) external view returns (address lib, bool isDefault);
function setReceiveLibraryTimeout(address _oapp, uint32 _eid, address _lib, uint256 _expiry) external;
function receiveLibraryTimeout(address _receiver, uint32 _eid) external view returns (address lib, uint256 expiry);
function setConfig(address _oapp, address _lib, SetConfigParam[] calldata _params) external;
function getConfig(
address _oapp,
address _lib,
uint32 _eid,
uint32 _configType
) external view returns (bytes memory config);
}// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
interface IMessagingComposer {
event ComposeSent(address from, address to, bytes32 guid, uint16 index, bytes message);
event ComposeDelivered(address from, address to, bytes32 guid, uint16 index);
event LzComposeAlert(
address indexed from,
address indexed to,
address indexed executor,
bytes32 guid,
uint16 index,
uint256 gas,
uint256 value,
bytes message,
bytes extraData,
bytes reason
);
function composeQueue(
address _from,
address _to,
bytes32 _guid,
uint16 _index
) external view returns (bytes32 messageHash);
function sendCompose(address _to, bytes32 _guid, uint16 _index, bytes calldata _message) external;
function lzCompose(
address _from,
address _to,
bytes32 _guid,
uint16 _index,
bytes calldata _message,
bytes calldata _extraData
) external payable;
}// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
interface IMessagingChannel {
event InboundNonceSkipped(uint32 srcEid, bytes32 sender, address receiver, uint64 nonce);
event PacketNilified(uint32 srcEid, bytes32 sender, address receiver, uint64 nonce, bytes32 payloadHash);
event PacketBurnt(uint32 srcEid, bytes32 sender, address receiver, uint64 nonce, bytes32 payloadHash);
function eid() external view returns (uint32);
// this is an emergency function if a message cannot be verified for some reasons
// required to provide _nextNonce to avoid race condition
function skip(address _oapp, uint32 _srcEid, bytes32 _sender, uint64 _nonce) external;
function nilify(address _oapp, uint32 _srcEid, bytes32 _sender, uint64 _nonce, bytes32 _payloadHash) external;
function burn(address _oapp, uint32 _srcEid, bytes32 _sender, uint64 _nonce, bytes32 _payloadHash) external;
function nextGuid(address _sender, uint32 _dstEid, bytes32 _receiver) external view returns (bytes32);
function inboundNonce(address _receiver, uint32 _srcEid, bytes32 _sender) external view returns (uint64);
function outboundNonce(address _sender, uint32 _dstEid, bytes32 _receiver) external view returns (uint64);
function inboundPayloadHash(
address _receiver,
uint32 _srcEid,
bytes32 _sender,
uint64 _nonce
) external view returns (bytes32);
function lazyInboundNonce(address _receiver, uint32 _srcEid, bytes32 _sender) external view returns (uint64);
}// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
interface IMessagingContext {
function isSendingMessage() external view returns (bool);
function getSendContext() external view returns (uint32 dstEid, address sender);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @title IPoolAddressesProvider
* @author Aave
* @notice Defines the basic interface for a Pool Addresses Provider.
*/
interface IPoolAddressesProvider {
/**
* @dev Emitted when the market identifier is updated.
* @param oldMarketId The old id of the market
* @param newMarketId The new id of the market
*/
event MarketIdSet(string indexed oldMarketId, string indexed newMarketId);
/**
* @dev Emitted when the pool is updated.
* @param oldAddress The old address of the Pool
* @param newAddress The new address of the Pool
*/
event PoolUpdated(address indexed oldAddress, address indexed newAddress);
/**
* @dev Emitted when the pool configurator is updated.
* @param oldAddress The old address of the PoolConfigurator
* @param newAddress The new address of the PoolConfigurator
*/
event PoolConfiguratorUpdated(address indexed oldAddress, address indexed newAddress);
/**
* @dev Emitted when the price oracle is updated.
* @param oldAddress The old address of the PriceOracle
* @param newAddress The new address of the PriceOracle
*/
event PriceOracleUpdated(address indexed oldAddress, address indexed newAddress);
/**
* @dev Emitted when the ACL manager is updated.
* @param oldAddress The old address of the ACLManager
* @param newAddress The new address of the ACLManager
*/
event ACLManagerUpdated(address indexed oldAddress, address indexed newAddress);
/**
* @dev Emitted when the ACL admin is updated.
* @param oldAddress The old address of the ACLAdmin
* @param newAddress The new address of the ACLAdmin
*/
event ACLAdminUpdated(address indexed oldAddress, address indexed newAddress);
/**
* @dev Emitted when the price oracle sentinel is updated.
* @param oldAddress The old address of the PriceOracleSentinel
* @param newAddress The new address of the PriceOracleSentinel
*/
event PriceOracleSentinelUpdated(address indexed oldAddress, address indexed newAddress);
/**
* @dev Emitted when the pool data provider is updated.
* @param oldAddress The old address of the PoolDataProvider
* @param newAddress The new address of the PoolDataProvider
*/
event PoolDataProviderUpdated(address indexed oldAddress, address indexed newAddress);
/**
* @dev Emitted when a new proxy is created.
* @param id The identifier of the proxy
* @param proxyAddress The address of the created proxy contract
* @param implementationAddress The address of the implementation contract
*/
event ProxyCreated(
bytes32 indexed id,
address indexed proxyAddress,
address indexed implementationAddress
);
/**
* @dev Emitted when a new non-proxied contract address is registered.
* @param id The identifier of the contract
* @param oldAddress The address of the old contract
* @param newAddress The address of the new contract
*/
event AddressSet(bytes32 indexed id, address indexed oldAddress, address indexed newAddress);
/**
* @dev Emitted when the implementation of the proxy registered with id is updated
* @param id The identifier of the contract
* @param proxyAddress The address of the proxy contract
* @param oldImplementationAddress The address of the old implementation contract
* @param newImplementationAddress The address of the new implementation contract
*/
event AddressSetAsProxy(
bytes32 indexed id,
address indexed proxyAddress,
address oldImplementationAddress,
address indexed newImplementationAddress
);
/**
* @notice Returns the id of the Aave market to which this contract points to.
* @return The market id
*/
function getMarketId() external view returns (string memory);
/**
* @notice Associates an id with a specific PoolAddressesProvider.
* @dev This can be used to create an onchain registry of PoolAddressesProviders to
* identify and validate multiple Aave markets.
* @param newMarketId The market id
*/
function setMarketId(string calldata newMarketId) external;
/**
* @notice Returns an address by its identifier.
* @dev The returned address might be an EOA or a contract, potentially proxied
* @dev It returns ZERO if there is no registered address with the given id
* @param id The id
* @return The address of the registered for the specified id
*/
function getAddress(bytes32 id) external view returns (address);
/**
* @notice General function to update the implementation of a proxy registered with
* certain `id`. If there is no proxy registered, it will instantiate one and
* set as implementation the `newImplementationAddress`.
* @dev IMPORTANT Use this function carefully, only for ids that don't have an explicit
* setter function, in order to avoid unexpected consequences
* @param id The id
* @param newImplementationAddress The address of the new implementation
*/
function setAddressAsProxy(bytes32 id, address newImplementationAddress) external;
/**
* @notice Sets an address for an id replacing the address saved in the addresses map.
* @dev IMPORTANT Use this function carefully, as it will do a hard replacement
* @param id The id
* @param newAddress The address to set
*/
function setAddress(bytes32 id, address newAddress) external;
/**
* @notice Returns the address of the Pool proxy.
* @return The Pool proxy address
*/
function getPool() external view returns (address);
/**
* @notice Updates the implementation of the Pool, or creates a proxy
* setting the new `pool` implementation when the function is called for the first time.
* @param newPoolImpl The new Pool implementation
*/
function setPoolImpl(address newPoolImpl) external;
/**
* @notice Returns the address of the PoolConfigurator proxy.
* @return The PoolConfigurator proxy address
*/
function getPoolConfigurator() external view returns (address);
/**
* @notice Updates the implementation of the PoolConfigurator, or creates a proxy
* setting the new `PoolConfigurator` implementation when the function is called for the first time.
* @param newPoolConfiguratorImpl The new PoolConfigurator implementation
*/
function setPoolConfiguratorImpl(address newPoolConfiguratorImpl) external;
/**
* @notice Returns the address of the price oracle.
* @return The address of the PriceOracle
*/
function getPriceOracle() external view returns (address);
/**
* @notice Updates the address of the price oracle.
* @param newPriceOracle The address of the new PriceOracle
*/
function setPriceOracle(address newPriceOracle) external;
/**
* @notice Returns the address of the ACL manager.
* @return The address of the ACLManager
*/
function getACLManager() external view returns (address);
/**
* @notice Updates the address of the ACL manager.
* @param newAclManager The address of the new ACLManager
*/
function setACLManager(address newAclManager) external;
/**
* @notice Returns the address of the ACL admin.
* @return The address of the ACL admin
*/
function getACLAdmin() external view returns (address);
/**
* @notice Updates the address of the ACL admin.
* @param newAclAdmin The address of the new ACL admin
*/
function setACLAdmin(address newAclAdmin) external;
/**
* @notice Returns the address of the price oracle sentinel.
* @return The address of the PriceOracleSentinel
*/
function getPriceOracleSentinel() external view returns (address);
/**
* @notice Updates the address of the price oracle sentinel.
* @param newPriceOracleSentinel The address of the new PriceOracleSentinel
*/
function setPriceOracleSentinel(address newPriceOracleSentinel) external;
/**
* @notice Returns the address of the data provider.
* @return The address of the DataProvider
*/
function getPoolDataProvider() external view returns (address);
/**
* @notice Updates the address of the data provider.
* @param newDataProvider The address of the new DataProvider
*/
function setPoolDataProvider(address newDataProvider) external;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
library DataTypes {
/**
* This exists specifically to maintain the `getReserveData()` interface, since the new, internal
* `ReserveData` struct includes the reserve's `virtualUnderlyingBalance`.
*/
struct ReserveDataLegacy {
//stores the reserve configuration
ReserveConfigurationMap configuration;
//the liquidity index. Expressed in ray
uint128 liquidityIndex;
//the current supply rate. Expressed in ray
uint128 currentLiquidityRate;
//variable borrow index. Expressed in ray
uint128 variableBorrowIndex;
//the current variable borrow rate. Expressed in ray
uint128 currentVariableBorrowRate;
// DEPRECATED on v3.2.0
uint128 currentStableBorrowRate;
//timestamp of last update
uint40 lastUpdateTimestamp;
//the id of the reserve. Represents the position in the list of the active reserves
uint16 id;
//aToken address
address aTokenAddress;
// DEPRECATED on v3.2.0
address stableDebtTokenAddress;
//variableDebtToken address
address variableDebtTokenAddress;
//address of the interest rate strategy
address interestRateStrategyAddress;
//the current treasury balance, scaled
uint128 accruedToTreasury;
//the outstanding unbacked aTokens minted through the bridging feature
uint128 unbacked;
//the outstanding debt borrowed against this asset in isolation mode
uint128 isolationModeTotalDebt;
}
struct ReserveData {
//stores the reserve configuration
ReserveConfigurationMap configuration;
//the liquidity index. Expressed in ray
uint128 liquidityIndex;
//the current supply rate. Expressed in ray
uint128 currentLiquidityRate;
//variable borrow index. Expressed in ray
uint128 variableBorrowIndex;
//the current variable borrow rate. Expressed in ray
uint128 currentVariableBorrowRate;
/// @notice reused `__deprecatedStableBorrowRate` storage from pre 3.2
// the current accumulate deficit in underlying tokens
uint128 deficit;
//timestamp of last update
uint40 lastUpdateTimestamp;
//the id of the reserve. Represents the position in the list of the active reserves
uint16 id;
//timestamp until when liquidations are not allowed on the reserve, if set to past liquidations will be allowed
uint40 liquidationGracePeriodUntil;
//aToken address
address aTokenAddress;
// DEPRECATED on v3.2.0
address __deprecatedStableDebtTokenAddress;
//variableDebtToken address
address variableDebtTokenAddress;
//address of the interest rate strategy
address interestRateStrategyAddress;
//the current treasury balance, scaled
uint128 accruedToTreasury;
//the outstanding unbacked aTokens minted through the bridging feature
uint128 unbacked;
//the outstanding debt borrowed against this asset in isolation mode
uint128 isolationModeTotalDebt;
//the amount of underlying accounted for by the protocol
uint128 virtualUnderlyingBalance;
}
struct ReserveConfigurationMap {
//bit 0-15: LTV
//bit 16-31: Liq. threshold
//bit 32-47: Liq. bonus
//bit 48-55: Decimals
//bit 56: reserve is active
//bit 57: reserve is frozen
//bit 58: borrowing is enabled
//bit 59: DEPRECATED: stable rate borrowing enabled
//bit 60: asset is paused
//bit 61: borrowing in isolation mode is enabled
//bit 62: siloed borrowing enabled
//bit 63: flashloaning enabled
//bit 64-79: reserve factor
//bit 80-115: borrow cap in whole tokens, borrowCap == 0 => no cap
//bit 116-151: supply cap in whole tokens, supplyCap == 0 => no cap
//bit 152-167: liquidation protocol fee
//bit 168-175: DEPRECATED: eMode category
//bit 176-211: unbacked mint cap in whole tokens, unbackedMintCap == 0 => minting disabled
//bit 212-251: debt ceiling for isolation mode with (ReserveConfiguration::DEBT_CEILING_DECIMALS) decimals
//bit 252: virtual accounting is enabled for the reserve
//bit 253-255 unused
uint256 data;
}
struct UserConfigurationMap {
/**
* @dev Bitmap of the users collaterals and borrows. It is divided in pairs of bits, one pair per asset.
* The first bit indicates if an asset is used as collateral by the user, the second whether an
* asset is borrowed by the user.
*/
uint256 data;
}
// DEPRECATED: kept for backwards compatibility, might be removed in a future version
struct EModeCategoryLegacy {
// each eMode category has a custom ltv and liquidation threshold
uint16 ltv;
uint16 liquidationThreshold;
uint16 liquidationBonus;
// DEPRECATED
address priceSource;
string label;
}
struct CollateralConfig {
uint16 ltv;
uint16 liquidationThreshold;
uint16 liquidationBonus;
}
struct EModeCategoryBaseConfiguration {
uint16 ltv;
uint16 liquidationThreshold;
uint16 liquidationBonus;
string label;
}
struct EModeCategory {
// each eMode category has a custom ltv and liquidation threshold
uint16 ltv;
uint16 liquidationThreshold;
uint16 liquidationBonus;
uint128 collateralBitmap;
string label;
uint128 borrowableBitmap;
}
enum InterestRateMode {
NONE,
__DEPRECATED,
VARIABLE
}
struct ReserveCache {
uint256 currScaledVariableDebt;
uint256 nextScaledVariableDebt;
uint256 currLiquidityIndex;
uint256 nextLiquidityIndex;
uint256 currVariableBorrowIndex;
uint256 nextVariableBorrowIndex;
uint256 currLiquidityRate;
uint256 currVariableBorrowRate;
uint256 reserveFactor;
ReserveConfigurationMap reserveConfiguration;
address aTokenAddress;
address variableDebtTokenAddress;
uint40 reserveLastUpdateTimestamp;
}
struct ExecuteLiquidationCallParams {
uint256 reservesCount;
uint256 debtToCover;
address collateralAsset;
address debtAsset;
address user;
bool receiveAToken;
address priceOracle;
uint8 userEModeCategory;
address priceOracleSentinel;
}
struct ExecuteSupplyParams {
address asset;
uint256 amount;
address onBehalfOf;
uint16 referralCode;
}
struct ExecuteBorrowParams {
address asset;
address user;
address onBehalfOf;
uint256 amount;
InterestRateMode interestRateMode;
uint16 referralCode;
bool releaseUnderlying;
uint256 reservesCount;
address oracle;
uint8 userEModeCategory;
address priceOracleSentinel;
}
struct ExecuteRepayParams {
address asset;
uint256 amount;
InterestRateMode interestRateMode;
address onBehalfOf;
bool useATokens;
}
struct ExecuteWithdrawParams {
address asset;
uint256 amount;
address to;
uint256 reservesCount;
address oracle;
uint8 userEModeCategory;
}
struct ExecuteEliminateDeficitParams {
address asset;
uint256 amount;
}
struct ExecuteSetUserEModeParams {
uint256 reservesCount;
address oracle;
uint8 categoryId;
}
struct FinalizeTransferParams {
address asset;
address from;
address to;
uint256 amount;
uint256 balanceFromBefore;
uint256 balanceToBefore;
uint256 reservesCount;
address oracle;
uint8 fromEModeCategory;
}
struct FlashloanParams {
address receiverAddress;
address[] assets;
uint256[] amounts;
uint256[] interestRateModes;
address onBehalfOf;
bytes params;
uint16 referralCode;
uint256 flashLoanPremiumToProtocol;
uint256 flashLoanPremiumTotal;
uint256 reservesCount;
address addressesProvider;
address pool;
uint8 userEModeCategory;
bool isAuthorizedFlashBorrower;
}
struct FlashloanSimpleParams {
address receiverAddress;
address asset;
uint256 amount;
bytes params;
uint16 referralCode;
uint256 flashLoanPremiumToProtocol;
uint256 flashLoanPremiumTotal;
}
struct FlashLoanRepaymentParams {
uint256 amount;
uint256 totalPremium;
uint256 flashLoanPremiumToProtocol;
address asset;
address receiverAddress;
uint16 referralCode;
}
struct CalculateUserAccountDataParams {
UserConfigurationMap userConfig;
uint256 reservesCount;
address user;
address oracle;
uint8 userEModeCategory;
}
struct ValidateBorrowParams {
ReserveCache reserveCache;
UserConfigurationMap userConfig;
address asset;
address userAddress;
uint256 amount;
InterestRateMode interestRateMode;
uint256 reservesCount;
address oracle;
uint8 userEModeCategory;
address priceOracleSentinel;
bool isolationModeActive;
address isolationModeCollateralAddress;
uint256 isolationModeDebtCeiling;
}
struct ValidateLiquidationCallParams {
ReserveCache debtReserveCache;
uint256 totalDebt;
uint256 healthFactor;
address priceOracleSentinel;
}
struct CalculateInterestRatesParams {
uint256 unbacked;
uint256 liquidityAdded;
uint256 liquidityTaken;
uint256 totalDebt;
uint256 reserveFactor;
address reserve;
bool usingVirtualBalance;
uint256 virtualUnderlyingBalance;
}
struct InitReserveParams {
address asset;
address aTokenAddress;
address variableDebtAddress;
address interestRateStrategyAddress;
uint16 reservesCount;
uint16 maxNumberReserves;
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @title FixedPoint96
/// @notice A library for handling binary fixed point numbers, see https://en.wikipedia.org/wiki/Q_(number_format)
/// @dev Used in SqrtPriceMath.sol
library FixedPoint96 {
uint8 internal constant RESOLUTION = 96;
uint256 internal constant Q96 = 0x1000000000000000000000000;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {CustomRevert} from "./CustomRevert.sol";
/// @title Safe casting methods
/// @notice Contains methods for safely casting between types
library SafeCast {
using CustomRevert for bytes4;
error SafeCastOverflow();
/// @notice Cast a uint256 to a uint160, revert on overflow
/// @param x The uint256 to be downcasted
/// @return y The downcasted integer, now type uint160
function toUint160(uint256 x) internal pure returns (uint160 y) {
y = uint160(x);
if (y != x) SafeCastOverflow.selector.revertWith();
}
/// @notice Cast a uint256 to a uint128, revert on overflow
/// @param x The uint256 to be downcasted
/// @return y The downcasted integer, now type uint128
function toUint128(uint256 x) internal pure returns (uint128 y) {
y = uint128(x);
if (x != y) SafeCastOverflow.selector.revertWith();
}
/// @notice Cast a int128 to a uint128, revert on overflow or underflow
/// @param x The int128 to be casted
/// @return y The casted integer, now type uint128
function toUint128(int128 x) internal pure returns (uint128 y) {
if (x < 0) SafeCastOverflow.selector.revertWith();
y = uint128(x);
}
/// @notice Cast a int256 to a int128, revert on overflow or underflow
/// @param x The int256 to be downcasted
/// @return y The downcasted integer, now type int128
function toInt128(int256 x) internal pure returns (int128 y) {
y = int128(x);
if (y != x) SafeCastOverflow.selector.revertWith();
}
/// @notice Cast a uint256 to a int256, revert on overflow
/// @param x The uint256 to be casted
/// @return y The casted integer, now type int256
function toInt256(uint256 x) internal pure returns (int256 y) {
y = int256(x);
if (y < 0) SafeCastOverflow.selector.revertWith();
}
/// @notice Cast a uint256 to a int128, revert on overflow
/// @param x The uint256 to be downcasted
/// @return The downcasted integer, now type int128
function toInt128(uint256 x) internal pure returns (int128) {
if (x >= 1 << 127) SafeCastOverflow.selector.revertWith();
return int128(int256(x));
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @notice Interface for the callback executed when an address unlocks the pool manager
interface IUnlockCallback {
/// @notice Called by the pool manager on `msg.sender` when the manager is unlocked
/// @param data The data that was passed to the call to unlock
/// @return Any data that you want to be returned from the unlock call
function unlockCallback(bytes calldata data) external returns (bytes memory);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {IImmutableState} from "../interfaces/IImmutableState.sol";
/// @title Immutable State
/// @notice A collection of immutable state variables, commonly used across multiple contracts
contract ImmutableState is IImmutableState {
/// @inheritdoc IImmutableState
IPoolManager public immutable poolManager;
/// @notice Thrown when the caller is not PoolManager
error NotPoolManager();
/// @notice Only allow calls from the PoolManager contract
modifier onlyPoolManager() {
if (msg.sender != address(poolManager)) revert NotPoolManager();
_;
}
constructor(IPoolManager _poolManager) {
poolManager = _poolManager;
}
}// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.24;
import {Currency} from "../types/Currency.sol";
import {CustomRevert} from "./CustomRevert.sol";
library CurrencyReserves {
using CustomRevert for bytes4;
/// bytes32(uint256(keccak256("ReservesOf")) - 1)
bytes32 constant RESERVES_OF_SLOT = 0x1e0745a7db1623981f0b2a5d4232364c00787266eb75ad546f190e6cebe9bd95;
/// bytes32(uint256(keccak256("Currency")) - 1)
bytes32 constant CURRENCY_SLOT = 0x27e098c505d44ec3574004bca052aabf76bd35004c182099d8c575fb238593b9;
function getSyncedCurrency() internal view returns (Currency currency) {
assembly ("memory-safe") {
currency := tload(CURRENCY_SLOT)
}
}
function resetCurrency() internal {
assembly ("memory-safe") {
tstore(CURRENCY_SLOT, 0)
}
}
function syncCurrencyAndReserves(Currency currency, uint256 value) internal {
assembly ("memory-safe") {
tstore(CURRENCY_SLOT, and(currency, 0xffffffffffffffffffffffffffffffffffffffff))
tstore(RESERVES_OF_SLOT, value)
}
}
function getSyncedReserves() internal view returns (uint256 value) {
assembly ("memory-safe") {
value := tload(RESERVES_OF_SLOT)
}
}
}// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.24;
/// @notice This is a temporary library that allows us to use transient storage (tstore/tload)
/// for the nonzero delta count.
/// TODO: This library can be deleted when we have the transient keyword support in solidity.
library NonzeroDeltaCount {
// The slot holding the number of nonzero deltas. bytes32(uint256(keccak256("NonzeroDeltaCount")) - 1)
bytes32 internal constant NONZERO_DELTA_COUNT_SLOT =
0x7d4b3164c6e45b97e7d87b7125a44c5828d005af88f9d751cfd78729c5d99a0b;
function read() internal view returns (uint256 count) {
assembly ("memory-safe") {
count := tload(NONZERO_DELTA_COUNT_SLOT)
}
}
function increment() internal {
assembly ("memory-safe") {
let count := tload(NONZERO_DELTA_COUNT_SLOT)
count := add(count, 1)
tstore(NONZERO_DELTA_COUNT_SLOT, count)
}
}
/// @notice Potential to underflow. Ensure checks are performed by integrating contracts to ensure this does not happen.
/// Current usage ensures this will not happen because we call decrement with known boundaries (only up to the number of times we call increment).
function decrement() internal {
assembly ("memory-safe") {
let count := tload(NONZERO_DELTA_COUNT_SLOT)
count := sub(count, 1)
tstore(NONZERO_DELTA_COUNT_SLOT, count)
}
}
}// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.24;
/// @notice This is a temporary library that allows us to use transient storage (tstore/tload)
/// TODO: This library can be deleted when we have the transient keyword support in solidity.
library Lock {
// The slot holding the unlocked state, transiently. bytes32(uint256(keccak256("Unlocked")) - 1)
bytes32 internal constant IS_UNLOCKED_SLOT = 0xc090fc4683624cfc3884e9d8de5eca132f2d0ec062aff75d43c0465d5ceeab23;
function unlock() internal {
assembly ("memory-safe") {
// unlock
tstore(IS_UNLOCKED_SLOT, true)
}
}
function lock() internal {
assembly ("memory-safe") {
tstore(IS_UNLOCKED_SLOT, false)
}
}
function isUnlocked() internal view returns (bool unlocked) {
assembly ("memory-safe") {
unlocked := tload(IS_UNLOCKED_SLOT)
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @title Minimal ERC20 interface for Uniswap
/// @notice Contains a subset of the full ERC20 interface that is used in Uniswap V3
interface IERC20Minimal {
/// @notice Returns an account's balance in the token
/// @param account The account for which to look up the number of tokens it has, i.e. its balance
/// @return The number of tokens held by the account
function balanceOf(address account) external view returns (uint256);
/// @notice Transfers the amount of token from the `msg.sender` to the recipient
/// @param recipient The account that will receive the amount transferred
/// @param amount The number of tokens to send from the sender to the recipient
/// @return Returns true for a successful transfer, false for an unsuccessful transfer
function transfer(address recipient, uint256 amount) external returns (bool);
/// @notice Returns the current allowance given to a spender by an owner
/// @param owner The account of the token owner
/// @param spender The account of the token spender
/// @return The current allowance granted by `owner` to `spender`
function allowance(address owner, address spender) external view returns (uint256);
/// @notice Sets the allowance of a spender from the `msg.sender` to the value `amount`
/// @param spender The account which will be allowed to spend a given amount of the owners tokens
/// @param amount The amount of tokens allowed to be used by `spender`
/// @return Returns true for a successful approval, false for unsuccessful
function approve(address spender, uint256 amount) external returns (bool);
/// @notice Transfers `amount` tokens from `sender` to `recipient` up to the allowance given to the `msg.sender`
/// @param sender The account from which the transfer will be initiated
/// @param recipient The recipient of the transfer
/// @param amount The amount of the transfer
/// @return Returns true for a successful transfer, false for unsuccessful
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
/// @notice Event emitted when tokens are transferred from one address to another, either via `#transfer` or `#transferFrom`.
/// @param from The account from which the tokens were sent, i.e. the balance decreased
/// @param to The account to which the tokens were sent, i.e. the balance increased
/// @param value The amount of tokens that were transferred
event Transfer(address indexed from, address indexed to, uint256 value);
/// @notice Event emitted when the approval amount for the spender of a given owner's tokens changes.
/// @param owner The account that approved spending of its tokens
/// @param spender The account for which the spending allowance was modified
/// @param value The new allowance from the owner to the spender
event Approval(address indexed owner, address indexed spender, uint256 value);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @notice Interface for claims over a contract balance, wrapped as a ERC6909
interface IERC6909Claims {
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event OperatorSet(address indexed owner, address indexed operator, bool approved);
event Approval(address indexed owner, address indexed spender, uint256 indexed id, uint256 amount);
event Transfer(address caller, address indexed from, address indexed to, uint256 indexed id, uint256 amount);
/*//////////////////////////////////////////////////////////////
FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @notice Owner balance of an id.
/// @param owner The address of the owner.
/// @param id The id of the token.
/// @return amount The balance of the token.
function balanceOf(address owner, uint256 id) external view returns (uint256 amount);
/// @notice Spender allowance of an id.
/// @param owner The address of the owner.
/// @param spender The address of the spender.
/// @param id The id of the token.
/// @return amount The allowance of the token.
function allowance(address owner, address spender, uint256 id) external view returns (uint256 amount);
/// @notice Checks if a spender is approved by an owner as an operator
/// @param owner The address of the owner.
/// @param spender The address of the spender.
/// @return approved The approval status.
function isOperator(address owner, address spender) external view returns (bool approved);
/// @notice Transfers an amount of an id from the caller to a receiver.
/// @param receiver The address of the receiver.
/// @param id The id of the token.
/// @param amount The amount of the token.
/// @return bool True, always, unless the function reverts
function transfer(address receiver, uint256 id, uint256 amount) external returns (bool);
/// @notice Transfers an amount of an id from a sender to a receiver.
/// @param sender The address of the sender.
/// @param receiver The address of the receiver.
/// @param id The id of the token.
/// @param amount The amount of the token.
/// @return bool True, always, unless the function reverts
function transferFrom(address sender, address receiver, uint256 id, uint256 amount) external returns (bool);
/// @notice Approves an amount of an id to a spender.
/// @param spender The address of the spender.
/// @param id The id of the token.
/// @param amount The amount of the token.
/// @return bool True, always
function approve(address spender, uint256 id, uint256 amount) external returns (bool);
/// @notice Sets or removes an operator for the caller.
/// @param operator The address of the operator.
/// @param approved The approval status.
/// @return bool True, always
function setOperator(address operator, bool approved) external returns (bool);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {Currency} from "../types/Currency.sol";
import {PoolId} from "../types/PoolId.sol";
import {PoolKey} from "../types/PoolKey.sol";
/// @notice Interface for all protocol-fee related functions in the pool manager
interface IProtocolFees {
/// @notice Thrown when protocol fee is set too high
error ProtocolFeeTooLarge(uint24 fee);
/// @notice Thrown when collectProtocolFees or setProtocolFee is not called by the controller.
error InvalidCaller();
/// @notice Thrown when collectProtocolFees is attempted on a token that is synced.
error ProtocolFeeCurrencySynced();
/// @notice Emitted when the protocol fee controller address is updated in setProtocolFeeController.
event ProtocolFeeControllerUpdated(address indexed protocolFeeController);
/// @notice Emitted when the protocol fee is updated for a pool.
event ProtocolFeeUpdated(PoolId indexed id, uint24 protocolFee);
/// @notice Given a currency address, returns the protocol fees accrued in that currency
/// @param currency The currency to check
/// @return amount The amount of protocol fees accrued in the currency
function protocolFeesAccrued(Currency currency) external view returns (uint256 amount);
/// @notice Sets the protocol fee for the given pool
/// @param key The key of the pool to set a protocol fee for
/// @param newProtocolFee The fee to set
function setProtocolFee(PoolKey memory key, uint24 newProtocolFee) external;
/// @notice Sets the protocol fee controller
/// @param controller The new protocol fee controller
function setProtocolFeeController(address controller) external;
/// @notice Collects the protocol fees for a given recipient and currency, returning the amount collected
/// @dev This will revert if the contract is unlocked
/// @param recipient The address to receive the protocol fees
/// @param currency The currency to withdraw
/// @param amount The amount of currency to withdraw
/// @return amountCollected The amount of currency successfully withdrawn
function collectProtocolFees(address recipient, Currency currency, uint256 amount)
external
returns (uint256 amountCollected);
/// @notice Returns the current protocol fee controller address
/// @return address The current protocol fee controller address
function protocolFeeController() external view returns (address);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @notice Interface for functions to access any storage slot in a contract
interface IExtsload {
/// @notice Called by external contracts to access granular pool state
/// @param slot Key of slot to sload
/// @return value The value of the slot as bytes32
function extsload(bytes32 slot) external view returns (bytes32 value);
/// @notice Called by external contracts to access granular pool state
/// @param startSlot Key of slot to start sloading from
/// @param nSlots Number of slots to load into return value
/// @return values List of loaded values.
function extsload(bytes32 startSlot, uint256 nSlots) external view returns (bytes32[] memory values);
/// @notice Called by external contracts to access sparse pool state
/// @param slots List of slots to SLOAD from.
/// @return values List of loaded values.
function extsload(bytes32[] calldata slots) external view returns (bytes32[] memory values);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
/// @notice Interface for functions to access any transient storage slot in a contract
interface IExttload {
/// @notice Called by external contracts to access transient storage of the contract
/// @param slot Key of slot to tload
/// @return value The value of the slot as bytes32
function exttload(bytes32 slot) external view returns (bytes32 value);
/// @notice Called by external contracts to access sparse transient pool state
/// @param slots List of slots to tload
/// @return values List of loaded values
function exttload(bytes32[] calldata slots) external view returns (bytes32[] memory values);
}// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import {FullMath} from "./FullMath.sol";
import {FixedPoint128} from "./FixedPoint128.sol";
import {LiquidityMath} from "./LiquidityMath.sol";
import {CustomRevert} from "./CustomRevert.sol";
/// @title Position
/// @notice Positions represent an owner address' liquidity between a lower and upper tick boundary
/// @dev Positions store additional state for tracking fees owed to the position
library Position {
using CustomRevert for bytes4;
/// @notice Cannot update a position with no liquidity
error CannotUpdateEmptyPosition();
// info stored for each user's position
struct State {
// the amount of liquidity owned by this position
uint128 liquidity;
// fee growth per unit of liquidity as of the last update to liquidity or fees owed
uint256 feeGrowthInside0LastX128;
uint256 feeGrowthInside1LastX128;
}
/// @notice Returns the State struct of a position, given an owner and position boundaries
/// @param self The mapping containing all user positions
/// @param owner The address of the position owner
/// @param tickLower The lower tick boundary of the position
/// @param tickUpper The upper tick boundary of the position
/// @param salt A unique value to differentiate between multiple positions in the same range
/// @return position The position info struct of the given owners' position
function get(mapping(bytes32 => State) storage self, address owner, int24 tickLower, int24 tickUpper, bytes32 salt)
internal
view
returns (State storage position)
{
bytes32 positionKey = calculatePositionKey(owner, tickLower, tickUpper, salt);
position = self[positionKey];
}
/// @notice A helper function to calculate the position key
/// @param owner The address of the position owner
/// @param tickLower the lower tick boundary of the position
/// @param tickUpper the upper tick boundary of the position
/// @param salt A unique value to differentiate between multiple positions in the same range, by the same owner. Passed in by the caller.
function calculatePositionKey(address owner, int24 tickLower, int24 tickUpper, bytes32 salt)
internal
pure
returns (bytes32 positionKey)
{
// positionKey = keccak256(abi.encodePacked(owner, tickLower, tickUpper, salt))
assembly ("memory-safe") {
let fmp := mload(0x40)
mstore(add(fmp, 0x26), salt) // [0x26, 0x46)
mstore(add(fmp, 0x06), tickUpper) // [0x23, 0x26)
mstore(add(fmp, 0x03), tickLower) // [0x20, 0x23)
mstore(fmp, owner) // [0x0c, 0x20)
positionKey := keccak256(add(fmp, 0x0c), 0x3a) // len is 58 bytes
// now clean the memory we used
mstore(add(fmp, 0x40), 0) // fmp+0x40 held salt
mstore(add(fmp, 0x20), 0) // fmp+0x20 held tickLower, tickUpper, salt
mstore(fmp, 0) // fmp held owner
}
}
/// @notice Credits accumulated fees to a user's position
/// @param self The individual position to update
/// @param liquidityDelta The change in pool liquidity as a result of the position update
/// @param feeGrowthInside0X128 The all-time fee growth in currency0, per unit of liquidity, inside the position's tick boundaries
/// @param feeGrowthInside1X128 The all-time fee growth in currency1, per unit of liquidity, inside the position's tick boundaries
/// @return feesOwed0 The amount of currency0 owed to the position owner
/// @return feesOwed1 The amount of currency1 owed to the position owner
function update(
State storage self,
int128 liquidityDelta,
uint256 feeGrowthInside0X128,
uint256 feeGrowthInside1X128
) internal returns (uint256 feesOwed0, uint256 feesOwed1) {
uint128 liquidity = self.liquidity;
if (liquidityDelta == 0) {
// disallow pokes for 0 liquidity positions
if (liquidity == 0) CannotUpdateEmptyPosition.selector.revertWith();
} else {
self.liquidity = LiquidityMath.addDelta(liquidity, liquidityDelta);
}
// calculate accumulated fees. overflow in the subtraction of fee growth is expected
unchecked {
feesOwed0 =
FullMath.mulDiv(feeGrowthInside0X128 - self.feeGrowthInside0LastX128, liquidity, FixedPoint128.Q128);
feesOwed1 =
FullMath.mulDiv(feeGrowthInside1X128 - self.feeGrowthInside1LastX128, liquidity, FixedPoint128.Q128);
}
// update the position
self.feeGrowthInside0LastX128 = feeGrowthInside0X128;
self.feeGrowthInside1LastX128 = feeGrowthInside1X128;
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
// Return type of the beforeSwap hook.
// Upper 128 bits is the delta in specified tokens. Lower 128 bits is delta in unspecified tokens (to match the afterSwap hook)
type BeforeSwapDelta is int256;
// Creates a BeforeSwapDelta from specified and unspecified
function toBeforeSwapDelta(int128 deltaSpecified, int128 deltaUnspecified)
pure
returns (BeforeSwapDelta beforeSwapDelta)
{
assembly ("memory-safe") {
beforeSwapDelta := or(shl(128, deltaSpecified), and(sub(shl(128, 1), 1), deltaUnspecified))
}
}
/// @notice Library for getting the specified and unspecified deltas from the BeforeSwapDelta type
library BeforeSwapDeltaLibrary {
/// @notice A BeforeSwapDelta of 0
BeforeSwapDelta public constant ZERO_DELTA = BeforeSwapDelta.wrap(0);
/// extracts int128 from the upper 128 bits of the BeforeSwapDelta
/// returned by beforeSwap
function getSpecifiedDelta(BeforeSwapDelta delta) internal pure returns (int128 deltaSpecified) {
assembly ("memory-safe") {
deltaSpecified := sar(128, delta)
}
}
/// extracts int128 from the lower 128 bits of the BeforeSwapDelta
/// returned by beforeSwap and afterSwap
function getUnspecifiedDelta(BeforeSwapDelta delta) internal pure returns (int128 deltaUnspecified) {
assembly ("memory-safe") {
deltaUnspecified := signextend(15, delta)
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/**
* @dev Struct representing enforced option parameters.
*/
struct EnforcedOptionParam {
uint32 eid; // Endpoint ID
uint16 msgType; // Message Type
bytes options; // Additional options
}
/**
* @title IOAppOptionsType3
* @dev Interface for the OApp with Type 3 Options, allowing the setting and combining of enforced options.
*/
interface IOAppOptionsType3 {
// Custom error message for invalid options
error InvalidOptions(bytes options);
// Event emitted when enforced options are set
event EnforcedOptionSet(EnforcedOptionParam[] _enforcedOptions);
/**
* @notice Sets enforced options for specific endpoint and message type combinations.
* @param _enforcedOptions An array of EnforcedOptionParam structures specifying enforced options.
*/
function setEnforcedOptions(EnforcedOptionParam[] calldata _enforcedOptions) external;
/**
* @notice Combines options for a given endpoint and message type.
* @param _eid The endpoint ID.
* @param _msgType The OApp message type.
* @param _extraOptions Additional options passed by the caller.
* @return options The combination of caller specified options AND enforced options.
*/
function combineOptions(
uint32 _eid,
uint16 _msgType,
bytes calldata _extraOptions
) external view returns (bytes memory options);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
struct PreCrimePeer {
uint32 eid;
bytes32 preCrime;
bytes32 oApp;
}
// TODO not done yet
interface IPreCrime {
error OnlyOffChain();
// for simulate()
error PacketOversize(uint256 max, uint256 actual);
error PacketUnsorted();
error SimulationFailed(bytes reason);
// for preCrime()
error SimulationResultNotFound(uint32 eid);
error InvalidSimulationResult(uint32 eid, bytes reason);
error CrimeFound(bytes crime);
function getConfig(bytes[] calldata _packets, uint256[] calldata _packetMsgValues) external returns (bytes memory);
function simulate(
bytes[] calldata _packets,
uint256[] calldata _packetMsgValues
) external payable returns (bytes memory);
function buildSimulationResult() external view returns (bytes memory);
function preCrime(
bytes[] calldata _packets,
uint256[] calldata _packetMsgValues,
bytes[] calldata _simulations
) external;
function version() external view returns (uint64 major, uint8 minor);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
// @dev Import the Origin so it's exposed to OAppPreCrimeSimulator implementers.
// solhint-disable-next-line no-unused-import
import { InboundPacket, Origin } from "../libs/Packet.sol";
/**
* @title IOAppPreCrimeSimulator Interface
* @dev Interface for the preCrime simulation functionality in an OApp.
*/
interface IOAppPreCrimeSimulator {
// @dev simulation result used in PreCrime implementation
error SimulationResult(bytes result);
error OnlySelf();
/**
* @dev Emitted when the preCrime contract address is set.
* @param preCrimeAddress The address of the preCrime contract.
*/
event PreCrimeSet(address preCrimeAddress);
/**
* @dev Retrieves the address of the preCrime contract implementation.
* @return The address of the preCrime contract.
*/
function preCrime() external view returns (address);
/**
* @dev Retrieves the address of the OApp contract.
* @return The address of the OApp contract.
*/
function oApp() external view returns (address);
/**
* @dev Sets the preCrime contract address.
* @param _preCrime The address of the preCrime contract.
*/
function setPreCrime(address _preCrime) external;
/**
* @dev Mocks receiving a packet, then reverts with a series of data to infer the state/result.
* @param _packets An array of LayerZero InboundPacket objects representing received packets.
*/
function lzReceiveAndRevert(InboundPacket[] calldata _packets) external payable;
/**
* @dev checks if the specified peer is considered 'trusted' by the OApp.
* @param _eid The endpoint Id to check.
* @param _peer The peer to check.
* @return Whether the peer passed is considered 'trusted' by the OApp.
*/
function isPeer(uint32 _eid, bytes32 _peer) external view returns (bool);
}// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
import { Origin } from "./ILayerZeroEndpointV2.sol";
interface ILayerZeroReceiver {
function allowInitializePath(Origin calldata _origin) external view returns (bool);
function nextNonce(uint32 _eid, bytes32 _sender) external view returns (uint64);
function lzReceive(
Origin calldata _origin,
bytes32 _guid,
bytes calldata _message,
address _executor,
bytes calldata _extraData
) external payable;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
/// @title IImmutableState
/// @notice Interface for the ImmutableState contract
interface IImmutableState {
/// @notice The Uniswap v4 PoolManager contract
function poolManager() external view returns (IPoolManager);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @title FixedPoint128
/// @notice A library for handling binary fixed point numbers, see https://en.wikipedia.org/wiki/Q_(number_format)
library FixedPoint128 {
uint256 internal constant Q128 = 0x100000000000000000000000000000000;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @title Math library for liquidity
library LiquidityMath {
/// @notice Add a signed liquidity delta to liquidity and revert if it overflows or underflows
/// @param x The liquidity before change
/// @param y The delta by which liquidity should be changed
/// @return z The liquidity delta
function addDelta(uint128 x, int128 y) internal pure returns (uint128 z) {
assembly ("memory-safe") {
z := add(and(x, 0xffffffffffffffffffffffffffffffff), signextend(15, y))
if shr(128, z) {
// revert SafeCastOverflow()
mstore(0, 0x93dafdf1)
revert(0x1c, 0x04)
}
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import { Origin } from "../interfaces/ILayerZeroEndpointV2.sol";
import { PacketV1Codec } from "./PacketV1Codec.sol";
/**
* @title InboundPacket
* @dev Structure representing an inbound packet received by the contract.
*/
struct InboundPacket {
Origin origin; // Origin information of the packet.
uint32 dstEid; // Destination endpointId of the packet.
address receiver; // Receiver address for the packet.
bytes32 guid; // Unique identifier of the packet.
uint256 value; // msg.value of the packet.
address executor; // Executor address for the packet.
bytes message; // Message payload of the packet.
bytes extraData; // Additional arbitrary data for the packet.
}
/**
* @title PacketDecoder
* @dev Library for decoding LayerZero packets.
*/
library PacketDecoder {
using PacketV1Codec for bytes;
/**
* @dev Decode an inbound packet from the given packet data.
* @param _packet The packet data to decode.
* @return packet An InboundPacket struct representing the decoded packet.
*/
function decode(bytes calldata _packet) internal pure returns (InboundPacket memory packet) {
packet.origin = Origin(_packet.srcEid(), _packet.sender(), _packet.nonce());
packet.dstEid = _packet.dstEid();
packet.receiver = _packet.receiverB20();
packet.guid = _packet.guid();
packet.message = _packet.message();
}
/**
* @dev Decode multiple inbound packets from the given packet data and associated message values.
* @param _packets An array of packet data to decode.
* @param _packetMsgValues An array of associated message values for each packet.
* @return packets An array of InboundPacket structs representing the decoded packets.
*/
function decode(
bytes[] calldata _packets,
uint256[] memory _packetMsgValues
) internal pure returns (InboundPacket[] memory packets) {
packets = new InboundPacket[](_packets.length);
for (uint256 i = 0; i < _packets.length; i++) {
bytes calldata packet = _packets[i];
packets[i] = PacketDecoder.decode(packet);
// @dev Allows the verifier to specify the msg.value that gets passed in lzReceive.
packets[i].value = _packetMsgValues[i];
}
}
}// SPDX-License-Identifier: LZBL-1.2
pragma solidity ^0.8.20;
import { Packet } from "../interfaces/ISendLib.sol";
import { AddressCast } from "./AddressCast.sol";
library PacketV1Codec {
using AddressCast for address;
using AddressCast for bytes32;
uint8 internal constant PACKET_VERSION = 1;
// header (version + nonce + path)
// version
uint256 private constant PACKET_VERSION_OFFSET = 0;
// nonce
uint256 private constant NONCE_OFFSET = 1;
// path
uint256 private constant SRC_EID_OFFSET = 9;
uint256 private constant SENDER_OFFSET = 13;
uint256 private constant DST_EID_OFFSET = 45;
uint256 private constant RECEIVER_OFFSET = 49;
// payload (guid + message)
uint256 private constant GUID_OFFSET = 81; // keccak256(nonce + path)
uint256 private constant MESSAGE_OFFSET = 113;
function encode(Packet memory _packet) internal pure returns (bytes memory encodedPacket) {
encodedPacket = abi.encodePacked(
PACKET_VERSION,
_packet.nonce,
_packet.srcEid,
_packet.sender.toBytes32(),
_packet.dstEid,
_packet.receiver,
_packet.guid,
_packet.message
);
}
function encodePacketHeader(Packet memory _packet) internal pure returns (bytes memory) {
return
abi.encodePacked(
PACKET_VERSION,
_packet.nonce,
_packet.srcEid,
_packet.sender.toBytes32(),
_packet.dstEid,
_packet.receiver
);
}
function encodePayload(Packet memory _packet) internal pure returns (bytes memory) {
return abi.encodePacked(_packet.guid, _packet.message);
}
function header(bytes calldata _packet) internal pure returns (bytes calldata) {
return _packet[0:GUID_OFFSET];
}
function version(bytes calldata _packet) internal pure returns (uint8) {
return uint8(bytes1(_packet[PACKET_VERSION_OFFSET:NONCE_OFFSET]));
}
function nonce(bytes calldata _packet) internal pure returns (uint64) {
return uint64(bytes8(_packet[NONCE_OFFSET:SRC_EID_OFFSET]));
}
function srcEid(bytes calldata _packet) internal pure returns (uint32) {
return uint32(bytes4(_packet[SRC_EID_OFFSET:SENDER_OFFSET]));
}
function sender(bytes calldata _packet) internal pure returns (bytes32) {
return bytes32(_packet[SENDER_OFFSET:DST_EID_OFFSET]);
}
function senderAddressB20(bytes calldata _packet) internal pure returns (address) {
return sender(_packet).toAddress();
}
function dstEid(bytes calldata _packet) internal pure returns (uint32) {
return uint32(bytes4(_packet[DST_EID_OFFSET:RECEIVER_OFFSET]));
}
function receiver(bytes calldata _packet) internal pure returns (bytes32) {
return bytes32(_packet[RECEIVER_OFFSET:GUID_OFFSET]);
}
function receiverB20(bytes calldata _packet) internal pure returns (address) {
return receiver(_packet).toAddress();
}
function guid(bytes calldata _packet) internal pure returns (bytes32) {
return bytes32(_packet[GUID_OFFSET:MESSAGE_OFFSET]);
}
function message(bytes calldata _packet) internal pure returns (bytes calldata) {
return bytes(_packet[MESSAGE_OFFSET:]);
}
function payload(bytes calldata _packet) internal pure returns (bytes calldata) {
return bytes(_packet[GUID_OFFSET:]);
}
function payloadHash(bytes calldata _packet) internal pure returns (bytes32) {
return keccak256(payload(_packet));
}
}// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
import { MessagingFee } from "./ILayerZeroEndpointV2.sol";
import { IMessageLib } from "./IMessageLib.sol";
struct Packet {
uint64 nonce;
uint32 srcEid;
address sender;
uint32 dstEid;
bytes32 receiver;
bytes32 guid;
bytes message;
}
interface ISendLib is IMessageLib {
function send(
Packet calldata _packet,
bytes calldata _options,
bool _payInLzToken
) external returns (MessagingFee memory, bytes memory encodedPacket);
function quote(
Packet calldata _packet,
bytes calldata _options,
bool _payInLzToken
) external view returns (MessagingFee memory);
function setTreasury(address _treasury) external;
function withdrawFee(address _to, uint256 _amount) external;
function withdrawLzTokenFee(address _lzToken, address _to, uint256 _amount) external;
}// SPDX-License-Identifier: LZBL-1.2
pragma solidity ^0.8.20;
library AddressCast {
error AddressCast_InvalidSizeForAddress();
error AddressCast_InvalidAddress();
function toBytes32(bytes calldata _addressBytes) internal pure returns (bytes32 result) {
if (_addressBytes.length > 32) revert AddressCast_InvalidAddress();
result = bytes32(_addressBytes);
unchecked {
uint256 offset = 32 - _addressBytes.length;
result = result >> (offset * 8);
}
}
function toBytes32(address _address) internal pure returns (bytes32 result) {
result = bytes32(uint256(uint160(_address)));
}
function toBytes(bytes32 _addressBytes32, uint256 _size) internal pure returns (bytes memory result) {
if (_size == 0 || _size > 32) revert AddressCast_InvalidSizeForAddress();
result = new bytes(_size);
unchecked {
uint256 offset = 256 - _size * 8;
assembly {
mstore(add(result, 32), shl(offset, _addressBytes32))
}
}
}
function toAddress(bytes32 _addressBytes32) internal pure returns (address result) {
result = address(uint160(uint256(_addressBytes32)));
}
function toAddress(bytes calldata _addressBytes) internal pure returns (address result) {
if (_addressBytes.length != 20) revert AddressCast_InvalidAddress();
result = address(bytes20(_addressBytes));
}
}// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import { SetConfigParam } from "./IMessageLibManager.sol";
enum MessageLibType {
Send,
Receive,
SendAndReceive
}
interface IMessageLib is IERC165 {
function setConfig(address _oapp, SetConfigParam[] calldata _config) external;
function getConfig(uint32 _eid, address _oapp, uint32 _configType) external view returns (bytes memory config);
function isSupportedEid(uint32 _eid) external view returns (bool);
// message libs of same major version are compatible
function version() external view returns (uint64 major, uint8 minor, uint8 endpointVersion);
function messageLibType() external view returns (MessageLibType);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/IERC165.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}{
"remappings": [
"@uniswap/v4-core/=lib/v4-core/",
"forge-gas-snapshot/=lib/v4-core/lib/forge-gas-snapshot/src/",
"forge-std/=lib/v4-core/lib/forge-std/src/",
"permit2/=lib/v4-periphery/lib/permit2/",
"solmate/=lib/v4-core/lib/solmate/",
"v4-core/=lib/v4-core/",
"v4-periphery/=lib/v4-periphery/",
"solidity-math-utils/=lib/solidity-math-utils/project/contracts/",
"aave-v3/=lib/aave-v3-origin/src/contracts/",
"@openzeppelin/=lib/v4-core/lib/openzeppelin-contracts/",
"@ensdomains/=lib/v4-core/node_modules/@ensdomains/",
"aave-v3-origin/=lib/aave-v3-origin/",
"ds-test/=lib/aave-v3-origin/lib/forge-std/lib/ds-test/src/",
"erc4626-tests/=lib/v4-core/lib/openzeppelin-contracts/lib/erc4626-tests/",
"halmos-cheatcodes/=lib/aave-v3-origin/lib/solidity-utils/lib/openzeppelin-contracts-upgradeable/lib/halmos-cheatcodes/src/",
"hardhat/=lib/v4-core/node_modules/hardhat/",
"openzeppelin-contracts-upgradeable/=lib/aave-v3-origin/lib/solidity-utils/lib/openzeppelin-contracts-upgradeable/",
"openzeppelin-contracts/=lib/v4-core/lib/openzeppelin-contracts/",
"solady/=lib/solady/src/",
"solidity-utils/=lib/aave-v3-origin/lib/solidity-utils/"
],
"optimizer": {
"enabled": true,
"runs": 200
},
"metadata": {
"useLiteralContent": false,
"bytecodeHash": "none",
"appendCBOR": false
},
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"evmVersion": "cancun",
"viaIR": false,
"libraries": {
"src/Basket.sol": {
"BasketLib": "0x5f4a43d56f7bfecbb4fe2b2f8b13bfb450b5c455"
}
}
}Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
Contract ABI
API[{"inputs":[{"internalType":"address","name":"_vogue","type":"address"},{"internalType":"address","name":"_aux","type":"address"},{"internalType":"address","name":"_uma","type":"address"},{"internalType":"address","name":"_usdc","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"target","type":"address"}],"name":"AddressEmptyCode","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"AddressInsufficientBalance","type":"error"},{"inputs":[],"name":"AlreadyIn","type":"error"},{"inputs":[],"name":"BadType","type":"error"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"allowance","type":"uint256"},{"internalType":"uint256","name":"needed","type":"uint256"}],"name":"ERC20InsufficientAllowance","type":"error"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"balance","type":"uint256"},{"internalType":"uint256","name":"needed","type":"uint256"}],"name":"ERC20InsufficientBalance","type":"error"},{"inputs":[{"internalType":"address","name":"approver","type":"address"}],"name":"ERC20InvalidApprover","type":"error"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"}],"name":"ERC20InvalidReceiver","type":"error"},{"inputs":[{"internalType":"address","name":"sender","type":"address"}],"name":"ERC20InvalidSender","type":"error"},{"inputs":[{"internalType":"address","name":"spender","type":"address"}],"name":"ERC20InvalidSpender","type":"error"},{"inputs":[],"name":"EmptyPayload","type":"error"},{"inputs":[],"name":"FailedInnerCall","type":"error"},{"inputs":[],"name":"InsufficientBalance","type":"error"},{"inputs":[],"name":"InsufficientFee","type":"error"},{"inputs":[],"name":"InsufficientUnlocked","type":"error"},{"inputs":[],"name":"InvalidAmount","type":"error"},{"inputs":[],"name":"InvalidDelegate","type":"error"},{"inputs":[],"name":"InvalidEndpointCall","type":"error"},{"inputs":[],"name":"InvalidLocalDecimals","type":"error"},{"inputs":[],"name":"InvalidMarketId","type":"error"},{"inputs":[],"name":"InvalidMessageLength","type":"error"},{"inputs":[],"name":"InvalidMessageType","type":"error"},{"inputs":[{"internalType":"bytes","name":"options","type":"bytes"}],"name":"InvalidOptions","type":"error"},{"inputs":[],"name":"Locked","type":"error"},{"inputs":[],"name":"LzTokenUnavailable","type":"error"},{"inputs":[],"name":"MismatchedArrays","type":"error"},{"inputs":[],"name":"NoBalance","type":"error"},{"inputs":[],"name":"NoEndpoint","type":"error"},{"inputs":[{"internalType":"uint32","name":"eid","type":"uint32"}],"name":"NoPeer","type":"error"},{"inputs":[{"internalType":"uint256","name":"msgValue","type":"uint256"}],"name":"NotEnoughNative","type":"error"},{"inputs":[],"name":"NotIn","type":"error"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"OnlyEndpoint","type":"error"},{"inputs":[{"internalType":"uint32","name":"eid","type":"uint32"},{"internalType":"bytes32","name":"sender","type":"bytes32"}],"name":"OnlyPeer","type":"error"},{"inputs":[],"name":"OnlySelf","type":"error"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"OwnableInvalidOwner","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"OwnableUnauthorizedAccount","type":"error"},{"inputs":[],"name":"PayloadMismatch","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"SafeERC20FailedOperation","type":"error"},{"inputs":[{"internalType":"bytes","name":"result","type":"bytes"}],"name":"SimulationResult","type":"error"},{"inputs":[{"internalType":"uint256","name":"amountLD","type":"uint256"},{"internalType":"uint256","name":"minAmountLD","type":"uint256"}],"name":"SlippageExceeded","type":"error"},{"inputs":[],"name":"Unauthorized","type":"error"},{"inputs":[],"name":"WrongChain","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"components":[{"internalType":"uint32","name":"eid","type":"uint32"},{"internalType":"uint16","name":"msgType","type":"uint16"},{"internalType":"bytes","name":"options","type":"bytes"}],"indexed":false,"internalType":"struct EnforcedOptionParam[]","name":"_enforcedOptions","type":"tuple[]"}],"name":"EnforcedOptionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"inspector","type":"address"}],"name":"MsgInspectorSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"guid","type":"bytes32"},{"indexed":false,"internalType":"uint32","name":"srcEid","type":"uint32"},{"indexed":true,"internalType":"address","name":"toAddress","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountReceivedLD","type":"uint256"}],"name":"OFTReceived","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"guid","type":"bytes32"},{"indexed":false,"internalType":"uint32","name":"dstEid","type":"uint32"},{"indexed":true,"internalType":"address","name":"fromAddress","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountSentLD","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountReceivedLD","type":"uint256"}],"name":"OFTSent","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"OperatorSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint32","name":"eid","type":"uint32"},{"indexed":false,"internalType":"bytes32","name":"peer","type":"bytes32"}],"name":"PeerSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"preCrimeAddress","type":"address"}],"name":"PreCrimeSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"caller","type":"address"},{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"AUX","outputs":[{"internalType":"contract Aux","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LINK","outputs":[{"internalType":"contract Link","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SEND","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SEND_AND_CALL","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SOL_MAINNET_EID","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"V4","outputs":[{"internalType":"address payable","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"uint32","name":"srcEid","type":"uint32"},{"internalType":"bytes32","name":"sender","type":"bytes32"},{"internalType":"uint64","name":"nonce","type":"uint64"}],"internalType":"struct Origin","name":"origin","type":"tuple"}],"name":"allowInitializePath","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"approvalRequired","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"who","type":"address"}],"name":"auth","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32","name":"_eid","type":"uint32"},{"internalType":"uint16","name":"_msgType","type":"uint16"},{"internalType":"bytes","name":"_extraOptions","type":"bytes"}],"name":"combineOptions","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"composeMsgSender","outputs":[{"internalType":"address","name":"sender","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"currentMonth","outputs":[{"internalType":"uint256","name":"month","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimalConversionRate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"burned","type":"uint256"},{"internalType":"uint256","name":"total","type":"uint256"}],"name":"distributeL2","outputs":[{"internalType":"uint256","name":"totalOut","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"endpoint","outputs":[{"internalType":"contract ILayerZeroEndpointV2","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32","name":"eid","type":"uint32"},{"internalType":"uint16","name":"msgType","type":"uint16"}],"name":"enforcedOptions","outputs":[{"internalType":"bytes","name":"enforcedOption","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"isOperator","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32","name":"_eid","type":"uint32"},{"internalType":"bytes32","name":"_peer","type":"bytes32"}],"name":"isPeer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"idx","type":"uint256"}],"name":"juryPoolMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"juryPoolSize","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"l2Deposits","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"juror","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"lockForJury","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint32","name":"srcEid","type":"uint32"},{"internalType":"bytes32","name":"sender","type":"bytes32"},{"internalType":"uint64","name":"nonce","type":"uint64"}],"internalType":"struct Origin","name":"_origin","type":"tuple"},{"internalType":"bytes32","name":"_guid","type":"bytes32"},{"internalType":"bytes","name":"_message","type":"bytes"},{"internalType":"address","name":"_executor","type":"address"},{"internalType":"bytes","name":"_extraData","type":"bytes"}],"name":"lzReceive","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"components":[{"internalType":"uint32","name":"srcEid","type":"uint32"},{"internalType":"bytes32","name":"sender","type":"bytes32"},{"internalType":"uint64","name":"nonce","type":"uint64"}],"internalType":"struct Origin","name":"origin","type":"tuple"},{"internalType":"uint32","name":"dstEid","type":"uint32"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"bytes32","name":"guid","type":"bytes32"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"address","name":"executor","type":"address"},{"internalType":"bytes","name":"message","type":"bytes"},{"internalType":"bytes","name":"extraData","type":"bytes"}],"internalType":"struct InboundPacket[]","name":"_packets","type":"tuple[]"}],"name":"lzReceiveAndRevert","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"uint32","name":"srcEid","type":"uint32"},{"internalType":"bytes32","name":"sender","type":"bytes32"},{"internalType":"uint64","name":"nonce","type":"uint64"}],"internalType":"struct Origin","name":"_origin","type":"tuple"},{"internalType":"bytes32","name":"_guid","type":"bytes32"},{"internalType":"bytes","name":"_message","type":"bytes"},{"internalType":"address","name":"_executor","type":"address"},{"internalType":"bytes","name":"_extraData","type":"bytes"}],"name":"lzReceiveSimulate","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"marketCreated","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"pledge","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"when","type":"uint256"}],"name":"mint","outputs":[{"internalType":"uint256","name":"normalized","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"msgInspector","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32","name":"","type":"uint32"},{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"nextNonce","outputs":[{"internalType":"uint64","name":"nonce","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"oApp","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"oAppVersion","outputs":[{"internalType":"uint64","name":"senderVersion","type":"uint64"},{"internalType":"uint64","name":"receiverVersion","type":"uint64"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"oftVersion","outputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"},{"internalType":"uint64","name":"version","type":"uint64"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"optInJury","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"optOutJury","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32","name":"eid","type":"uint32"}],"name":"peers","outputs":[{"internalType":"bytes32","name":"peer","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"preCrime","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"uint32","name":"dstEid","type":"uint32"},{"internalType":"bytes32","name":"to","type":"bytes32"},{"internalType":"uint256","name":"amountLD","type":"uint256"},{"internalType":"uint256","name":"minAmountLD","type":"uint256"},{"internalType":"bytes","name":"extraOptions","type":"bytes"},{"internalType":"bytes","name":"composeMsg","type":"bytes"},{"internalType":"bytes","name":"oftCmd","type":"bytes"}],"internalType":"struct SendParam","name":"_sendParam","type":"tuple"}],"name":"quoteOFT","outputs":[{"components":[{"internalType":"uint256","name":"minAmountLD","type":"uint256"},{"internalType":"uint256","name":"maxAmountLD","type":"uint256"}],"internalType":"struct OFTLimit","name":"oftLimit","type":"tuple"},{"components":[{"internalType":"int256","name":"feeAmountLD","type":"int256"},{"internalType":"string","name":"description","type":"string"}],"internalType":"struct OFTFeeDetail[]","name":"oftFeeDetails","type":"tuple[]"},{"components":[{"internalType":"uint256","name":"amountSentLD","type":"uint256"},{"internalType":"uint256","name":"amountReceivedLD","type":"uint256"}],"internalType":"struct OFTReceipt","name":"oftReceipt","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"uint32","name":"dstEid","type":"uint32"},{"internalType":"bytes32","name":"to","type":"bytes32"},{"internalType":"uint256","name":"amountLD","type":"uint256"},{"internalType":"uint256","name":"minAmountLD","type":"uint256"},{"internalType":"bytes","name":"extraOptions","type":"bytes"},{"internalType":"bytes","name":"composeMsg","type":"bytes"},{"internalType":"bytes","name":"oftCmd","type":"bytes"}],"internalType":"struct SendParam","name":"_sendParam","type":"tuple"},{"internalType":"bool","name":"_payInLzToken","type":"bool"}],"name":"quoteSend","outputs":[{"components":[{"internalType":"uint256","name":"nativeFee","type":"uint256"},{"internalType":"uint256","name":"lzTokenFee","type":"uint256"}],"internalType":"struct MessagingFee","name":"msgFee","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_l2","type":"address"}],"name":"registerL2Basket","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint32","name":"dstEid","type":"uint32"},{"internalType":"bytes32","name":"to","type":"bytes32"},{"internalType":"uint256","name":"amountLD","type":"uint256"},{"internalType":"uint256","name":"minAmountLD","type":"uint256"},{"internalType":"bytes","name":"extraOptions","type":"bytes"},{"internalType":"bytes","name":"composeMsg","type":"bytes"},{"internalType":"bytes","name":"oftCmd","type":"bytes"}],"internalType":"struct SendParam","name":"_sendParam","type":"tuple"},{"components":[{"internalType":"uint256","name":"nativeFee","type":"uint256"},{"internalType":"uint256","name":"lzTokenFee","type":"uint256"}],"internalType":"struct MessagingFee","name":"_fee","type":"tuple"},{"internalType":"address","name":"_refundAddress","type":"address"}],"name":"send","outputs":[{"components":[{"internalType":"bytes32","name":"guid","type":"bytes32"},{"internalType":"uint64","name":"nonce","type":"uint64"},{"components":[{"internalType":"uint256","name":"nativeFee","type":"uint256"},{"internalType":"uint256","name":"lzTokenFee","type":"uint256"}],"internalType":"struct MessagingFee","name":"fee","type":"tuple"}],"internalType":"struct MessagingReceipt","name":"msgReceipt","type":"tuple"},{"components":[{"internalType":"uint256","name":"amountSentLD","type":"uint256"},{"internalType":"uint256","name":"amountReceivedLD","type":"uint256"}],"internalType":"struct OFTReceipt","name":"oftReceipt","type":"tuple"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes","name":"composeMsg","type":"bytes"}],"name":"sendToSolana","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"_delegate","type":"address"}],"name":"setDelegate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint32","name":"eid","type":"uint32"},{"internalType":"uint16","name":"msgType","type":"uint16"},{"internalType":"bytes","name":"options","type":"bytes"}],"internalType":"struct EnforcedOptionParam[]","name":"_enforcedOptions","type":"tuple[]"}],"name":"setEnforcedOptions","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_msgInspector","type":"address"}],"name":"setMsgInspector","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setOperator","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint32","name":"_eid","type":"uint32"},{"internalType":"bytes32","name":"_peer","type":"bytes32"}],"name":"setPeer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_preCrime","type":"address"}],"name":"setPreCrime","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_hook","type":"address"},{"internalType":"address","name":"_court","type":"address"},{"internalType":"address","name":"_jury","type":"address"}],"name":"setup","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"sharedDecimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"target","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"token","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"turn","outputs":[{"internalType":"uint256","name":"sent","type":"uint256"},{"internalType":"uint256","name":"seedBurned","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"juror","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"unlockFromJury","outputs":[],"stateMutability":"nonpayable","type":"function"}]Contract Creation Code
60c06040526001600d55348015610014575f80fd5b5060405161620938038061620983398101604081905261003391610291565b604051806040016040528060048152602001631455485160e21b81525060405180604001604052806002815260200161145160f21b815250731a44076050125825900e736c501f859c50fe728c33838361009161021d60201b60201c565b84848181818133806100bc57604051631e4fbdf760e01b81525f600482015260240160405180910390fd5b6100c581610222565b506001600160a01b0380831660805281166100f357604051632d618d8160e21b815260040160405180910390fd5b60805160405163ca5eb5e160e01b81526001600160a01b0383811660048301529091169063ca5eb5e1906024015f604051808303815f87803b158015610137575f80fd5b505af1158015610149573d5f803e3d5ffd5b505050505050505061015f61027160201b60201c565b60ff168360ff161015610185576040516301e9714b60e41b815260040160405180910390fd5b6101906006846102f6565b61019b90600a6103f8565b60a05250600891506101af905083826104a5565b5060096101bc82826104a5565b5050601280546001600160a01b03199081166001600160a01b039b8c1617909155601580548216978b169790971790965550506013805485169588169590951790945550506017805490911692909316919091179091555042600e5561055f565b601290565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b600690565b80516001600160a01b038116811461028c575f80fd5b919050565b5f805f80608085870312156102a4575f80fd5b6102ad85610276565b93506102bb60208601610276565b92506102c960408601610276565b91506102d760608601610276565b905092959194509250565b634e487b7160e01b5f52601160045260245ffd5b60ff828116828216039081111561030f5761030f6102e2565b92915050565b6001815b600184111561035057808504811115610334576103346102e2565b600184161561034257908102905b60019390931c928002610319565b935093915050565b5f826103665750600161030f565b8161037257505f61030f565b81600181146103885760028114610392576103ae565b600191505061030f565b60ff8411156103a3576103a36102e2565b50506001821b61030f565b5060208310610133831016604e8410600b84101617156103d1575081810a61030f565b6103dd5f198484610315565b805f19048211156103f0576103f06102e2565b029392505050565b5f61040660ff841683610358565b9392505050565b634e487b7160e01b5f52604160045260245ffd5b600181811c9082168061043557607f821691505b60208210810361045357634e487b7160e01b5f52602260045260245ffd5b50919050565b601f8211156104a057805f5260205f20601f840160051c8101602085101561047e5750805b601f840160051c820191505b8181101561049d575f815560010161048a565b50505b505050565b81516001600160401b038111156104be576104be61040d565b6104d2816104cc8454610421565b84610459565b6020601f821160018114610504575f83156104ed5750848201515b5f19600385901b1c1916600184901b17845561049d565b5f84815260208120601f198516915b828110156105335787850151825560209485019460019092019101610513565b508482101561055057868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b60805160a051615c436105c65f395f8181610a4a015281816139d901528181613a34015261417d01525f81816108c301528181610fa7015281816112fd015281816125fa015281816131ba015281816132b401528181614097015261414e0152615c435ff3fe6080604052600436106103ff575f3560e01c80636100d69a11610215578063b98bd0701161011e578063d4243885116100a8578063dd62ed3e11610078578063dd62ed3e14610c74578063f2fde38b14610cb8578063fc0c546a146107d2578063fe99049a14610cd7578063ff7bd03d14610cf6575f80fd5b8063d424388514610c18578063d4b8399214610c37578063d90b466114610c4c578063db2da34e14610c60575f80fd5b8063c7c7f5b3116100ee578063c7c7f5b314610b91578063ca01b5cf14610bb2578063ca5eb5e114610bc7578063cd5d211814610be6578063d045a0dc14610c05575f80fd5b8063b98bd07014610b15578063bb0b6a5314610b34578063bc70b35414610b5f578063bd815db014610b7e575f80fd5b80638da5cb5b1161019f578063a9059cbb1161016f578063a9059cbb14610a7e578063b6363cf214610a9d578063b731ea0a14610ad6578063b777fb7e14610af5578063b92d0eff146107d2575f80fd5b80638da5cb5b14610a0957806395d89b4114610a25578063963efcaa14610a395780639f68b96414610a6c575f80fd5b8063715018a6116101e5578063715018a61461097657806377b8b1c71461098a5780637d25a05e146109a9578063857749b0146109e2578063862a4d47146109f5575f80fd5b80636100d69a146108e5578063696c5ded146109045780636fc1b31e1461092357806370a0823114610942575f80fd5b80631b7fa24b116103175780633c173a4f116102a1578063558a729711610271578063558a729714610803578063598af9e7146108225780635a0dfe4d1461085e5780635cb0fd15146108935780635e280f11146108b2575f80fd5b80633c173a4f14610794578063426a8493146107b357806352ae2879146107d25780635535d461146107e4575f80fd5b8063313ce567116102e7578063313ce5671461070157806331ba5c0f146107225780633400288b1461073557806334c95fba146107545780633b6f743b14610768575f80fd5b80631b7fa24b1461067b5780631f5e13341461069a57806323b872dd146106ae5780632ff42ab6146106cd575f80fd5b806312530fb41161039857806316ce049b1161036857806316ce049b146105ea57806317442b701461060957806318160ddd1461062957806319b2c8111461063d5780631b6b6d231461065c575f80fd5b806312530fb41461056957806313137d6514610588578063134d4f251461059d578063156a0d0f146105c4575f80fd5b8063095bcdb6116103d3578063095bcdb6146104c6578063095ea7b3146104e55780630d35b41514610504578063111ecdad14610532575f80fd5b8062fdd58e1461040357806301ffc9a71461044c5780630455a1a11461047b57806306fdde03146104a5575b5f80fd5b34801561040e575f80fd5b5061043961041d366004614980565b600b60209081525f928352604080842090915290825290205481565b6040519081526020015b60405180910390f35b348015610457575f80fd5b5061046b6104663660046149aa565b610d15565b6040519015158152602001610443565b348015610486575f80fd5b506104906175d881565b60405163ffffffff9091168152602001610443565b3480156104b0575f80fd5b506104b9610d4b565b60405161044391906149ff565b3480156104d1575f80fd5b5061046b6104e0366004614a11565b610ddb565b3480156104f0575f80fd5b5061046b6104ff366004614980565b610e95565b34801561050f575f80fd5b5061052361051e366004614a59565b610eac565b60405161044393929190614a8a565b34801561053d575f80fd5b50600454610551906001600160a01b031681565b6040516001600160a01b039091168152602001610443565b348015610574575f80fd5b50610551610583366004614b25565b610f77565b61059b610596366004614b89565b610fa5565b005b3480156105a8575f80fd5b506105b1600281565b60405161ffff9091168152602001610443565b3480156105cf575f80fd5b506040805162b9270b60e21b81526001602082015201610443565b3480156105f5575f80fd5b50601254610551906001600160a01b031681565b348015610614575f80fd5b50604080516001808252602082015201610443565b348015610634575f80fd5b50600754610439565b348015610648575f80fd5b50610439610657366004614a11565b611065565b348015610667575f80fd5b50601654610551906001600160a01b031681565b348015610686575f80fd5b5061059b610695366004614c23565b611142565b3480156106a5575f80fd5b506105b1600181565b3480156106b9575f80fd5b5061046b6106c8366004614c3e565b6111f4565b3480156106d8575f80fd5b506106ec6106e7366004614980565b611218565b60408051928352602083019190915201610443565b34801561070c575f80fd5b5060125b60405160ff9091168152602001610443565b610439610730366004614d0e565b6112b4565b348015610740575f80fd5b5061059b61074f366004614d9a565b611481565b34801561075f575f80fd5b5061059b6114de565b348015610773575f80fd5b50610787610782366004614dc1565b611594565b6040516104439190614e0f565b34801561079f575f80fd5b506104396107ae366004614e26565b6115f8565b3480156107be575f80fd5b5061046b6107cd366004614a11565b611d14565b3480156107dd575f80fd5b5030610551565b3480156107ef575f80fd5b506104b96107fe366004614e7c565b611d78565b34801561080e575f80fd5b5061046b61081d366004614ead565b611e1a565b34801561082d575f80fd5b5061043961083c366004614c3e565b600c60209081525f938452604080852082529284528284209052825290205481565b348015610869575f80fd5b5061046b610878366004614d9a565b63ffffffff919091165f908152600160205260409020541490565b34801561089e575f80fd5b5061059b6108ad366004614980565b611e89565b3480156108bd575f80fd5b506105517f000000000000000000000000000000000000000000000000000000000000000081565b3480156108f0575f80fd5b5061059b6108ff366004614980565b611f00565b34801561090f575f80fd5b50601754610551906001600160a01b031681565b34801561092e575f80fd5b5061059b61093d366004614c23565b611f4c565b34801561094d575f80fd5b5061043961095c366004614c23565b6001600160a01b03165f9081526005602052604090205490565b348015610981575f80fd5b5061059b611fa9565b348015610995575f80fd5b5061059b6109a4366004614ed9565b611fbc565b3480156109b4575f80fd5b506109ca6109c3366004614d9a565b5f92915050565b6040516001600160401b039091168152602001610443565b3480156109ed575f80fd5b506006610710565b348015610a00575f80fd5b5061043961203b565b348015610a14575f80fd5b505f546001600160a01b0316610551565b348015610a30575f80fd5b506104b961205d565b348015610a44575f80fd5b506104397f000000000000000000000000000000000000000000000000000000000000000081565b348015610a77575f80fd5b505f61046b565b348015610a89575f80fd5b5061046b610a98366004614980565b61206c565b348015610aa8575f80fd5b5061046b610ab7366004614f21565b600a60209081525f928352604080842090915290825290205460ff1681565b348015610ae1575f80fd5b50600254610551906001600160a01b031681565b348015610b00575f80fd5b5060115461046b90600160a01b900460ff1681565b348015610b20575f80fd5b5061059b610b2f366004614f8d565b61208b565b348015610b3f575f80fd5b50610439610b4e366004614fcb565b60016020525f908152604090205481565b348015610b6a575f80fd5b506104b9610b79366004614fe4565b6121e7565b61059b610b8c366004614f8d565b61235c565b610ba4610b9f366004615040565b6124dc565b60405161044392919061509d565b348015610bbd575f80fd5b50610439601f5481565b348015610bd2575f80fd5b5061059b610be1366004614c23565b6125d3565b348015610bf1575f80fd5b5061046b610c00366004614c23565b612654565b61059b610c13366004614b89565b6126ca565b348015610c23575f80fd5b5061059b610c32366004614c23565b6126f9565b348015610c42575f80fd5b5061043960105481565b348015610c57575f80fd5b5061059b61274f565b348015610c6b575f80fd5b50601854610439565b348015610c7f575f80fd5b50610439610c8e366004614f21565b6001600160a01b039182165f90815260066020908152604080832093909416825291909152205490565b348015610cc3575f80fd5b5061059b610cd2366004614c23565b612887565b348015610ce2575f80fd5b5061046b610cf13660046150ee565b6128c4565b348015610d01575f80fd5b5061046b610d10366004615131565b612a35565b5f6301ffc9a760e01b6001600160e01b031983161480610d455750630f632fb360e01b6001600160e01b03198316145b92915050565b606060088054610d5a9061514b565b80601f0160208091040260200160405190810160405280929190818152602001828054610d869061514b565b8015610dd15780601f10610da857610100808354040283529160200191610dd1565b820191905f5260205f20905b815481529060010190602001808311610db457829003601f168201915b5050505050905090565b335f908152600b60209081526040808320858452909152812080548391908390610e06908490615191565b90915550506001600160a01b0384165f908152600b6020908152604080832086845290915281208054849290610e3d9084906151a4565b909155505060408051338082526020820185905285926001600160a01b038816927f1b3d7edb2e9c0b0e7c525b20aaaef0f5940d2ed71663c7d39266ecafac72885991015b60405180910390a45060015b9392505050565b5f33610ea2818585612a69565b5060019392505050565b604080518082019091525f80825260208201526060610edc60405180604001604052805f81526020015f81525090565b6040805180820182525f8082526001600160401b03602080840182905284518381529081019094529195509182610f35565b604080518082019091525f815260606020820152815260200190600190039081610f0e5790505b5093505f80610f59604089013560608a0135610f5460208c018c614fcb565b612a7b565b60408051808201909152918252602082015296989597505050505050565b5f60188281548110610f8b57610f8b6151b7565b5f918252602090912001546001600160a01b031692915050565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03163314610ff5576040516391ac5e4f60e01b81523360048201526024015b60405180910390fd5b6020870180359061100f9061100a908a614fcb565b612abe565b1461104d576110216020880188614fcb565b60405163309afaf360e21b815263ffffffff909116600482015260208801356024820152604401610fec565b61105c87878787878787612af9565b50505050505050565b5f61106f33612654565b61108b576040516282b42960e81b815260040160405180910390fd5b601f545f0361109b57505f610e8e565b6040516317b933cd60e11b8152735f4a43d56f7bfecbb4fe2b2f8b13bfb450b5c45590632f72679a906110d990601e908890889088906004016151cb565b602060405180830381865af41580156110f4573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906111189190615238565b9050611126601f5482612d18565b601f5f8282546111369190615191565b90915550509392505050565b5f546001600160a01b03163314158061117257506001600160a01b0381165f908152601d602052604090205460ff165b1561118f576040516282b42960e81b815260040160405180910390fd5b6001600160a01b03165f818152601d60205260408120805460ff19166001908117909155601e805491820181559091527f50bb669a95c7b50b7e8a6f09454034b2b14cf2b85c730dca9a539ca82cb6e3500180546001600160a01b0319169091179055565b5f33611201858285612d2d565b61120c858585612da8565b50600195945050505050565b5f8061122333612654565b61123f576040516282b42960e81b815260040160405180910390fd5b6001600160a01b038481165f908152601c6020526040812054601454909216331461126a575f611277565b6014546001600160a01b03165b9050611284868287612da8565b6001600160a01b0387165f908152601c60205260409020549094506112a99083615191565b925050509250929050565b5f6112be33612654565b6112da576040516282b42960e81b815260040160405180910390fd5b81515f036112fb57604051630b8fc7cd60e21b815260040160405180910390fd5b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031661134257604051631c277d0f60e21b815260040160405180910390fd5b5f61134c83613163565b905060ff811660061461137257604051635087d85f60e01b815260040160405180910390fd5b60405163815d56a960e01b815260ff821660048201526175d8905f90735f4a43d56f7bfecbb4fe2b2f8b13bfb450b5c4559063815d56a9906024015f60405180830381865af41580156113c7573d5f803e3d5ffd5b505050506040513d5f823e601f3d908101601f191682016040526113ee919081019061524f565b90505f6113fd8387845f6131a5565b80519091503410156114215760405162976f7560e21b815260040160405180910390fd5b5f61142f8488858533613283565b825190915034111561147457815133906108fc9061144d9034615191565b6040518115909202915f818181858888f19350505050158015611472573d5f803e3d5ffd5b505b519450505050505b919050565b611489613389565b63ffffffff82165f81815260016020908152604091829020849055815192835282018390527f238399d427b947898edb290f5ff0f9109849b1c3ba196a42e35f00c50a54b98b91015b60405180910390a15050565b335f90815260056020526040902054681b1ae4d6e2ef5000001061151557604051636165515360e11b815260040160405180910390fd5b335f908152601a60205260409020541561154257604051632de0270560e01b815260040160405180910390fd5b601880546001810182557fb13d2d76d1f4b7be834882e410b3e3a8afaf69f83600ae24db354391d2378d2e0180546001600160a01b0319163390811790915590545f918252601a602052604090912055565b604080518082019091525f80825260208201525f6115c260408501356060860135610f546020880188614fcb565b9150505f806115d186846133b5565b90925090506115ee6115e66020880188614fcb565b8383886131a5565b9695505050505050565b5f600d546001146116385760405162461bcd60e51b815260206004820152600a6024820152695245454e5452414e435960b01b6044820152606401610fec565b6002600d555f61164661203b565b6116519060016151a4565b905061165c33612654565b156116755761166c8682876134d4565b84915050611d07565b6001600160a01b0384165f908152601d602052604090205460ff161561172e576040516323b872dd60e01b81526001600160a01b038781166004830152306024830152604482018790528516906323b872dd906064016020604051808303815f875af11580156116e7573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061170b91906152c3565b5084601f5f82825461171d91906151a4565b9091555061166c90508682876134d4565b601754604051638340f54960e01b81526001600160a01b0388811660048301528681166024830152604482018890525f921690638340f549906064016020604051808303815f875af1158015611786573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906117aa9190615238565b601154909150600160a01b900460ff161580156117d457506015546001600160a01b038681169116145b15611ac057601754604080516359b44ca960e11b815290515f926001600160a01b03169163b3689952916004808301926101c0929190829003018187875af1158015611822573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061184691906152de565b9050685220d9392f9ccc0000816001602002015110611abe57601754604080516361c75c5960e01b815290515f926001600160a01b0316916361c75c5991600480830192869291908290030181865afa1580156118a5573d5f803e3d5ffd5b505050506040513d5f823e601f3d908101601f191682016040526118cc919081019061537c565b6017546015546040516301c82d6160e21b8152306004820152635a4d10c0602482018190526001600160a01b0392831660448301525f6064830152939450911690630720b584906084016020604051808303815f875af1158015611932573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906119569190615238565b6015546013549192506001600160a01b039081169163a9059cbb911661198164e8d4a510008561540b565b6040516001600160e01b031960e085901b1681526001600160a01b03909216600483015260248201526044016020604051808303815f875af11580156119c9573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906119ed91906152c3565b50601354604051631220ca0560e31b81526001600160a01b0390911690639106502890611a1e90859060040161542a565b5f604051808303815f87803b158015611a35575f80fd5b505af1158015611a47573d5f803e3d5ffd5b505060165460405163539a6ec960e11b81526001600160a01b03909116925063a734dd929150611a7b90859060040161542a565b5f604051808303815f87803b158015611a92575f80fd5b505af1158015611aa4573d5f803e3d5ffd5b50506011805460ff60a01b1916600160a01b179055505050505b505b5f856001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015611afd573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611b219190615475565b60ff1690505f611b3186856135a5565b90505f600c611b408684615191565b118015611b4d5750601882105b8015611b64575069be951906eba2aa800000600f54105b9050735f4a43d56f7bfecbb4fe2b2f8b13bfb450b5c45563955bc93a85858a89611b8c61203b565b600f5460175f9054906101000a90046001600160a01b03166001600160a01b031663949b9c696040518163ffffffff1660e01b8152600401602060405180830381865afa158015611bdf573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611c039190615238565b6040516001600160e01b031960e08a901b1681526004810197909752602487019590955260448601939093526064850191909152608484015260a483015260c482015283151560e4820152610104016040805180830381865af4158015611c6c573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611c909190615495565b90965091508015611cf65785600f5f828254611cac91906151a4565b90915550506001600160a01b038a165f908152601c602052604081208054889290611cd89084906151a4565b925050819055508560105f828254611cf091906151a4565b90915550505b611d018a83886134d4565b50505050505b6001600d55949350505050565b335f818152600c602090815260408083206001600160a01b03881680855290835281842087855290925280832085905551919285927fb3fd5071835887567a0671151121894ddccc2842f1d10bedad13e0d17cace9a790610e829087815260200190565b600360209081525f928352604080842090915290825290208054611d9b9061514b565b80601f0160208091040260200160405190810160405280929190818152602001828054611dc79061514b565b8015611e125780601f10611de957610100808354040283529160200191611e12565b820191905f5260205f20905b815481529060010190602001808311611df557829003601f168201915b505050505081565b335f818152600a602090815260408083206001600160a01b038716808552908352818420805460ff191687151590811790915591519182529293917fceb576d9f15e4e200fdb5096d64d5dfd667e16def20c1eefd14256d8e3faa267910160405180910390a350600192915050565b611e9233612654565b611eae576040516282b42960e81b815260040160405180910390fd5b6001600160a01b0382165f90815260196020526040902054611ed09082612d18565b6001600160a01b0383165f9081526019602052604081208054909190611ef7908490615191565b90915550505050565b611f0933612654565b611f25576040516282b42960e81b815260040160405180910390fd5b6001600160a01b0382165f9081526019602052604081208054839290611ef79084906151a4565b611f54613389565b600480546001600160a01b0319166001600160a01b0383169081179091556040519081527ff0be4f1e87349231d80c36b33f9e8639658eeaf474014dee15a3e6a4d4414197906020015b60405180910390a150565b611fb1613389565b611fba5f6135b3565b565b5f546001600160a01b031633141580611fdf57506016546001600160a01b031615155b15611ffc576040516282b42960e81b815260040160405180910390fd5b601680546001600160a01b039485166001600160a01b031991821617909155601180549385169382169390931790925560148054919093169116179055565b5f6224ed20600e544261204e9190615191565b612058919061540b565b905090565b606060098054610d5a9061514b565b5f612078338484612da8565b8214612082575f80fd5b50600192915050565b612093613389565b5f5b818110156121b5576120d78383838181106120b2576120b26151b7565b90506020028101906120c491906154b7565b6120d29060408101906154d5565b613602565b8282828181106120e9576120e96151b7565b90506020028101906120fb91906154b7565b6121099060408101906154d5565b60035f86868681811061211e5761211e6151b7565b905060200281019061213091906154b7565b61213e906020810190614fcb565b63ffffffff1663ffffffff1681526020019081526020015f205f86868681811061216a5761216a6151b7565b905060200281019061217c91906154b7565b61218d906040810190602001615517565b61ffff16815260208101919091526040015f20916121ac919083615574565b50600101612095565b507fbe4864a8e820971c0247f5992e2da559595f7bf076a21cb5928d443d2a13b67482826040516114d2929190615655565b63ffffffff84165f90815260036020908152604080832061ffff8716845290915281208054606092919061221a9061514b565b80601f01602080910402602001604051908101604052809291908181526020018280546122469061514b565b80156122915780601f1061226857610100808354040283529160200191612291565b820191905f5260205f20905b81548152906001019060200180831161227457829003601f168201915b5050505050905080515f036122df5783838080601f0160208091040260200160405190810160405280939291908181526020018383808284375f920191909152509294506123549350505050565b5f8390036122ee579050612354565b60028310612337576123008484613602565b8061230e846002818861573a565b60405160200161232093929190615778565b604051602081830303815290604052915050612354565b8383604051639a6d49cd60e01b8152600401610fec929190615796565b949350505050565b5f5b8181101561245f5736838383818110612379576123796151b7565b905060200281019061238b91906157a9565b90506123bd61239d6020830183614fcb565b602083013563ffffffff919091165f908152600160205260409020541490565b6123c75750612457565b3063d045a0dc60c08301358360a08101356123e66101008301836154d5565b6123f7610100890160e08a01614c23565b6124056101208a018a6154d5565b6040518963ffffffff1660e01b815260040161242797969594939291906157d2565b5f604051808303818588803b15801561243e575f80fd5b505af1158015612450573d5f803e3d5ffd5b5050505050505b60010161235e565b50336001600160a01b0316638e9e70996040518163ffffffff1660e01b81526004015f60405180830381865afa15801561249b573d5f803e3d5ffd5b505050506040513d5f823e601f3d908101601f191682016040526124c2919081019061524f565b604051638351eea760e01b8152600401610fec91906149ff565b6124e46148d5565b604080518082019091525f80825260208201525f806125186040880135606089013561251360208b018b614fcb565b613643565b915091505f8061252889846133b5565b909250905061255461253d60208b018b614fcb565b838361254e368d90038d018d615857565b8b613283565b60408051808201909152858152602080820186905282519298509096503391907f85496b760a4b7f8d66384b9df21b381f5d1b1e79f229a47aaf4c232edc2fe59a906125a2908d018d614fcb565b6040805163ffffffff909216825260208201899052810187905260600160405180910390a350505050935093915050565b6125db613389565b60405163ca5eb5e160e01b81526001600160a01b0382811660048301527f0000000000000000000000000000000000000000000000000000000000000000169063ca5eb5e1906024015f604051808303815f87803b15801561263b575f80fd5b505af115801561264d573d5f803e3d5ffd5b5050505050565b6017545f906001600160a01b038381169116148061267f57506012546001600160a01b038381169116145b8061269757506016546001600160a01b038381169116145b806126af57506014546001600160a01b038381169116145b80610d455750506011546001600160a01b0390811691161490565b3330146126ea5760405163029a949d60e31b815260040160405180910390fd5b61105c8787878787878761104d565b612701613389565b600280546001600160a01b0319166001600160a01b0383169081179091556040519081527fd48d879cef83a1c0bdda516f27b13ddb1b3f8bbac1c9e1511bb2a659c242776090602001611f9e565b335f908152601a60205260408120549081900361277f5760405163dae3bbdf60e01b815260040160405180910390fd5b335f90815260196020526040902054156127ac576040516303cb96db60e21b815260040160405180910390fd5b601854818114612842575f60186127c4600184615191565b815481106127d4576127d46151b7565b5f918252602090912001546001600160a01b031690508060186127f8600186615191565b81548110612808576128086151b7565b5f91825260208083209190910180546001600160a01b0319166001600160a01b03948516179055929091168152601a909152604090208290555b601880548061285357612853615888565b5f828152602080822083015f1990810180546001600160a01b0319169055909201909255338252601a905260408120555050565b61288f613389565b6001600160a01b0381166128b857604051631e4fbdf760e01b81525f6004820152602401610fec565b6128c1816135b3565b50565b5f336001600160a01b0386161480159061290157506001600160a01b0385165f908152600a6020908152604080832033845290915290205460ff16155b15612971576001600160a01b0385165f908152600c6020908152604080832033845282528083208684529091529020545f19811461296f576129438382615191565b6001600160a01b0387165f908152600c6020908152604080832033845282528083208884529091529020555b505b6001600160a01b0385165f908152600b60209081526040808320868452909152812080548492906129a3908490615191565b90915550506001600160a01b0384165f908152600b60209081526040808320868452909152812080548492906129da9084906151a4565b9091555050604080513381526020810184905284916001600160a01b0380881692908916917f1b3d7edb2e9c0b0e7c525b20aaaef0f5940d2ed71663c7d39266ecafac728859910160405180910390a4506001949350505050565b5f602082018035906001908390612a4c9086614fcb565b63ffffffff16815260208101919091526040015f20541492915050565b612a768383836001613904565b505050565b5f80612a86856139d6565b915081905083811015612ab6576040516371c4efed60e01b81526004810182905260248101859052604401610fec565b935093915050565b63ffffffff81165f9081526001602052604081205480610d455760405163f6ff4fb760e01b815263ffffffff84166004820152602401610fec565b60015f612b0960208a018a614fcb565b63ffffffff1663ffffffff1681526020019081526020015f2054876020013514612b45576040516282b42960e81b815260040160405180910390fd5b5f612b508686613a0c565b90505f612b5c82613a2e565b90505f612b698888613a62565b90505f612b7c612b798a8a613aac565b90565b90505f612b8883613163565b905060041960ff821601612c9a576175d8612ba660208e018e614fcb565b63ffffffff1614612bca576040516310dfc03360e01b815260040160405180910390fd5b601154604051620b465d60e71b81526001600160a01b03909116906305a32e8090612bf99086906004016149ff565b5f604051808303815f87803b158015612c10575f80fd5b505af1158015612c22573d5f803e3d5ffd5b5050505060115f9054906101000a90046001600160a01b03166001600160a01b03168b7fefed6d3500546b29533b128a29e3a94d70788727f0507505ac12eaf2e578fd9c8e5f016020810190612c789190614fcb565b6040805163ffffffff90921682525f60208301520160405180910390a3612d0a565b60061960ff821601612cf6576175d8612cb660208e018e614fcb565b63ffffffff1614612cda576040516310dfc03360e01b815260040160405180910390fd5b612cf18b612ceb60208f018f614fcb565b85613ac3565b612d0a565b83612d018484613bc8565b14612d0a575f80fd5b505050505050505050505050565b5f818310612d265781610e8e565b5090919050565b6001600160a01b038381165f908152600660209081526040808320938616835292905220545f198114612da25781811015612d9457604051637dc7a0d960e11b81526001600160a01b03841660048201526024810182905260448101839052606401610fec565b612da284848484035f613904565b50505050565b6001600160a01b0383165f9081526019602090815260408083205460059092528220548391612dd691615191565b1015612df5576040516313424e8d60e31b815260040160405180910390fd5b6001600160a01b0384165f9081526020805260408120612e1490613cea565b90506001600160a01b038416155f818015612e3d57506016546001600160a01b03888116911614155b612e545760018351612e4f9190615191565b612ed0565b600e5460405163c6dbdf1d60e01b8152735f4a43d56f7bfecbb4fe2b2f8b13bfb450b5c4559163c6dbdf1d91612e9191879142919060040161589c565b602060405180830381865af4158015612eac573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612ed09190615238565b90505b5f85118015612ee257505f8112155b15613073575f838281518110612efa57612efa6151b7565b6020908102919091018101516001600160a01b038a165f908152600b835260408082208383529093529190912054909150801561305f57612f3b8782612d18565b6001600160a01b038a165f908152600b60209081526040808320868452909152812080549293508392909190612f72908490615191565b90915550849050612fda576001600160a01b0388165f9081526020805260409020612f9d9083613d43565b6001600160a01b0388165f908152600b6020908152604080832085845290915281208054839290612fcf9084906151a4565b90915550612ffd9050565b5f828152601b602052604081208054839290612ff7908490615191565b90915550505b6001600160a01b0389165f908152600b602090815260408083208584529091528120549003613046576001600160a01b0389165f90815260208052604090206130469083613e34565b6130508188615191565b965061305c81876151a4565b95505b61306a6001846158ea565b92505050612ed3565b831561315957613084878786613f47565b6001600160a01b0387165f908152601c602052604090205415613159576001600160a01b0387165f908152601c60205260408120546130c4908690612d18565b6001600160a01b0389165f908152601c60205260408120805492935083929091906130f0908490615191565b90915550506001600160a01b03871661312a5761310f60105482612d18565b60105f82825461311f9190615191565b909155506131579050565b6001600160a01b0387165f908152601c6020526040812080548392906131519084906151a4565b90915550505b505b5050509392505050565b5f81515f0361318557604051638d0242c960e01b815260040160405180910390fd5b815f81518110613197576131976151b7565b016020015160f81c92915050565b604080518082019091525f80825260208201527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663ddc28c586040518060a001604052808863ffffffff16815260200161320789612abe565b8152602001878152602001868152602001851515815250306040518363ffffffff1660e01b815260040161323c929190615910565b6040805180830381865afa158015613256573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061327a91906159b7565b95945050505050565b61328b6148d5565b5f613298845f015161406d565b6020850151909150156132b2576132b28460200151614094565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316632637a450826040518060a001604052808b63ffffffff1681526020016133028c612abe565b81526020018a81526020018981526020015f8960200151111515815250866040518463ffffffff1660e01b815260040161333d929190615910565b60806040518083038185885af1158015613359573d5f803e3d5ffd5b50505050506040513d601f19601f8201168201806040525081019061337e91906159d1565b979650505050505050565b5f546001600160a01b03163314611fba5760405163118cdaa760e01b8152336004820152602401610fec565b6060805f61341085602001356133ca86614177565b6133d760a08901896154d5565b8080601f0160208091040260200160405190810160405280939291908181526020018383808284375f920191909152506141a292505050565b90935090505f81613422576001613425565b60025b90506134456134376020880188614fcb565b82610b7960808a018a6154d5565b6004549093506001600160a01b0316156134cb576004805460405163043a78eb60e01b81526001600160a01b039091169163043a78eb9161348a918891889101615a38565b602060405180830381865afa1580156134a5573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906134c991906152c3565b505b50509250929050565b5f828152601b6020526040812080548392906134f19084906151a4565b90915550506001600160a01b0383165f90815260208052604090206135169083613d43565b6135215f8483613f47565b6001600160a01b0383165f908152600b60209081526040808320858452909152812080548392906135539084906151a4565b9091555050604080513381526020810183905283916001600160a01b038616915f917f1b3d7edb2e9c0b0e7c525b20aaaef0f5940d2ed71663c7d39266ecafac728859910160405180910390a4505050565b5f818311612d265781610e8e565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b5f613610600282848661573a565b61361991615a5c565b60f01c905060038114612a76578282604051639a6d49cd60e01b8152600401610fec929190615796565b5f80613650858585612a7b565b6040516323a9655f60e21b815291935091505f90735f4a43d56f7bfecbb4fe2b2f8b13bfb450b5c45590638ea5957c906136909084903690600401615796565b5f60405180830381865af41580156136aa573d5f803e3d5ffd5b505050506040513d5f823e601f3d908101601f191682016040526136d1919081019061524f565b905080515f036136f457604051630b8fc7cd60e21b815260040160405180910390fd5b5f805f8380602001905181019061370b9190615af7565b915091508051825114158061371f57508151155b1561373d5760405163a121188760e01b815260040160405180910390fd5b5f306001600160a01b031663963efcaa6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561377a573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061379e9190615238565b90505f5b83518110156138cb575f8482815181106137be576137be6151b7565b602002602001015190505f838584815181106137dc576137dc6151b7565b60200260200101516137ee9190615b5a565b335f908152600b6020908152604080832086845290915290205490915081111561382b57604051631e9acf1760e31b815260040160405180910390fd5b335f908152600b6020908152604080832085845290915281208054839290613854908490615191565b90915550505f828152601b602052604081208054839290613876908490615191565b9091555050335f908152600b6020908152604080832085845290915281205490036138b257335f90815260208052604090206138b29083613e34565b6138bc81886151a4565b965050508060010190506137a2565b508684146138ec57604051631d6e22b960e01b815260040160405180910390fd5b6138f7335f86613f47565b5050505050935093915050565b6001600160a01b03841661392d5760405163e602df0560e01b81525f6004820152602401610fec565b6001600160a01b03831661395657604051634a1406b160e11b81525f6004820152602401610fec565b6001600160a01b038085165f9081526006602090815260408083209387168352929052208290558015612da257826001600160a01b0316846001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040516139c891815260200190565b60405180910390a350505050565b5f7f0000000000000000000000000000000000000000000000000000000000000000613a02818461540b565b610d459190615b5a565b5f613a1b60286020848661573a565b613a2491615b71565b60c01c9392505050565b5f610d457f00000000000000000000000000000000000000000000000000000000000000006001600160401b038416615b5a565b6060613a71826028818661573a565b8080601f0160208091040260200160405190810160405280939291908181526020018383808284375f92019190915250929695505050505050565b5f613aba602082848661573a565b610e8e91615ba7565b5f80613ace8361421c565b915091505f613adc8261434c565b601454909150613b08906001600160a01b0316613af761203b565b613b029060016151a4565b836134d4565b60145460405163557502cb60e11b81526001600160401b0385166004820152602481018390526001600160a01b039091169063aaea0596906044015f604051808303815f87803b158015613b5a575f80fd5b505af1158015613b6c573d5f803e3d5ffd5b50506014546040805163ffffffff8a168152602081018690526001600160a01b0390921693508992507fefed6d3500546b29533b128a29e3a94d70788727f0507505ac12eaf2e578fd9c910160405180910390a3505050505050565b5f805f84806020019051810190613bdf9190615af7565b9150915080518251141580613bf357508151155b15613c115760405163a121188760e01b815260040160405180910390fd5b5f306001600160a01b031663963efcaa6040518163ffffffff1660e01b8152600401602060405180830381865afa158015613c4e573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190613c729190615238565b90505f5b8351811015613ce0575f82848381518110613c9357613c936151b7565b6020026020010151613ca59190615b5a565b9050613ccb87868481518110613cbd57613cbd6151b7565b6020026020010151836134d4565b613cd581876151a4565b955050600101613c76565b5050505092915050565b6060815f01805480602002602001604051908101604052809291908181526020018280548015613d3757602002820191905f5260205f20905b815481526020019060010190808311613d23575b50505050509050919050565b5f81815260018301602052604090205460ff1615613d5f575050565b5f818152600183810160205260408220805460ff19169091179055613d848383614365565b508354600181810186555f8681526020812090920182905585549293509091613dad9190615191565b90505b81811115613e0e5783613dc4600183615191565b81548110613dd457613dd46151b7565b905f5260205f200154845f018281548110613df157613df16151b7565b5f9182526020909120015580613e0681615bc4565b915050613db0565b5081835f018281548110613e2457613e246151b7565b5f91825260209091200155505050565b5f81815260018301602052604090205460ff16613e8a5760405162461bcd60e51b815260206004820152601460248201527315985b1d5948191bd95cc81b9bdd08195e1a5cdd60621b6044820152606401610fec565b5f613e958383614365565b50835490915081108015613ec4575081835f018281548110613eb957613eb96151b7565b905f5260205f200154145b613f025760405162461bcd60e51b815260206004820152600f60248201526e15985b1d59481b9bdd08199bdd5b99608a1b6044820152606401610fec565b5f19835f018281548110613f1857613f186151b7565b5f9182526020808320909101929092558381526001850190915260409020805460ff19169055612a7683614418565b6001600160a01b038316613f71578060075f828254613f6691906151a4565b90915550613fe19050565b6001600160a01b0383165f9081526005602052604090205481811015613fc35760405163391434e360e21b81526001600160a01b03851660048201526024810182905260448101839052606401610fec565b6001600160a01b0384165f9081526005602052604090209082900390555b6001600160a01b038216613ffd5760078054829003905561401b565b6001600160a01b0382165f9081526005602052604090208054820190555b816001600160a01b0316836001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8360405161406091815260200190565b60405180910390a3505050565b5f813414614090576040516304fb820960e51b8152346004820152602401610fec565b5090565b5f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663e4fe1d946040518163ffffffff1660e01b8152600401602060405180830381865afa1580156140f1573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906141159190615bd9565b90506001600160a01b03811661413e576040516329b99a9560e11b815260040160405180910390fd5b6141736001600160a01b038216337f000000000000000000000000000000000000000000000000000000000000000085614582565b5050565b5f610d457f00000000000000000000000000000000000000000000000000000000000000008361540b565b80516060901515806141eb5784846040516020016141d792919091825260c01b6001600160c01b031916602082015260280190565b604051602081830303815290604052614212565b848433856040516020016142029493929190615bf4565b6040516020818303038152906040525b9150935093915050565b5f8060118351101561424157604051638d0242c960e01b815260040160405180910390fd5b600760ff16835f81518110614258576142586151b7565b016020015160f81c1461427e57604051635b60892f60e01b815260040160405180910390fd5b6142898360016145dc565b91506142968360096145dc565b9050816001600160401b03165f036142c05760405162320bd360e51b815260040160405180910390fd5b806001600160401b03165f036142e95760405163162908e360e11b815260040160405180910390fd5b64e8d4a51000816001600160401b031611156143475760405162461bcd60e51b815260206004820152601c60248201527f436f6d70656e736174696f6e2065786365656473206d6178696d756d000000006044820152606401610fec565b915091565b5f610d456001600160401b03831664e8d4a51000615b5a565b81545f90819081905b8082101561440a575f60026143838484615191565b61438d919061540b565b61439790846151a4565b905085875f0182815481106143ae576143ae6151b7565b905f5260205f200154036143ca57935060019250614411915050565b85875f0182815481106143df576143df6151b7565b905f5260205f2001541015614400576143f98160016151a4565b9250614404565b8091505b5061436e565b5091505f90505b9250929050565b80545f9081906001600160401b0381111561443557614435614c7c565b60405190808252806020026020018201604052801561445e578160200160208202803683370190505b5090505f5b83548110156144e0575f19845f018281548110614482576144826151b7565b905f5260205f200154146144d857835f0181815481106144a4576144a46151b7565b905f5260205f2001548284815181106144bf576144bf6151b7565b6020908102919091010152826144d481615c20565b9350505b600101614463565b50816001600160401b038111156144f9576144f9614c7c565b604051908082528060200260200182016040528015614522578160200160208202803683370190505b508051614536918591602090910190614918565b505f5b82811015612da257818181518110614553576145536151b7565b6020026020010151845f01828154811061456f5761456f6151b7565b5f91825260209091200155600101614539565b604080516001600160a01b0385811660248301528416604482015260648082018490528251808303909101815260849091019091526020810180516001600160e01b03166323b872dd60e01b179052612da2908590614781565b5f6145e88260086151a4565b835110156146385760405162461bcd60e51b815260206004820152601c60248201527f496e73756666696369656e74206461746120666f722075696e743634000000006044820152606401610fec565b6038836146468460076151a4565b81518110614656576146566151b7565b016020015160f81c901b60308461466e8560066151a4565b8151811061467e5761467e6151b7565b016020015160f81c901b6028856146968660056151a4565b815181106146a6576146a66151b7565b0160209081015160f81c90911b90866146c08760046151a4565b815181106146d0576146d06151b7565b016020015160f81c901b6018876146e88860036151a4565b815181106146f8576146f86151b7565b016020015160f81c901b6010886147108960026151a4565b81518110614720576147206151b7565b016020015160f81c901b6008896147388a60016151a4565b81518110614748576147486151b7565b01602001518a5160f89190911c90911b908a908a90811061476b5761476b6151b7565b016020015160f81c171717171717179392505050565b5f6147956001600160a01b038416836147e2565b905080515f141580156147b95750808060200190518101906147b791906152c3565b155b15612a7657604051635274afe760e01b81526001600160a01b0384166004820152602401610fec565b6060610e8e83835f845f80856001600160a01b031684866040516148069190615c38565b5f6040518083038185875af1925050503d805f8114614840576040519150601f19603f3d011682016040523d82523d5f602084013e614845565b606091505b50915091506115ee86838360608261486557614860826148ac565b610e8e565b815115801561487c57506001600160a01b0384163b155b156148a557604051639996b31560e01b81526001600160a01b0385166004820152602401610fec565b5080610e8e565b8051156148bc5780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b60405180606001604052805f80191681526020015f6001600160401b0316815260200161491360405180604001604052805f81526020015f81525090565b905290565b828054828255905f5260205f20908101928215614951579160200282015b82811115614951578251825591602001919060010190614936565b506140909291505b80821115614090575f8155600101614959565b6001600160a01b03811681146128c1575f80fd5b5f8060408385031215614991575f80fd5b823561499c8161496c565b946020939093013593505050565b5f602082840312156149ba575f80fd5b81356001600160e01b031981168114610e8e575f80fd5b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b602081525f610e8e60208301846149d1565b5f805f60608486031215614a23575f80fd5b8335614a2e8161496c565b95602085013595506040909401359392505050565b5f60e08284031215614a53575f80fd5b50919050565b5f60208284031215614a69575f80fd5b81356001600160401b03811115614a7e575f80fd5b61235484828501614a43565b83518152602080850151908201525f60a0820160a0604084015280855180835260c08501915060c08160051b8601019250602087015f5b82811015614b095760bf198786030184528151805186526020810151905060406020870152614af360408701826149d1565b9550506020938401939190910190600101614ac1565b5050855160608601525050602084015160808401529050612354565b5f60208284031215614b35575f80fd5b5035919050565b5f60608284031215614a53575f80fd5b5f8083601f840112614b5c575f80fd5b5081356001600160401b03811115614b72575f80fd5b602083019150836020828501011115614411575f80fd5b5f805f805f805f60e0888a031215614b9f575f80fd5b614ba98989614b3c565b96506060880135955060808801356001600160401b03811115614bca575f80fd5b614bd68a828b01614b4c565b90965094505060a0880135614bea8161496c565b925060c08801356001600160401b03811115614c04575f80fd5b614c108a828b01614b4c565b989b979a50959850939692959293505050565b5f60208284031215614c33575f80fd5b8135610e8e8161496c565b5f805f60608486031215614c50575f80fd5b8335614c5b8161496c565b92506020840135614c6b8161496c565b929592945050506040919091013590565b634e487b7160e01b5f52604160045260245ffd5b604080519081016001600160401b0381118282101715614cb257614cb2614c7c565b60405290565b604051601f8201601f191681016001600160401b0381118282101715614ce057614ce0614c7c565b604052919050565b5f6001600160401b03821115614d0057614d00614c7c565b50601f01601f191660200190565b5f60208284031215614d1e575f80fd5b81356001600160401b03811115614d33575f80fd5b8201601f81018413614d43575f80fd5b8035614d56614d5182614ce8565b614cb8565b818152856020838501011115614d6a575f80fd5b816020840160208301375f91810160200191909152949350505050565b803563ffffffff8116811461147c575f80fd5b5f8060408385031215614dab575f80fd5b61499c83614d87565b80151581146128c1575f80fd5b5f8060408385031215614dd2575f80fd5b82356001600160401b03811115614de7575f80fd5b614df385828601614a43565b9250506020830135614e0481614db4565b809150509250929050565b815181526020808301519082015260408101610d45565b5f805f8060808587031215614e39575f80fd5b8435614e448161496c565b9350602085013592506040850135614e5b8161496c565b9396929550929360600135925050565b803561ffff8116811461147c575f80fd5b5f8060408385031215614e8d575f80fd5b614e9683614d87565b9150614ea460208401614e6b565b90509250929050565b5f8060408385031215614ebe575f80fd5b8235614ec98161496c565b91506020830135614e0481614db4565b5f805f60608486031215614eeb575f80fd5b8335614ef68161496c565b92506020840135614f068161496c565b91506040840135614f168161496c565b809150509250925092565b5f8060408385031215614f32575f80fd5b8235614f3d8161496c565b91506020830135614e048161496c565b5f8083601f840112614f5d575f80fd5b5081356001600160401b03811115614f73575f80fd5b6020830191508360208260051b8501011115614411575f80fd5b5f8060208385031215614f9e575f80fd5b82356001600160401b03811115614fb3575f80fd5b614fbf85828601614f4d565b90969095509350505050565b5f60208284031215614fdb575f80fd5b610e8e82614d87565b5f805f8060608587031215614ff7575f80fd5b61500085614d87565b935061500e60208601614e6b565b925060408501356001600160401b03811115615028575f80fd5b61503487828801614b4c565b95989497509550505050565b5f805f8385036080811215615053575f80fd5b84356001600160401b03811115615068575f80fd5b61507487828801614a43565b9450506040601f1982011215615088575f80fd5b506020840191506060840135614f168161496c565b5f60c082019050835182526001600160401b03602085015116602083015260408401516150d7604084018280518252602090810151910152565b5082516080830152602083015160a0830152610e8e565b5f805f8060808587031215615101575f80fd5b843561510c8161496c565b9350602085013561511c8161496c565b93969395505050506040820135916060013590565b5f60608284031215615141575f80fd5b610e8e8383614b3c565b600181811c9082168061515f57607f821691505b602082108103614a5357634e487b7160e01b5f52602260045260245ffd5b634e487b7160e01b5f52601160045260245ffd5b81810381811115610d4557610d4561517d565b80820180821115610d4557610d4561517d565b634e487b7160e01b5f52603260045260245ffd5b608080825285549082018190525f86815260208120909160a0840190835b818110156152105783546001600160a01b03168352600193840193602090930192016151e9565b50506001600160a01b0396909616602084015250506040810192909252606090910152919050565b5f60208284031215615248575f80fd5b5051919050565b5f6020828403121561525f575f80fd5b81516001600160401b03811115615274575f80fd5b8201601f81018413615284575f80fd5b8051615292614d5182614ce8565b8181528560208385010111156152a6575f80fd5b8160208401602083015e5f91810160200191909152949350505050565b5f602082840312156152d3575f80fd5b8151610e8e81614db4565b5f6101c082840312156152ef575f80fd5b82601f8301126152fd575f80fd5b6040516101c081016001600160401b038111828210171561532057615320614c7c565b604052806101c0840185811115615335575f80fd5b845b8181101561534f578051835260209283019201615337565b509195945050505050565b5f6001600160401b0382111561537257615372614c7c565b5060051b60200190565b5f6020828403121561538c575f80fd5b81516001600160401b038111156153a1575f80fd5b8201601f810184136153b1575f80fd5b80516153bf614d518261535a565b8082825260208201915060208360051b8501019250868311156153e0575f80fd5b6020840193505b828410156115ee5783516153fa8161496c565b8252602093840193909101906153e7565b5f8261542557634e487b7160e01b5f52601260045260245ffd5b500490565b602080825282518282018190525f918401906040840190835b8181101561546a5783516001600160a01b0316835260209384019390920191600101615443565b509095945050505050565b5f60208284031215615485575f80fd5b815160ff81168114610e8e575f80fd5b5f80604083850312156154a6575f80fd5b505080516020909101519092909150565b5f8235605e198336030181126154cb575f80fd5b9190910192915050565b5f808335601e198436030181126154ea575f80fd5b8301803591506001600160401b03821115615503575f80fd5b602001915036819003821315614411575f80fd5b5f60208284031215615527575f80fd5b610e8e82614e6b565b601f821115612a7657805f5260205f20601f840160051c810160208510156155555750805b601f840160051c820191505b8181101561264d575f8155600101615561565b6001600160401b0383111561558b5761558b614c7c565b61559f83615599835461514b565b83615530565b5f601f8411600181146155d0575f85156155b95750838201355b5f19600387901b1c1916600186901b17835561264d565b5f83815260208120601f198716915b828110156155ff57868501358255602094850194600190920191016155df565b508682101561561b575f1960f88860031b161c19848701351681555b505060018560011b0183555050505050565b81835281816020850137505f828201602090810191909152601f909101601f19169091010190565b602080825281018290525f6040600584901b830181019083018583605e1936839003015b8782101561572d57868503603f190184528235818112615697575f80fd5b890163ffffffff6156a782614d87565b16865261ffff6156b960208301614e6b565b1660208701526040810135601e198236030181126156d5575f80fd5b016020810190356001600160401b038111156156ef575f80fd5b8036038213156156fd575f80fd5b6060604088015261571260608801828461562d565b96505050602083019250602084019350600182019150615679565b5092979650505050505050565b5f8085851115615748575f80fd5b83861115615754575f80fd5b5050820193919092039150565b5f81518060208401855e5f93019283525090919050565b5f6157838286615761565b838582375f930192835250909392505050565b602081525f61235460208301848661562d565b5f823561013e198336030181126154cb575f80fd5b6001600160401b03811681146128c1575f80fd5b63ffffffff6157e089614d87565b168152602088810135908201525f60408901356157fc816157be565b6001600160401b03811660408401525087606083015260e0608083015261582760e08301878961562d565b6001600160a01b03861660a084015282810360c084015261584981858761562d565b9a9950505050505050505050565b5f6040828403128015615868575f80fd5b50615871614c90565b823581526020928301359281019290925250919050565b634e487b7160e01b5f52603160045260245ffd5b606080825284519082018190525f9060208601906080840190835b818110156158d55783518352602093840193909201916001016158b7565b50506020840195909552505060400152919050565b8181035f8312801583831316838312821617156159095761590961517d565b5092915050565b6040815263ffffffff8351166040820152602083015160608201525f604084015160a0608084015261594560e08401826149d1565b90506060850151603f198483030160a085015261596282826149d1565b60809690960151151560c08501525050506001600160a01b039190911660209091015290565b5f60408284031215615998575f80fd5b6159a0614c90565b825181526020928301519281019290925250919050565b5f604082840312156159c7575f80fd5b610e8e8383615988565b5f60808284031280156159e2575f80fd5b50604051606081016001600160401b0381118282101715615a0557615a05614c7c565b604052825181526020830151615a1a816157be565b6020820152615a2c8460408501615988565b60408201529392505050565b604081525f615a4a60408301856149d1565b828103602084015261327a81856149d1565b80356001600160f01b03198116906002841015615909576001600160f01b031960029490940360031b84901b1690921692915050565b5f82601f830112615aa1575f80fd5b8151615aaf614d518261535a565b8082825260208201915060208360051b860101925085831115615ad0575f80fd5b602085015b83811015615aed578051835260209283019201615ad5565b5095945050505050565b5f8060408385031215615b08575f80fd5b82516001600160401b03811115615b1d575f80fd5b615b2985828601615a92565b92505060208301516001600160401b03811115615b44575f80fd5b615b5085828601615a92565b9150509250929050565b8082028115828204841417610d4557610d4561517d565b80356001600160c01b03198116906008841015615909576001600160c01b031960089490940360031b84901b1690921692915050565b80356020831015610d45575f19602084900360031b1b1692915050565b5f81615bd257615bd261517d565b505f190190565b5f60208284031215615be9575f80fd5b8151610e8e8161496c565b8481526001600160401b0360c01b8460c01b1660208201528260288201525f6115ee6048830184615761565b5f60018201615c3157615c3161517d565b5060010190565b5f610e8e828461576156000000000000000000000000ba45bf3b4701ae737dd69034f50f3d821240a34600000000000000000000000073ed5341ea060d4cfdec7bcc38e6c83bda1f24a200000000000000000000000099d9050adbb262184ae867a0c9a76eca4e2043c70000000000000000000000003c499c542cef5e3811e1192ce70d8cc03d5c3359
Deployed Bytecode
0x6080604052600436106103ff575f3560e01c80636100d69a11610215578063b98bd0701161011e578063d4243885116100a8578063dd62ed3e11610078578063dd62ed3e14610c74578063f2fde38b14610cb8578063fc0c546a146107d2578063fe99049a14610cd7578063ff7bd03d14610cf6575f80fd5b8063d424388514610c18578063d4b8399214610c37578063d90b466114610c4c578063db2da34e14610c60575f80fd5b8063c7c7f5b3116100ee578063c7c7f5b314610b91578063ca01b5cf14610bb2578063ca5eb5e114610bc7578063cd5d211814610be6578063d045a0dc14610c05575f80fd5b8063b98bd07014610b15578063bb0b6a5314610b34578063bc70b35414610b5f578063bd815db014610b7e575f80fd5b80638da5cb5b1161019f578063a9059cbb1161016f578063a9059cbb14610a7e578063b6363cf214610a9d578063b731ea0a14610ad6578063b777fb7e14610af5578063b92d0eff146107d2575f80fd5b80638da5cb5b14610a0957806395d89b4114610a25578063963efcaa14610a395780639f68b96414610a6c575f80fd5b8063715018a6116101e5578063715018a61461097657806377b8b1c71461098a5780637d25a05e146109a9578063857749b0146109e2578063862a4d47146109f5575f80fd5b80636100d69a146108e5578063696c5ded146109045780636fc1b31e1461092357806370a0823114610942575f80fd5b80631b7fa24b116103175780633c173a4f116102a1578063558a729711610271578063558a729714610803578063598af9e7146108225780635a0dfe4d1461085e5780635cb0fd15146108935780635e280f11146108b2575f80fd5b80633c173a4f14610794578063426a8493146107b357806352ae2879146107d25780635535d461146107e4575f80fd5b8063313ce567116102e7578063313ce5671461070157806331ba5c0f146107225780633400288b1461073557806334c95fba146107545780633b6f743b14610768575f80fd5b80631b7fa24b1461067b5780631f5e13341461069a57806323b872dd146106ae5780632ff42ab6146106cd575f80fd5b806312530fb41161039857806316ce049b1161036857806316ce049b146105ea57806317442b701461060957806318160ddd1461062957806319b2c8111461063d5780631b6b6d231461065c575f80fd5b806312530fb41461056957806313137d6514610588578063134d4f251461059d578063156a0d0f146105c4575f80fd5b8063095bcdb6116103d3578063095bcdb6146104c6578063095ea7b3146104e55780630d35b41514610504578063111ecdad14610532575f80fd5b8062fdd58e1461040357806301ffc9a71461044c5780630455a1a11461047b57806306fdde03146104a5575b5f80fd5b34801561040e575f80fd5b5061043961041d366004614980565b600b60209081525f928352604080842090915290825290205481565b6040519081526020015b60405180910390f35b348015610457575f80fd5b5061046b6104663660046149aa565b610d15565b6040519015158152602001610443565b348015610486575f80fd5b506104906175d881565b60405163ffffffff9091168152602001610443565b3480156104b0575f80fd5b506104b9610d4b565b60405161044391906149ff565b3480156104d1575f80fd5b5061046b6104e0366004614a11565b610ddb565b3480156104f0575f80fd5b5061046b6104ff366004614980565b610e95565b34801561050f575f80fd5b5061052361051e366004614a59565b610eac565b60405161044393929190614a8a565b34801561053d575f80fd5b50600454610551906001600160a01b031681565b6040516001600160a01b039091168152602001610443565b348015610574575f80fd5b50610551610583366004614b25565b610f77565b61059b610596366004614b89565b610fa5565b005b3480156105a8575f80fd5b506105b1600281565b60405161ffff9091168152602001610443565b3480156105cf575f80fd5b506040805162b9270b60e21b81526001602082015201610443565b3480156105f5575f80fd5b50601254610551906001600160a01b031681565b348015610614575f80fd5b50604080516001808252602082015201610443565b348015610634575f80fd5b50600754610439565b348015610648575f80fd5b50610439610657366004614a11565b611065565b348015610667575f80fd5b50601654610551906001600160a01b031681565b348015610686575f80fd5b5061059b610695366004614c23565b611142565b3480156106a5575f80fd5b506105b1600181565b3480156106b9575f80fd5b5061046b6106c8366004614c3e565b6111f4565b3480156106d8575f80fd5b506106ec6106e7366004614980565b611218565b60408051928352602083019190915201610443565b34801561070c575f80fd5b5060125b60405160ff9091168152602001610443565b610439610730366004614d0e565b6112b4565b348015610740575f80fd5b5061059b61074f366004614d9a565b611481565b34801561075f575f80fd5b5061059b6114de565b348015610773575f80fd5b50610787610782366004614dc1565b611594565b6040516104439190614e0f565b34801561079f575f80fd5b506104396107ae366004614e26565b6115f8565b3480156107be575f80fd5b5061046b6107cd366004614a11565b611d14565b3480156107dd575f80fd5b5030610551565b3480156107ef575f80fd5b506104b96107fe366004614e7c565b611d78565b34801561080e575f80fd5b5061046b61081d366004614ead565b611e1a565b34801561082d575f80fd5b5061043961083c366004614c3e565b600c60209081525f938452604080852082529284528284209052825290205481565b348015610869575f80fd5b5061046b610878366004614d9a565b63ffffffff919091165f908152600160205260409020541490565b34801561089e575f80fd5b5061059b6108ad366004614980565b611e89565b3480156108bd575f80fd5b506105517f0000000000000000000000001a44076050125825900e736c501f859c50fe728c81565b3480156108f0575f80fd5b5061059b6108ff366004614980565b611f00565b34801561090f575f80fd5b50601754610551906001600160a01b031681565b34801561092e575f80fd5b5061059b61093d366004614c23565b611f4c565b34801561094d575f80fd5b5061043961095c366004614c23565b6001600160a01b03165f9081526005602052604090205490565b348015610981575f80fd5b5061059b611fa9565b348015610995575f80fd5b5061059b6109a4366004614ed9565b611fbc565b3480156109b4575f80fd5b506109ca6109c3366004614d9a565b5f92915050565b6040516001600160401b039091168152602001610443565b3480156109ed575f80fd5b506006610710565b348015610a00575f80fd5b5061043961203b565b348015610a14575f80fd5b505f546001600160a01b0316610551565b348015610a30575f80fd5b506104b961205d565b348015610a44575f80fd5b506104397f000000000000000000000000000000000000000000000000000000e8d4a5100081565b348015610a77575f80fd5b505f61046b565b348015610a89575f80fd5b5061046b610a98366004614980565b61206c565b348015610aa8575f80fd5b5061046b610ab7366004614f21565b600a60209081525f928352604080842090915290825290205460ff1681565b348015610ae1575f80fd5b50600254610551906001600160a01b031681565b348015610b00575f80fd5b5060115461046b90600160a01b900460ff1681565b348015610b20575f80fd5b5061059b610b2f366004614f8d565b61208b565b348015610b3f575f80fd5b50610439610b4e366004614fcb565b60016020525f908152604090205481565b348015610b6a575f80fd5b506104b9610b79366004614fe4565b6121e7565b61059b610b8c366004614f8d565b61235c565b610ba4610b9f366004615040565b6124dc565b60405161044392919061509d565b348015610bbd575f80fd5b50610439601f5481565b348015610bd2575f80fd5b5061059b610be1366004614c23565b6125d3565b348015610bf1575f80fd5b5061046b610c00366004614c23565b612654565b61059b610c13366004614b89565b6126ca565b348015610c23575f80fd5b5061059b610c32366004614c23565b6126f9565b348015610c42575f80fd5b5061043960105481565b348015610c57575f80fd5b5061059b61274f565b348015610c6b575f80fd5b50601854610439565b348015610c7f575f80fd5b50610439610c8e366004614f21565b6001600160a01b039182165f90815260066020908152604080832093909416825291909152205490565b348015610cc3575f80fd5b5061059b610cd2366004614c23565b612887565b348015610ce2575f80fd5b5061046b610cf13660046150ee565b6128c4565b348015610d01575f80fd5b5061046b610d10366004615131565b612a35565b5f6301ffc9a760e01b6001600160e01b031983161480610d455750630f632fb360e01b6001600160e01b03198316145b92915050565b606060088054610d5a9061514b565b80601f0160208091040260200160405190810160405280929190818152602001828054610d869061514b565b8015610dd15780601f10610da857610100808354040283529160200191610dd1565b820191905f5260205f20905b815481529060010190602001808311610db457829003601f168201915b5050505050905090565b335f908152600b60209081526040808320858452909152812080548391908390610e06908490615191565b90915550506001600160a01b0384165f908152600b6020908152604080832086845290915281208054849290610e3d9084906151a4565b909155505060408051338082526020820185905285926001600160a01b038816927f1b3d7edb2e9c0b0e7c525b20aaaef0f5940d2ed71663c7d39266ecafac72885991015b60405180910390a45060015b9392505050565b5f33610ea2818585612a69565b5060019392505050565b604080518082019091525f80825260208201526060610edc60405180604001604052805f81526020015f81525090565b6040805180820182525f8082526001600160401b03602080840182905284518381529081019094529195509182610f35565b604080518082019091525f815260606020820152815260200190600190039081610f0e5790505b5093505f80610f59604089013560608a0135610f5460208c018c614fcb565b612a7b565b60408051808201909152918252602082015296989597505050505050565b5f60188281548110610f8b57610f8b6151b7565b5f918252602090912001546001600160a01b031692915050565b7f0000000000000000000000001a44076050125825900e736c501f859c50fe728c6001600160a01b03163314610ff5576040516391ac5e4f60e01b81523360048201526024015b60405180910390fd5b6020870180359061100f9061100a908a614fcb565b612abe565b1461104d576110216020880188614fcb565b60405163309afaf360e21b815263ffffffff909116600482015260208801356024820152604401610fec565b61105c87878787878787612af9565b50505050505050565b5f61106f33612654565b61108b576040516282b42960e81b815260040160405180910390fd5b601f545f0361109b57505f610e8e565b6040516317b933cd60e11b8152735f4a43d56f7bfecbb4fe2b2f8b13bfb450b5c45590632f72679a906110d990601e908890889088906004016151cb565b602060405180830381865af41580156110f4573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906111189190615238565b9050611126601f5482612d18565b601f5f8282546111369190615191565b90915550509392505050565b5f546001600160a01b03163314158061117257506001600160a01b0381165f908152601d602052604090205460ff165b1561118f576040516282b42960e81b815260040160405180910390fd5b6001600160a01b03165f818152601d60205260408120805460ff19166001908117909155601e805491820181559091527f50bb669a95c7b50b7e8a6f09454034b2b14cf2b85c730dca9a539ca82cb6e3500180546001600160a01b0319169091179055565b5f33611201858285612d2d565b61120c858585612da8565b50600195945050505050565b5f8061122333612654565b61123f576040516282b42960e81b815260040160405180910390fd5b6001600160a01b038481165f908152601c6020526040812054601454909216331461126a575f611277565b6014546001600160a01b03165b9050611284868287612da8565b6001600160a01b0387165f908152601c60205260409020549094506112a99083615191565b925050509250929050565b5f6112be33612654565b6112da576040516282b42960e81b815260040160405180910390fd5b81515f036112fb57604051630b8fc7cd60e21b815260040160405180910390fd5b7f0000000000000000000000001a44076050125825900e736c501f859c50fe728c6001600160a01b031661134257604051631c277d0f60e21b815260040160405180910390fd5b5f61134c83613163565b905060ff811660061461137257604051635087d85f60e01b815260040160405180910390fd5b60405163815d56a960e01b815260ff821660048201526175d8905f90735f4a43d56f7bfecbb4fe2b2f8b13bfb450b5c4559063815d56a9906024015f60405180830381865af41580156113c7573d5f803e3d5ffd5b505050506040513d5f823e601f3d908101601f191682016040526113ee919081019061524f565b90505f6113fd8387845f6131a5565b80519091503410156114215760405162976f7560e21b815260040160405180910390fd5b5f61142f8488858533613283565b825190915034111561147457815133906108fc9061144d9034615191565b6040518115909202915f818181858888f19350505050158015611472573d5f803e3d5ffd5b505b519450505050505b919050565b611489613389565b63ffffffff82165f81815260016020908152604091829020849055815192835282018390527f238399d427b947898edb290f5ff0f9109849b1c3ba196a42e35f00c50a54b98b91015b60405180910390a15050565b335f90815260056020526040902054681b1ae4d6e2ef5000001061151557604051636165515360e11b815260040160405180910390fd5b335f908152601a60205260409020541561154257604051632de0270560e01b815260040160405180910390fd5b601880546001810182557fb13d2d76d1f4b7be834882e410b3e3a8afaf69f83600ae24db354391d2378d2e0180546001600160a01b0319163390811790915590545f918252601a602052604090912055565b604080518082019091525f80825260208201525f6115c260408501356060860135610f546020880188614fcb565b9150505f806115d186846133b5565b90925090506115ee6115e66020880188614fcb565b8383886131a5565b9695505050505050565b5f600d546001146116385760405162461bcd60e51b815260206004820152600a6024820152695245454e5452414e435960b01b6044820152606401610fec565b6002600d555f61164661203b565b6116519060016151a4565b905061165c33612654565b156116755761166c8682876134d4565b84915050611d07565b6001600160a01b0384165f908152601d602052604090205460ff161561172e576040516323b872dd60e01b81526001600160a01b038781166004830152306024830152604482018790528516906323b872dd906064016020604051808303815f875af11580156116e7573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061170b91906152c3565b5084601f5f82825461171d91906151a4565b9091555061166c90508682876134d4565b601754604051638340f54960e01b81526001600160a01b0388811660048301528681166024830152604482018890525f921690638340f549906064016020604051808303815f875af1158015611786573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906117aa9190615238565b601154909150600160a01b900460ff161580156117d457506015546001600160a01b038681169116145b15611ac057601754604080516359b44ca960e11b815290515f926001600160a01b03169163b3689952916004808301926101c0929190829003018187875af1158015611822573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061184691906152de565b9050685220d9392f9ccc0000816001602002015110611abe57601754604080516361c75c5960e01b815290515f926001600160a01b0316916361c75c5991600480830192869291908290030181865afa1580156118a5573d5f803e3d5ffd5b505050506040513d5f823e601f3d908101601f191682016040526118cc919081019061537c565b6017546015546040516301c82d6160e21b8152306004820152635a4d10c0602482018190526001600160a01b0392831660448301525f6064830152939450911690630720b584906084016020604051808303815f875af1158015611932573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906119569190615238565b6015546013549192506001600160a01b039081169163a9059cbb911661198164e8d4a510008561540b565b6040516001600160e01b031960e085901b1681526001600160a01b03909216600483015260248201526044016020604051808303815f875af11580156119c9573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906119ed91906152c3565b50601354604051631220ca0560e31b81526001600160a01b0390911690639106502890611a1e90859060040161542a565b5f604051808303815f87803b158015611a35575f80fd5b505af1158015611a47573d5f803e3d5ffd5b505060165460405163539a6ec960e11b81526001600160a01b03909116925063a734dd929150611a7b90859060040161542a565b5f604051808303815f87803b158015611a92575f80fd5b505af1158015611aa4573d5f803e3d5ffd5b50506011805460ff60a01b1916600160a01b179055505050505b505b5f856001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015611afd573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611b219190615475565b60ff1690505f611b3186856135a5565b90505f600c611b408684615191565b118015611b4d5750601882105b8015611b64575069be951906eba2aa800000600f54105b9050735f4a43d56f7bfecbb4fe2b2f8b13bfb450b5c45563955bc93a85858a89611b8c61203b565b600f5460175f9054906101000a90046001600160a01b03166001600160a01b031663949b9c696040518163ffffffff1660e01b8152600401602060405180830381865afa158015611bdf573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611c039190615238565b6040516001600160e01b031960e08a901b1681526004810197909752602487019590955260448601939093526064850191909152608484015260a483015260c482015283151560e4820152610104016040805180830381865af4158015611c6c573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611c909190615495565b90965091508015611cf65785600f5f828254611cac91906151a4565b90915550506001600160a01b038a165f908152601c602052604081208054889290611cd89084906151a4565b925050819055508560105f828254611cf091906151a4565b90915550505b611d018a83886134d4565b50505050505b6001600d55949350505050565b335f818152600c602090815260408083206001600160a01b03881680855290835281842087855290925280832085905551919285927fb3fd5071835887567a0671151121894ddccc2842f1d10bedad13e0d17cace9a790610e829087815260200190565b600360209081525f928352604080842090915290825290208054611d9b9061514b565b80601f0160208091040260200160405190810160405280929190818152602001828054611dc79061514b565b8015611e125780601f10611de957610100808354040283529160200191611e12565b820191905f5260205f20905b815481529060010190602001808311611df557829003601f168201915b505050505081565b335f818152600a602090815260408083206001600160a01b038716808552908352818420805460ff191687151590811790915591519182529293917fceb576d9f15e4e200fdb5096d64d5dfd667e16def20c1eefd14256d8e3faa267910160405180910390a350600192915050565b611e9233612654565b611eae576040516282b42960e81b815260040160405180910390fd5b6001600160a01b0382165f90815260196020526040902054611ed09082612d18565b6001600160a01b0383165f9081526019602052604081208054909190611ef7908490615191565b90915550505050565b611f0933612654565b611f25576040516282b42960e81b815260040160405180910390fd5b6001600160a01b0382165f9081526019602052604081208054839290611ef79084906151a4565b611f54613389565b600480546001600160a01b0319166001600160a01b0383169081179091556040519081527ff0be4f1e87349231d80c36b33f9e8639658eeaf474014dee15a3e6a4d4414197906020015b60405180910390a150565b611fb1613389565b611fba5f6135b3565b565b5f546001600160a01b031633141580611fdf57506016546001600160a01b031615155b15611ffc576040516282b42960e81b815260040160405180910390fd5b601680546001600160a01b039485166001600160a01b031991821617909155601180549385169382169390931790925560148054919093169116179055565b5f6224ed20600e544261204e9190615191565b612058919061540b565b905090565b606060098054610d5a9061514b565b5f612078338484612da8565b8214612082575f80fd5b50600192915050565b612093613389565b5f5b818110156121b5576120d78383838181106120b2576120b26151b7565b90506020028101906120c491906154b7565b6120d29060408101906154d5565b613602565b8282828181106120e9576120e96151b7565b90506020028101906120fb91906154b7565b6121099060408101906154d5565b60035f86868681811061211e5761211e6151b7565b905060200281019061213091906154b7565b61213e906020810190614fcb565b63ffffffff1663ffffffff1681526020019081526020015f205f86868681811061216a5761216a6151b7565b905060200281019061217c91906154b7565b61218d906040810190602001615517565b61ffff16815260208101919091526040015f20916121ac919083615574565b50600101612095565b507fbe4864a8e820971c0247f5992e2da559595f7bf076a21cb5928d443d2a13b67482826040516114d2929190615655565b63ffffffff84165f90815260036020908152604080832061ffff8716845290915281208054606092919061221a9061514b565b80601f01602080910402602001604051908101604052809291908181526020018280546122469061514b565b80156122915780601f1061226857610100808354040283529160200191612291565b820191905f5260205f20905b81548152906001019060200180831161227457829003601f168201915b5050505050905080515f036122df5783838080601f0160208091040260200160405190810160405280939291908181526020018383808284375f920191909152509294506123549350505050565b5f8390036122ee579050612354565b60028310612337576123008484613602565b8061230e846002818861573a565b60405160200161232093929190615778565b604051602081830303815290604052915050612354565b8383604051639a6d49cd60e01b8152600401610fec929190615796565b949350505050565b5f5b8181101561245f5736838383818110612379576123796151b7565b905060200281019061238b91906157a9565b90506123bd61239d6020830183614fcb565b602083013563ffffffff919091165f908152600160205260409020541490565b6123c75750612457565b3063d045a0dc60c08301358360a08101356123e66101008301836154d5565b6123f7610100890160e08a01614c23565b6124056101208a018a6154d5565b6040518963ffffffff1660e01b815260040161242797969594939291906157d2565b5f604051808303818588803b15801561243e575f80fd5b505af1158015612450573d5f803e3d5ffd5b5050505050505b60010161235e565b50336001600160a01b0316638e9e70996040518163ffffffff1660e01b81526004015f60405180830381865afa15801561249b573d5f803e3d5ffd5b505050506040513d5f823e601f3d908101601f191682016040526124c2919081019061524f565b604051638351eea760e01b8152600401610fec91906149ff565b6124e46148d5565b604080518082019091525f80825260208201525f806125186040880135606089013561251360208b018b614fcb565b613643565b915091505f8061252889846133b5565b909250905061255461253d60208b018b614fcb565b838361254e368d90038d018d615857565b8b613283565b60408051808201909152858152602080820186905282519298509096503391907f85496b760a4b7f8d66384b9df21b381f5d1b1e79f229a47aaf4c232edc2fe59a906125a2908d018d614fcb565b6040805163ffffffff909216825260208201899052810187905260600160405180910390a350505050935093915050565b6125db613389565b60405163ca5eb5e160e01b81526001600160a01b0382811660048301527f0000000000000000000000001a44076050125825900e736c501f859c50fe728c169063ca5eb5e1906024015f604051808303815f87803b15801561263b575f80fd5b505af115801561264d573d5f803e3d5ffd5b5050505050565b6017545f906001600160a01b038381169116148061267f57506012546001600160a01b038381169116145b8061269757506016546001600160a01b038381169116145b806126af57506014546001600160a01b038381169116145b80610d455750506011546001600160a01b0390811691161490565b3330146126ea5760405163029a949d60e31b815260040160405180910390fd5b61105c8787878787878761104d565b612701613389565b600280546001600160a01b0319166001600160a01b0383169081179091556040519081527fd48d879cef83a1c0bdda516f27b13ddb1b3f8bbac1c9e1511bb2a659c242776090602001611f9e565b335f908152601a60205260408120549081900361277f5760405163dae3bbdf60e01b815260040160405180910390fd5b335f90815260196020526040902054156127ac576040516303cb96db60e21b815260040160405180910390fd5b601854818114612842575f60186127c4600184615191565b815481106127d4576127d46151b7565b5f918252602090912001546001600160a01b031690508060186127f8600186615191565b81548110612808576128086151b7565b5f91825260208083209190910180546001600160a01b0319166001600160a01b03948516179055929091168152601a909152604090208290555b601880548061285357612853615888565b5f828152602080822083015f1990810180546001600160a01b0319169055909201909255338252601a905260408120555050565b61288f613389565b6001600160a01b0381166128b857604051631e4fbdf760e01b81525f6004820152602401610fec565b6128c1816135b3565b50565b5f336001600160a01b0386161480159061290157506001600160a01b0385165f908152600a6020908152604080832033845290915290205460ff16155b15612971576001600160a01b0385165f908152600c6020908152604080832033845282528083208684529091529020545f19811461296f576129438382615191565b6001600160a01b0387165f908152600c6020908152604080832033845282528083208884529091529020555b505b6001600160a01b0385165f908152600b60209081526040808320868452909152812080548492906129a3908490615191565b90915550506001600160a01b0384165f908152600b60209081526040808320868452909152812080548492906129da9084906151a4565b9091555050604080513381526020810184905284916001600160a01b0380881692908916917f1b3d7edb2e9c0b0e7c525b20aaaef0f5940d2ed71663c7d39266ecafac728859910160405180910390a4506001949350505050565b5f602082018035906001908390612a4c9086614fcb565b63ffffffff16815260208101919091526040015f20541492915050565b612a768383836001613904565b505050565b5f80612a86856139d6565b915081905083811015612ab6576040516371c4efed60e01b81526004810182905260248101859052604401610fec565b935093915050565b63ffffffff81165f9081526001602052604081205480610d455760405163f6ff4fb760e01b815263ffffffff84166004820152602401610fec565b60015f612b0960208a018a614fcb565b63ffffffff1663ffffffff1681526020019081526020015f2054876020013514612b45576040516282b42960e81b815260040160405180910390fd5b5f612b508686613a0c565b90505f612b5c82613a2e565b90505f612b698888613a62565b90505f612b7c612b798a8a613aac565b90565b90505f612b8883613163565b905060041960ff821601612c9a576175d8612ba660208e018e614fcb565b63ffffffff1614612bca576040516310dfc03360e01b815260040160405180910390fd5b601154604051620b465d60e71b81526001600160a01b03909116906305a32e8090612bf99086906004016149ff565b5f604051808303815f87803b158015612c10575f80fd5b505af1158015612c22573d5f803e3d5ffd5b5050505060115f9054906101000a90046001600160a01b03166001600160a01b03168b7fefed6d3500546b29533b128a29e3a94d70788727f0507505ac12eaf2e578fd9c8e5f016020810190612c789190614fcb565b6040805163ffffffff90921682525f60208301520160405180910390a3612d0a565b60061960ff821601612cf6576175d8612cb660208e018e614fcb565b63ffffffff1614612cda576040516310dfc03360e01b815260040160405180910390fd5b612cf18b612ceb60208f018f614fcb565b85613ac3565b612d0a565b83612d018484613bc8565b14612d0a575f80fd5b505050505050505050505050565b5f818310612d265781610e8e565b5090919050565b6001600160a01b038381165f908152600660209081526040808320938616835292905220545f198114612da25781811015612d9457604051637dc7a0d960e11b81526001600160a01b03841660048201526024810182905260448101839052606401610fec565b612da284848484035f613904565b50505050565b6001600160a01b0383165f9081526019602090815260408083205460059092528220548391612dd691615191565b1015612df5576040516313424e8d60e31b815260040160405180910390fd5b6001600160a01b0384165f9081526020805260408120612e1490613cea565b90506001600160a01b038416155f818015612e3d57506016546001600160a01b03888116911614155b612e545760018351612e4f9190615191565b612ed0565b600e5460405163c6dbdf1d60e01b8152735f4a43d56f7bfecbb4fe2b2f8b13bfb450b5c4559163c6dbdf1d91612e9191879142919060040161589c565b602060405180830381865af4158015612eac573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612ed09190615238565b90505b5f85118015612ee257505f8112155b15613073575f838281518110612efa57612efa6151b7565b6020908102919091018101516001600160a01b038a165f908152600b835260408082208383529093529190912054909150801561305f57612f3b8782612d18565b6001600160a01b038a165f908152600b60209081526040808320868452909152812080549293508392909190612f72908490615191565b90915550849050612fda576001600160a01b0388165f9081526020805260409020612f9d9083613d43565b6001600160a01b0388165f908152600b6020908152604080832085845290915281208054839290612fcf9084906151a4565b90915550612ffd9050565b5f828152601b602052604081208054839290612ff7908490615191565b90915550505b6001600160a01b0389165f908152600b602090815260408083208584529091528120549003613046576001600160a01b0389165f90815260208052604090206130469083613e34565b6130508188615191565b965061305c81876151a4565b95505b61306a6001846158ea565b92505050612ed3565b831561315957613084878786613f47565b6001600160a01b0387165f908152601c602052604090205415613159576001600160a01b0387165f908152601c60205260408120546130c4908690612d18565b6001600160a01b0389165f908152601c60205260408120805492935083929091906130f0908490615191565b90915550506001600160a01b03871661312a5761310f60105482612d18565b60105f82825461311f9190615191565b909155506131579050565b6001600160a01b0387165f908152601c6020526040812080548392906131519084906151a4565b90915550505b505b5050509392505050565b5f81515f0361318557604051638d0242c960e01b815260040160405180910390fd5b815f81518110613197576131976151b7565b016020015160f81c92915050565b604080518082019091525f80825260208201527f0000000000000000000000001a44076050125825900e736c501f859c50fe728c6001600160a01b031663ddc28c586040518060a001604052808863ffffffff16815260200161320789612abe565b8152602001878152602001868152602001851515815250306040518363ffffffff1660e01b815260040161323c929190615910565b6040805180830381865afa158015613256573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061327a91906159b7565b95945050505050565b61328b6148d5565b5f613298845f015161406d565b6020850151909150156132b2576132b28460200151614094565b7f0000000000000000000000001a44076050125825900e736c501f859c50fe728c6001600160a01b0316632637a450826040518060a001604052808b63ffffffff1681526020016133028c612abe565b81526020018a81526020018981526020015f8960200151111515815250866040518463ffffffff1660e01b815260040161333d929190615910565b60806040518083038185885af1158015613359573d5f803e3d5ffd5b50505050506040513d601f19601f8201168201806040525081019061337e91906159d1565b979650505050505050565b5f546001600160a01b03163314611fba5760405163118cdaa760e01b8152336004820152602401610fec565b6060805f61341085602001356133ca86614177565b6133d760a08901896154d5565b8080601f0160208091040260200160405190810160405280939291908181526020018383808284375f920191909152506141a292505050565b90935090505f81613422576001613425565b60025b90506134456134376020880188614fcb565b82610b7960808a018a6154d5565b6004549093506001600160a01b0316156134cb576004805460405163043a78eb60e01b81526001600160a01b039091169163043a78eb9161348a918891889101615a38565b602060405180830381865afa1580156134a5573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906134c991906152c3565b505b50509250929050565b5f828152601b6020526040812080548392906134f19084906151a4565b90915550506001600160a01b0383165f90815260208052604090206135169083613d43565b6135215f8483613f47565b6001600160a01b0383165f908152600b60209081526040808320858452909152812080548392906135539084906151a4565b9091555050604080513381526020810183905283916001600160a01b038616915f917f1b3d7edb2e9c0b0e7c525b20aaaef0f5940d2ed71663c7d39266ecafac728859910160405180910390a4505050565b5f818311612d265781610e8e565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b5f613610600282848661573a565b61361991615a5c565b60f01c905060038114612a76578282604051639a6d49cd60e01b8152600401610fec929190615796565b5f80613650858585612a7b565b6040516323a9655f60e21b815291935091505f90735f4a43d56f7bfecbb4fe2b2f8b13bfb450b5c45590638ea5957c906136909084903690600401615796565b5f60405180830381865af41580156136aa573d5f803e3d5ffd5b505050506040513d5f823e601f3d908101601f191682016040526136d1919081019061524f565b905080515f036136f457604051630b8fc7cd60e21b815260040160405180910390fd5b5f805f8380602001905181019061370b9190615af7565b915091508051825114158061371f57508151155b1561373d5760405163a121188760e01b815260040160405180910390fd5b5f306001600160a01b031663963efcaa6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561377a573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061379e9190615238565b90505f5b83518110156138cb575f8482815181106137be576137be6151b7565b602002602001015190505f838584815181106137dc576137dc6151b7565b60200260200101516137ee9190615b5a565b335f908152600b6020908152604080832086845290915290205490915081111561382b57604051631e9acf1760e31b815260040160405180910390fd5b335f908152600b6020908152604080832085845290915281208054839290613854908490615191565b90915550505f828152601b602052604081208054839290613876908490615191565b9091555050335f908152600b6020908152604080832085845290915281205490036138b257335f90815260208052604090206138b29083613e34565b6138bc81886151a4565b965050508060010190506137a2565b508684146138ec57604051631d6e22b960e01b815260040160405180910390fd5b6138f7335f86613f47565b5050505050935093915050565b6001600160a01b03841661392d5760405163e602df0560e01b81525f6004820152602401610fec565b6001600160a01b03831661395657604051634a1406b160e11b81525f6004820152602401610fec565b6001600160a01b038085165f9081526006602090815260408083209387168352929052208290558015612da257826001600160a01b0316846001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040516139c891815260200190565b60405180910390a350505050565b5f7f000000000000000000000000000000000000000000000000000000e8d4a51000613a02818461540b565b610d459190615b5a565b5f613a1b60286020848661573a565b613a2491615b71565b60c01c9392505050565b5f610d457f000000000000000000000000000000000000000000000000000000e8d4a510006001600160401b038416615b5a565b6060613a71826028818661573a565b8080601f0160208091040260200160405190810160405280939291908181526020018383808284375f92019190915250929695505050505050565b5f613aba602082848661573a565b610e8e91615ba7565b5f80613ace8361421c565b915091505f613adc8261434c565b601454909150613b08906001600160a01b0316613af761203b565b613b029060016151a4565b836134d4565b60145460405163557502cb60e11b81526001600160401b0385166004820152602481018390526001600160a01b039091169063aaea0596906044015f604051808303815f87803b158015613b5a575f80fd5b505af1158015613b6c573d5f803e3d5ffd5b50506014546040805163ffffffff8a168152602081018690526001600160a01b0390921693508992507fefed6d3500546b29533b128a29e3a94d70788727f0507505ac12eaf2e578fd9c910160405180910390a3505050505050565b5f805f84806020019051810190613bdf9190615af7565b9150915080518251141580613bf357508151155b15613c115760405163a121188760e01b815260040160405180910390fd5b5f306001600160a01b031663963efcaa6040518163ffffffff1660e01b8152600401602060405180830381865afa158015613c4e573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190613c729190615238565b90505f5b8351811015613ce0575f82848381518110613c9357613c936151b7565b6020026020010151613ca59190615b5a565b9050613ccb87868481518110613cbd57613cbd6151b7565b6020026020010151836134d4565b613cd581876151a4565b955050600101613c76565b5050505092915050565b6060815f01805480602002602001604051908101604052809291908181526020018280548015613d3757602002820191905f5260205f20905b815481526020019060010190808311613d23575b50505050509050919050565b5f81815260018301602052604090205460ff1615613d5f575050565b5f818152600183810160205260408220805460ff19169091179055613d848383614365565b508354600181810186555f8681526020812090920182905585549293509091613dad9190615191565b90505b81811115613e0e5783613dc4600183615191565b81548110613dd457613dd46151b7565b905f5260205f200154845f018281548110613df157613df16151b7565b5f9182526020909120015580613e0681615bc4565b915050613db0565b5081835f018281548110613e2457613e246151b7565b5f91825260209091200155505050565b5f81815260018301602052604090205460ff16613e8a5760405162461bcd60e51b815260206004820152601460248201527315985b1d5948191bd95cc81b9bdd08195e1a5cdd60621b6044820152606401610fec565b5f613e958383614365565b50835490915081108015613ec4575081835f018281548110613eb957613eb96151b7565b905f5260205f200154145b613f025760405162461bcd60e51b815260206004820152600f60248201526e15985b1d59481b9bdd08199bdd5b99608a1b6044820152606401610fec565b5f19835f018281548110613f1857613f186151b7565b5f9182526020808320909101929092558381526001850190915260409020805460ff19169055612a7683614418565b6001600160a01b038316613f71578060075f828254613f6691906151a4565b90915550613fe19050565b6001600160a01b0383165f9081526005602052604090205481811015613fc35760405163391434e360e21b81526001600160a01b03851660048201526024810182905260448101839052606401610fec565b6001600160a01b0384165f9081526005602052604090209082900390555b6001600160a01b038216613ffd5760078054829003905561401b565b6001600160a01b0382165f9081526005602052604090208054820190555b816001600160a01b0316836001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8360405161406091815260200190565b60405180910390a3505050565b5f813414614090576040516304fb820960e51b8152346004820152602401610fec565b5090565b5f7f0000000000000000000000001a44076050125825900e736c501f859c50fe728c6001600160a01b031663e4fe1d946040518163ffffffff1660e01b8152600401602060405180830381865afa1580156140f1573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906141159190615bd9565b90506001600160a01b03811661413e576040516329b99a9560e11b815260040160405180910390fd5b6141736001600160a01b038216337f0000000000000000000000001a44076050125825900e736c501f859c50fe728c85614582565b5050565b5f610d457f000000000000000000000000000000000000000000000000000000e8d4a510008361540b565b80516060901515806141eb5784846040516020016141d792919091825260c01b6001600160c01b031916602082015260280190565b604051602081830303815290604052614212565b848433856040516020016142029493929190615bf4565b6040516020818303038152906040525b9150935093915050565b5f8060118351101561424157604051638d0242c960e01b815260040160405180910390fd5b600760ff16835f81518110614258576142586151b7565b016020015160f81c1461427e57604051635b60892f60e01b815260040160405180910390fd5b6142898360016145dc565b91506142968360096145dc565b9050816001600160401b03165f036142c05760405162320bd360e51b815260040160405180910390fd5b806001600160401b03165f036142e95760405163162908e360e11b815260040160405180910390fd5b64e8d4a51000816001600160401b031611156143475760405162461bcd60e51b815260206004820152601c60248201527f436f6d70656e736174696f6e2065786365656473206d6178696d756d000000006044820152606401610fec565b915091565b5f610d456001600160401b03831664e8d4a51000615b5a565b81545f90819081905b8082101561440a575f60026143838484615191565b61438d919061540b565b61439790846151a4565b905085875f0182815481106143ae576143ae6151b7565b905f5260205f200154036143ca57935060019250614411915050565b85875f0182815481106143df576143df6151b7565b905f5260205f2001541015614400576143f98160016151a4565b9250614404565b8091505b5061436e565b5091505f90505b9250929050565b80545f9081906001600160401b0381111561443557614435614c7c565b60405190808252806020026020018201604052801561445e578160200160208202803683370190505b5090505f5b83548110156144e0575f19845f018281548110614482576144826151b7565b905f5260205f200154146144d857835f0181815481106144a4576144a46151b7565b905f5260205f2001548284815181106144bf576144bf6151b7565b6020908102919091010152826144d481615c20565b9350505b600101614463565b50816001600160401b038111156144f9576144f9614c7c565b604051908082528060200260200182016040528015614522578160200160208202803683370190505b508051614536918591602090910190614918565b505f5b82811015612da257818181518110614553576145536151b7565b6020026020010151845f01828154811061456f5761456f6151b7565b5f91825260209091200155600101614539565b604080516001600160a01b0385811660248301528416604482015260648082018490528251808303909101815260849091019091526020810180516001600160e01b03166323b872dd60e01b179052612da2908590614781565b5f6145e88260086151a4565b835110156146385760405162461bcd60e51b815260206004820152601c60248201527f496e73756666696369656e74206461746120666f722075696e743634000000006044820152606401610fec565b6038836146468460076151a4565b81518110614656576146566151b7565b016020015160f81c901b60308461466e8560066151a4565b8151811061467e5761467e6151b7565b016020015160f81c901b6028856146968660056151a4565b815181106146a6576146a66151b7565b0160209081015160f81c90911b90866146c08760046151a4565b815181106146d0576146d06151b7565b016020015160f81c901b6018876146e88860036151a4565b815181106146f8576146f86151b7565b016020015160f81c901b6010886147108960026151a4565b81518110614720576147206151b7565b016020015160f81c901b6008896147388a60016151a4565b81518110614748576147486151b7565b01602001518a5160f89190911c90911b908a908a90811061476b5761476b6151b7565b016020015160f81c171717171717179392505050565b5f6147956001600160a01b038416836147e2565b905080515f141580156147b95750808060200190518101906147b791906152c3565b155b15612a7657604051635274afe760e01b81526001600160a01b0384166004820152602401610fec565b6060610e8e83835f845f80856001600160a01b031684866040516148069190615c38565b5f6040518083038185875af1925050503d805f8114614840576040519150601f19603f3d011682016040523d82523d5f602084013e614845565b606091505b50915091506115ee86838360608261486557614860826148ac565b610e8e565b815115801561487c57506001600160a01b0384163b155b156148a557604051639996b31560e01b81526001600160a01b0385166004820152602401610fec565b5080610e8e565b8051156148bc5780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b60405180606001604052805f80191681526020015f6001600160401b0316815260200161491360405180604001604052805f81526020015f81525090565b905290565b828054828255905f5260205f20908101928215614951579160200282015b82811115614951578251825591602001919060010190614936565b506140909291505b80821115614090575f8155600101614959565b6001600160a01b03811681146128c1575f80fd5b5f8060408385031215614991575f80fd5b823561499c8161496c565b946020939093013593505050565b5f602082840312156149ba575f80fd5b81356001600160e01b031981168114610e8e575f80fd5b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b602081525f610e8e60208301846149d1565b5f805f60608486031215614a23575f80fd5b8335614a2e8161496c565b95602085013595506040909401359392505050565b5f60e08284031215614a53575f80fd5b50919050565b5f60208284031215614a69575f80fd5b81356001600160401b03811115614a7e575f80fd5b61235484828501614a43565b83518152602080850151908201525f60a0820160a0604084015280855180835260c08501915060c08160051b8601019250602087015f5b82811015614b095760bf198786030184528151805186526020810151905060406020870152614af360408701826149d1565b9550506020938401939190910190600101614ac1565b5050855160608601525050602084015160808401529050612354565b5f60208284031215614b35575f80fd5b5035919050565b5f60608284031215614a53575f80fd5b5f8083601f840112614b5c575f80fd5b5081356001600160401b03811115614b72575f80fd5b602083019150836020828501011115614411575f80fd5b5f805f805f805f60e0888a031215614b9f575f80fd5b614ba98989614b3c565b96506060880135955060808801356001600160401b03811115614bca575f80fd5b614bd68a828b01614b4c565b90965094505060a0880135614bea8161496c565b925060c08801356001600160401b03811115614c04575f80fd5b614c108a828b01614b4c565b989b979a50959850939692959293505050565b5f60208284031215614c33575f80fd5b8135610e8e8161496c565b5f805f60608486031215614c50575f80fd5b8335614c5b8161496c565b92506020840135614c6b8161496c565b929592945050506040919091013590565b634e487b7160e01b5f52604160045260245ffd5b604080519081016001600160401b0381118282101715614cb257614cb2614c7c565b60405290565b604051601f8201601f191681016001600160401b0381118282101715614ce057614ce0614c7c565b604052919050565b5f6001600160401b03821115614d0057614d00614c7c565b50601f01601f191660200190565b5f60208284031215614d1e575f80fd5b81356001600160401b03811115614d33575f80fd5b8201601f81018413614d43575f80fd5b8035614d56614d5182614ce8565b614cb8565b818152856020838501011115614d6a575f80fd5b816020840160208301375f91810160200191909152949350505050565b803563ffffffff8116811461147c575f80fd5b5f8060408385031215614dab575f80fd5b61499c83614d87565b80151581146128c1575f80fd5b5f8060408385031215614dd2575f80fd5b82356001600160401b03811115614de7575f80fd5b614df385828601614a43565b9250506020830135614e0481614db4565b809150509250929050565b815181526020808301519082015260408101610d45565b5f805f8060808587031215614e39575f80fd5b8435614e448161496c565b9350602085013592506040850135614e5b8161496c565b9396929550929360600135925050565b803561ffff8116811461147c575f80fd5b5f8060408385031215614e8d575f80fd5b614e9683614d87565b9150614ea460208401614e6b565b90509250929050565b5f8060408385031215614ebe575f80fd5b8235614ec98161496c565b91506020830135614e0481614db4565b5f805f60608486031215614eeb575f80fd5b8335614ef68161496c565b92506020840135614f068161496c565b91506040840135614f168161496c565b809150509250925092565b5f8060408385031215614f32575f80fd5b8235614f3d8161496c565b91506020830135614e048161496c565b5f8083601f840112614f5d575f80fd5b5081356001600160401b03811115614f73575f80fd5b6020830191508360208260051b8501011115614411575f80fd5b5f8060208385031215614f9e575f80fd5b82356001600160401b03811115614fb3575f80fd5b614fbf85828601614f4d565b90969095509350505050565b5f60208284031215614fdb575f80fd5b610e8e82614d87565b5f805f8060608587031215614ff7575f80fd5b61500085614d87565b935061500e60208601614e6b565b925060408501356001600160401b03811115615028575f80fd5b61503487828801614b4c565b95989497509550505050565b5f805f8385036080811215615053575f80fd5b84356001600160401b03811115615068575f80fd5b61507487828801614a43565b9450506040601f1982011215615088575f80fd5b506020840191506060840135614f168161496c565b5f60c082019050835182526001600160401b03602085015116602083015260408401516150d7604084018280518252602090810151910152565b5082516080830152602083015160a0830152610e8e565b5f805f8060808587031215615101575f80fd5b843561510c8161496c565b9350602085013561511c8161496c565b93969395505050506040820135916060013590565b5f60608284031215615141575f80fd5b610e8e8383614b3c565b600181811c9082168061515f57607f821691505b602082108103614a5357634e487b7160e01b5f52602260045260245ffd5b634e487b7160e01b5f52601160045260245ffd5b81810381811115610d4557610d4561517d565b80820180821115610d4557610d4561517d565b634e487b7160e01b5f52603260045260245ffd5b608080825285549082018190525f86815260208120909160a0840190835b818110156152105783546001600160a01b03168352600193840193602090930192016151e9565b50506001600160a01b0396909616602084015250506040810192909252606090910152919050565b5f60208284031215615248575f80fd5b5051919050565b5f6020828403121561525f575f80fd5b81516001600160401b03811115615274575f80fd5b8201601f81018413615284575f80fd5b8051615292614d5182614ce8565b8181528560208385010111156152a6575f80fd5b8160208401602083015e5f91810160200191909152949350505050565b5f602082840312156152d3575f80fd5b8151610e8e81614db4565b5f6101c082840312156152ef575f80fd5b82601f8301126152fd575f80fd5b6040516101c081016001600160401b038111828210171561532057615320614c7c565b604052806101c0840185811115615335575f80fd5b845b8181101561534f578051835260209283019201615337565b509195945050505050565b5f6001600160401b0382111561537257615372614c7c565b5060051b60200190565b5f6020828403121561538c575f80fd5b81516001600160401b038111156153a1575f80fd5b8201601f810184136153b1575f80fd5b80516153bf614d518261535a565b8082825260208201915060208360051b8501019250868311156153e0575f80fd5b6020840193505b828410156115ee5783516153fa8161496c565b8252602093840193909101906153e7565b5f8261542557634e487b7160e01b5f52601260045260245ffd5b500490565b602080825282518282018190525f918401906040840190835b8181101561546a5783516001600160a01b0316835260209384019390920191600101615443565b509095945050505050565b5f60208284031215615485575f80fd5b815160ff81168114610e8e575f80fd5b5f80604083850312156154a6575f80fd5b505080516020909101519092909150565b5f8235605e198336030181126154cb575f80fd5b9190910192915050565b5f808335601e198436030181126154ea575f80fd5b8301803591506001600160401b03821115615503575f80fd5b602001915036819003821315614411575f80fd5b5f60208284031215615527575f80fd5b610e8e82614e6b565b601f821115612a7657805f5260205f20601f840160051c810160208510156155555750805b601f840160051c820191505b8181101561264d575f8155600101615561565b6001600160401b0383111561558b5761558b614c7c565b61559f83615599835461514b565b83615530565b5f601f8411600181146155d0575f85156155b95750838201355b5f19600387901b1c1916600186901b17835561264d565b5f83815260208120601f198716915b828110156155ff57868501358255602094850194600190920191016155df565b508682101561561b575f1960f88860031b161c19848701351681555b505060018560011b0183555050505050565b81835281816020850137505f828201602090810191909152601f909101601f19169091010190565b602080825281018290525f6040600584901b830181019083018583605e1936839003015b8782101561572d57868503603f190184528235818112615697575f80fd5b890163ffffffff6156a782614d87565b16865261ffff6156b960208301614e6b565b1660208701526040810135601e198236030181126156d5575f80fd5b016020810190356001600160401b038111156156ef575f80fd5b8036038213156156fd575f80fd5b6060604088015261571260608801828461562d565b96505050602083019250602084019350600182019150615679565b5092979650505050505050565b5f8085851115615748575f80fd5b83861115615754575f80fd5b5050820193919092039150565b5f81518060208401855e5f93019283525090919050565b5f6157838286615761565b838582375f930192835250909392505050565b602081525f61235460208301848661562d565b5f823561013e198336030181126154cb575f80fd5b6001600160401b03811681146128c1575f80fd5b63ffffffff6157e089614d87565b168152602088810135908201525f60408901356157fc816157be565b6001600160401b03811660408401525087606083015260e0608083015261582760e08301878961562d565b6001600160a01b03861660a084015282810360c084015261584981858761562d565b9a9950505050505050505050565b5f6040828403128015615868575f80fd5b50615871614c90565b823581526020928301359281019290925250919050565b634e487b7160e01b5f52603160045260245ffd5b606080825284519082018190525f9060208601906080840190835b818110156158d55783518352602093840193909201916001016158b7565b50506020840195909552505060400152919050565b8181035f8312801583831316838312821617156159095761590961517d565b5092915050565b6040815263ffffffff8351166040820152602083015160608201525f604084015160a0608084015261594560e08401826149d1565b90506060850151603f198483030160a085015261596282826149d1565b60809690960151151560c08501525050506001600160a01b039190911660209091015290565b5f60408284031215615998575f80fd5b6159a0614c90565b825181526020928301519281019290925250919050565b5f604082840312156159c7575f80fd5b610e8e8383615988565b5f60808284031280156159e2575f80fd5b50604051606081016001600160401b0381118282101715615a0557615a05614c7c565b604052825181526020830151615a1a816157be565b6020820152615a2c8460408501615988565b60408201529392505050565b604081525f615a4a60408301856149d1565b828103602084015261327a81856149d1565b80356001600160f01b03198116906002841015615909576001600160f01b031960029490940360031b84901b1690921692915050565b5f82601f830112615aa1575f80fd5b8151615aaf614d518261535a565b8082825260208201915060208360051b860101925085831115615ad0575f80fd5b602085015b83811015615aed578051835260209283019201615ad5565b5095945050505050565b5f8060408385031215615b08575f80fd5b82516001600160401b03811115615b1d575f80fd5b615b2985828601615a92565b92505060208301516001600160401b03811115615b44575f80fd5b615b5085828601615a92565b9150509250929050565b8082028115828204841417610d4557610d4561517d565b80356001600160c01b03198116906008841015615909576001600160c01b031960089490940360031b84901b1690921692915050565b80356020831015610d45575f19602084900360031b1b1692915050565b5f81615bd257615bd261517d565b505f190190565b5f60208284031215615be9575f80fd5b8151610e8e8161496c565b8481526001600160401b0360c01b8460c01b1660208201528260288201525f6115ee6048830184615761565b5f60018201615c3157615c3161517d565b5060010190565b5f610e8e828461576156
Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)
000000000000000000000000ba45bf3b4701ae737dd69034f50f3d821240a34600000000000000000000000073ed5341ea060d4cfdec7bcc38e6c83bda1f24a200000000000000000000000099d9050adbb262184ae867a0c9a76eca4e2043c70000000000000000000000003c499c542cef5e3811e1192ce70d8cc03d5c3359
-----Decoded View---------------
Arg [0] : _vogue (address): 0xbA45bF3B4701aE737Dd69034F50F3d821240A346
Arg [1] : _aux (address): 0x73ed5341ea060d4CfdeC7bcc38e6c83BDa1F24A2
Arg [2] : _uma (address): 0x99d9050AdBB262184aE867a0C9A76ecA4e2043C7
Arg [3] : _usdc (address): 0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359
-----Encoded View---------------
4 Constructor Arguments found :
Arg [0] : 000000000000000000000000ba45bf3b4701ae737dd69034f50f3d821240a346
Arg [1] : 00000000000000000000000073ed5341ea060d4cfdec7bcc38e6c83bda1f24a2
Arg [2] : 00000000000000000000000099d9050adbb262184ae867a0c9a76eca4e2043c7
Arg [3] : 0000000000000000000000003c499c542cef5e3811e1192ce70d8cc03d5c3359
Loading...
Loading
Loading...
Loading
Loading...
Loading
Net Worth in USD
$0.00
Net Worth in POL
Multichain Portfolio | 32 Chains
| Chain | Token | Portfolio % | Price | Amount | Value |
|---|
Loading...
Loading
Loading...
Loading
Loading...
Loading
[ Download: CSV Export ]
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.