Liquidity Party adapter
This commit is contained in:
@@ -40,7 +40,6 @@ interface ISwapAdapterTypes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// @dev Representation used for rational numbers such as prices.
|
/// @dev Representation used for rational numbers such as prices.
|
||||||
// TODO: Use only uint128 for numerator and denominator.
|
|
||||||
struct Fraction {
|
struct Fraction {
|
||||||
uint256 numerator;
|
uint256 numerator;
|
||||||
uint256 denominator;
|
uint256 denominator;
|
||||||
@@ -61,6 +60,20 @@ interface ISwapAdapterTypes {
|
|||||||
/// available for unexpected reason. E.g. it was paused due to a bug.
|
/// available for unexpected reason. E.g. it was paused due to a bug.
|
||||||
error Unavailable(string reason);
|
error Unavailable(string reason);
|
||||||
|
|
||||||
|
/// @dev The InvalidOrder error is thrown when the input to a swap is
|
||||||
|
/// not valid: e.g. if the limit price is negative, or below the
|
||||||
|
/// current price; the request amount is 0; the requested swap tokens
|
||||||
|
/// are not part of the pool; etc.
|
||||||
|
error InvalidOrder(string reason);
|
||||||
|
|
||||||
|
/// @dev The TooSmall error is thrown when the requested trade amount
|
||||||
|
/// is too small, causing either zero output or a numerical imprecision
|
||||||
|
/// problem. If lowerLimit is not zero, then it specifies the minimum
|
||||||
|
/// trade size required. If lowerLimit is zero, then the lower bound
|
||||||
|
/// cannot be easily computed, in which case solvers can binary search
|
||||||
|
/// for a precise lower bound.
|
||||||
|
error TooSmall(uint256 lowerLimit);
|
||||||
|
|
||||||
/// @dev The LimitExceeded error is thrown when a limit has been exceeded.
|
/// @dev The LimitExceeded error is thrown when a limit has been exceeded.
|
||||||
/// E.g. the specified amount can't be traded safely.
|
/// E.g. the specified amount can't be traded safely.
|
||||||
error LimitExceeded(uint256 limit);
|
error LimitExceeded(uint256 limit);
|
||||||
|
|||||||
@@ -13,14 +13,267 @@ library FractionMath {
|
|||||||
ISwapAdapterTypes.Fraction memory frac1,
|
ISwapAdapterTypes.Fraction memory frac1,
|
||||||
ISwapAdapterTypes.Fraction memory frac2
|
ISwapAdapterTypes.Fraction memory frac2
|
||||||
) internal pure returns (int8) {
|
) internal pure returns (int8) {
|
||||||
uint256 crossProduct1 = frac1.numerator * frac2.denominator;
|
uint256 fixed1 = toQ128x128(frac1.numerator, frac1.denominator);
|
||||||
uint256 crossProduct2 = frac2.numerator * frac1.denominator;
|
uint256 fixed2 = toQ128x128(frac2.numerator, frac2.denominator);
|
||||||
|
|
||||||
// fractions are equal
|
// fractions are equal
|
||||||
if (crossProduct1 == crossProduct2) return 0;
|
if (fixed1 == fixed2) return 0;
|
||||||
// frac1 is greater than frac2
|
// frac1 is greater than frac2
|
||||||
else if (crossProduct1 > crossProduct2) return 1;
|
else if (fixed1 > fixed2) return 1;
|
||||||
// frac1 is less than frac2
|
// frac1 is less than frac2
|
||||||
else return -1;
|
else return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @notice Converts a Fraction into unsigned Q128.128 fixed point
|
||||||
|
function toQ128x128(ISwapAdapterTypes.Fraction memory rational)
|
||||||
|
internal
|
||||||
|
pure
|
||||||
|
returns (uint256 result)
|
||||||
|
{
|
||||||
|
return toQ128x128(rational.numerator, rational.denominator);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @notice Converts an unsigned rational `numerator / denominator`
|
||||||
|
/// into Q128.128 (unsigned 128.128 fixed point),
|
||||||
|
/// rounding toward zero (floor for positive inputs).
|
||||||
|
///
|
||||||
|
/// see https://github.com/Liquidity-Party/toQ128x128
|
||||||
|
///
|
||||||
|
/// @dev Reverts if:
|
||||||
|
/// - `denominator == 0`, or
|
||||||
|
/// - the exact result >= 2^256 (i.e. overflow of uint256).
|
||||||
|
///
|
||||||
|
/// This computes floor(numerator * 2^128 / denominator)
|
||||||
|
/// using a full 512-bit intermediate to avoid precision loss.
|
||||||
|
///
|
||||||
|
function toQ128x128(uint256 numerator, uint256 denominator)
|
||||||
|
internal
|
||||||
|
pure
|
||||||
|
returns (uint256 result)
|
||||||
|
{
|
||||||
|
require(denominator != 0, "toQ128x128: div by zero");
|
||||||
|
|
||||||
|
// We want (numerator * 2^128) / denominator using full precision,
|
||||||
|
// so we implement a 512-bit muldiv.
|
||||||
|
//
|
||||||
|
// Let:
|
||||||
|
// prod = numerator * 2^128
|
||||||
|
//
|
||||||
|
// Since 2^128 is a power of two, the 512-bit product is easy:
|
||||||
|
// prod0 = (numerator << 128) mod 2^256 (low 256 bits)
|
||||||
|
// prod1 = (numerator >> 128) (high 256 bits)
|
||||||
|
//
|
||||||
|
// So prod = (prod1 * 2^256 + prod0).
|
||||||
|
uint256 prod0;
|
||||||
|
uint256 prod1;
|
||||||
|
unchecked {
|
||||||
|
prod0 = numerator << 128;
|
||||||
|
prod1 = numerator >> 128;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the high 256 bits are zero, the product fits in 256 bits.
|
||||||
|
// This is the cheap path: just do a normal 256-bit division.
|
||||||
|
if (prod1 == 0) {
|
||||||
|
unchecked {
|
||||||
|
// denominator was already checked for 0.
|
||||||
|
return prod0 / denominator;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point prod1 > 0, so the 512-bit product does not fit in a
|
||||||
|
// uint256. We need a full-precision 512/256 division:
|
||||||
|
//
|
||||||
|
// result = floor((prod1 * 2^256 + prod0) / denominator)
|
||||||
|
//
|
||||||
|
// and we must ensure the final result fits in uint256.
|
||||||
|
|
||||||
|
// Ensure result < 2^256. This is equivalent to requiring:
|
||||||
|
// denominator > prod1
|
||||||
|
// because if denominator <= prod1, then:
|
||||||
|
// (prod1 * 2^256) / denominator >= 2^256.
|
||||||
|
require(denominator > prod1, "Q128x128: overflow");
|
||||||
|
|
||||||
|
// Make division exact by subtracting the remainder from [prod1 prod0].
|
||||||
|
uint256 remainder;
|
||||||
|
assembly {
|
||||||
|
// remainder = (prod1 * 2^256 + prod0) % denominator
|
||||||
|
// Since we can only directly mod 256-bit values, we first mod
|
||||||
|
// `prod0`, then adjust using the high word.
|
||||||
|
remainder := mulmod(numerator, shl(128, 1), denominator)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now subtract `remainder` from the 512-bit product [prod1 prod0].
|
||||||
|
assembly {
|
||||||
|
// Subtract remainder from the low part; if it underflows, borrow
|
||||||
|
// 1 from the high part.
|
||||||
|
let borrow := lt(prod0, remainder)
|
||||||
|
prod0 := sub(prod0, remainder)
|
||||||
|
prod1 := sub(prod1, borrow)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Factor powers of two out of denominator to simplify the division.
|
||||||
|
//
|
||||||
|
// Let denominator = d * 2^shift, with d odd.
|
||||||
|
// We can divide prod0 by 2^shift cheaply (bit shift),
|
||||||
|
// then do an exact division by the odd d using modular inverse.
|
||||||
|
uint256 twos;
|
||||||
|
unchecked {
|
||||||
|
// largest power of two divisor of denominator
|
||||||
|
twos = denominator & (~denominator + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
assembly {
|
||||||
|
// Divide denominator by twos.
|
||||||
|
denominator := div(denominator, twos)
|
||||||
|
|
||||||
|
// Divide the low word by twos.
|
||||||
|
prod0 := div(prod0, twos)
|
||||||
|
|
||||||
|
// Adjust the high word so that the full 512-bit number is shifted
|
||||||
|
// by `twos`.
|
||||||
|
// twos = 2^k, so:
|
||||||
|
// combined = prod1 * 2^256 + prod0
|
||||||
|
// combined / twos =
|
||||||
|
// prod1 * 2^256 / twos + prod0 / twos
|
||||||
|
// and 2^256 / twos = 2^(256-k).
|
||||||
|
//
|
||||||
|
// Here we compute:
|
||||||
|
// twos = 2^256 / twos
|
||||||
|
twos := add(div(sub(0, twos), twos), 1)
|
||||||
|
|
||||||
|
// Now add the shifted high bits into prod0:
|
||||||
|
prod0 := or(prod0, mul(prod1, twos))
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point, denominator is odd and the 512-bit value
|
||||||
|
// has been squeezed into prod0 (prod1 is effectively 0).
|
||||||
|
|
||||||
|
// Compute the modular inverse of denominator modulo 2^256.
|
||||||
|
// This uses Newton-Raphson iteration:
|
||||||
|
//
|
||||||
|
// inv ≡ denominator^{-1} (mod 2^256)
|
||||||
|
//
|
||||||
|
// Starting from a seed for odd denominator:
|
||||||
|
// All operations must be unchecked as they rely on modular arithmetic.
|
||||||
|
unchecked {
|
||||||
|
uint256 inv = (3 * denominator) ^ 2;
|
||||||
|
|
||||||
|
// Perform Newton-Raphson iterations to refine the inverse.
|
||||||
|
// Starting from inv which is correct modulo 2^4, then each
|
||||||
|
// Newton-Raphson step doubles the number of correct bits:
|
||||||
|
// 2⁴ → 2⁸ → 2¹⁶ → 2³² → 2⁶⁴ →
|
||||||
|
// 2¹²⁸ → 2²⁵⁶
|
||||||
|
// Requiring six iterations for 256-bit precision:
|
||||||
|
inv *= 2 - denominator * inv;
|
||||||
|
inv *= 2 - denominator * inv;
|
||||||
|
inv *= 2 - denominator * inv;
|
||||||
|
inv *= 2 - denominator * inv;
|
||||||
|
inv *= 2 - denominator * inv;
|
||||||
|
inv *= 2 - denominator * inv;
|
||||||
|
|
||||||
|
// Now inv is the modular inverse of denominator mod 2^256.
|
||||||
|
// The exact division result is then:
|
||||||
|
//
|
||||||
|
// result = (prod0 * inv) mod 2^256
|
||||||
|
//
|
||||||
|
// which is just ordinary 256-bit multiplication.
|
||||||
|
result = prod0 * inv;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @notice Multiply a Fraction and a uint256 using full precision
|
||||||
|
function mul(ISwapAdapterTypes.Fraction memory rational, uint256 y)
|
||||||
|
internal
|
||||||
|
pure
|
||||||
|
returns (uint256 result)
|
||||||
|
{
|
||||||
|
return mulDiv(rational.numerator, y, rational.denominator);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @notice Full-precision mulDiv: computes floor(x * y / denominator)
|
||||||
|
/// with 512-bit intermediate precision to avoid overflow.
|
||||||
|
///
|
||||||
|
/// @dev Reverts if `denominator == 0` or the exact result >= 2^256.
|
||||||
|
/// The implementation mirrors the 512/256 division flow used by
|
||||||
|
/// `toQ128x128(uint256,uint256)`, but with a general multiplicand `y`
|
||||||
|
/// instead of the fixed 2^128 shift.
|
||||||
|
function mulDiv(uint256 x, uint256 y, uint256 denominator)
|
||||||
|
internal
|
||||||
|
pure
|
||||||
|
returns (uint256 result)
|
||||||
|
{
|
||||||
|
require(denominator != 0, "mulDiv: div by zero");
|
||||||
|
|
||||||
|
// Compute the 512-bit product [prod1 prod0] = x * y.
|
||||||
|
// mm = (x * y) mod (2^256 - 1)
|
||||||
|
// prod0 = (x * y) mod 2^256
|
||||||
|
// prod1 = (x * y - prod0 - (mm < prod0 ? 1 : 0)) / 2^256
|
||||||
|
uint256 prod0;
|
||||||
|
uint256 prod1;
|
||||||
|
assembly {
|
||||||
|
let mm := mulmod(x, y, not(0))
|
||||||
|
prod0 := mul(x, y)
|
||||||
|
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the high 256 bits are zero, we can do a simple 256-bit division.
|
||||||
|
if (prod1 == 0) {
|
||||||
|
unchecked {
|
||||||
|
return prod0 / denominator;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure result < 2^256. This is equivalent to requiring
|
||||||
|
// denominator > prod1.
|
||||||
|
require(denominator > prod1, "mulDiv: overflow");
|
||||||
|
|
||||||
|
// Make division exact by subtracting the remainder from [prod1 prod0].
|
||||||
|
uint256 remainder;
|
||||||
|
assembly {
|
||||||
|
remainder := mulmod(x, y, denominator)
|
||||||
|
// Subtract remainder from the low part; if it underflows, borrow 1
|
||||||
|
// from the high part.
|
||||||
|
let borrow := lt(prod0, remainder)
|
||||||
|
prod0 := sub(prod0, remainder)
|
||||||
|
prod1 := sub(prod1, borrow)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Factor powers of two out of denominator to simplify the division.
|
||||||
|
uint256 twos;
|
||||||
|
unchecked {
|
||||||
|
// largest power of two divisor of denominator
|
||||||
|
twos = denominator & (~denominator + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
assembly {
|
||||||
|
// Divide denominator by twos.
|
||||||
|
denominator := div(denominator, twos)
|
||||||
|
|
||||||
|
// Divide the low word by twos.
|
||||||
|
prod0 := div(prod0, twos)
|
||||||
|
|
||||||
|
// Compute twos = 2^256 / twos.
|
||||||
|
twos := add(div(sub(0, twos), twos), 1)
|
||||||
|
|
||||||
|
// Shift bits from the high word into the low word.
|
||||||
|
prod0 := or(prod0, mul(prod1, twos))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute modular inverse of the (now odd) denominator modulo 2^256
|
||||||
|
// via Newton-Raphson iterations.
|
||||||
|
// `inv` is correct to four bits, so we require six iterations
|
||||||
|
// to achieve 256-bit precision.
|
||||||
|
unchecked {
|
||||||
|
uint256 inv = (3 * denominator) ^ 2;
|
||||||
|
inv *= 2 - denominator * inv; // 2^8
|
||||||
|
inv *= 2 - denominator * inv; // 2^16
|
||||||
|
inv *= 2 - denominator * inv; // 2^32
|
||||||
|
inv *= 2 - denominator * inv; // 2^64
|
||||||
|
inv *= 2 - denominator * inv; // 2^128
|
||||||
|
inv *= 2 - denominator * inv; // 2^256
|
||||||
|
|
||||||
|
// Exact division: result = prod0 * inv mod 2^256
|
||||||
|
result = prod0 * inv;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
18
evm/src/liquidityparty/Funding.sol
Normal file
18
evm/src/liquidityparty/Funding.sol
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
// SPDX-License-Identifier: UNLICENSED
|
||||||
|
pragma solidity ^0.8.27;
|
||||||
|
|
||||||
|
library Funding {
|
||||||
|
/// @notice a constant passed to swap as the fundingSelector to indicate
|
||||||
|
/// that the payer has used regular ERC20 approvals to allow the pool to
|
||||||
|
/// move the necessary input tokens.
|
||||||
|
// Slither analysis of this line is literally wrong and broken. The extra zero digits are REQUIRED by Solidity since it is a bytes4 literal.
|
||||||
|
// slither-disable-next-line too-many-digits
|
||||||
|
bytes4 internal constant APPROVALS = 0x00000000;
|
||||||
|
|
||||||
|
/// @notice a constant passed to swap as the fundingSelector to indicate
|
||||||
|
/// that the payer has already sent sufficient input tokens to the pool
|
||||||
|
/// before calling swap, so no movement of input tokens is required.
|
||||||
|
// Slither analysis of this line is literally wrong and broken. The extra zero digits are REQUIRED by Solidity since it is a bytes4 literal.
|
||||||
|
// slither-disable-next-line too-many-digits
|
||||||
|
bytes4 internal constant PREFUNDING = 0x00000001;
|
||||||
|
}
|
||||||
24
evm/src/liquidityparty/IPartyInfo.sol
Normal file
24
evm/src/liquidityparty/IPartyInfo.sol
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
// SPDX-License-Identifier: UNLICENSED
|
||||||
|
pragma solidity ^0.8.27;
|
||||||
|
|
||||||
|
import {IPartyPool} from "./IPartyPool.sol";
|
||||||
|
|
||||||
|
interface IPartyInfo {
|
||||||
|
/// @notice returns true iff the pool is not killed and has been initialized
|
||||||
|
/// with liquidity.
|
||||||
|
function working(IPartyPool pool) external view returns (bool);
|
||||||
|
|
||||||
|
/// @notice Infinitesimal out-per-in marginal price for swap base->quote as
|
||||||
|
/// Q128.128, not adjusted for token decimals.
|
||||||
|
/// @dev Returns p_base / p_quote in Q128.128 format, scaled to external
|
||||||
|
/// units by (denom_quote / denom_base). This aligns with the swap kernel so
|
||||||
|
/// that, fee-free, avg(out/in) ≤ price(base, quote) for exact-in trades.
|
||||||
|
/// @param baseTokenIndex index of the input (base) asset
|
||||||
|
/// @param quoteTokenIndex index of the output (quote) asset
|
||||||
|
/// @return price Q128.128 value equal to out-per-in (j per i)
|
||||||
|
function price(
|
||||||
|
IPartyPool pool,
|
||||||
|
uint256 baseTokenIndex,
|
||||||
|
uint256 quoteTokenIndex
|
||||||
|
) external view returns (uint256);
|
||||||
|
}
|
||||||
18
evm/src/liquidityparty/IPartyPlanner.sol
Normal file
18
evm/src/liquidityparty/IPartyPlanner.sol
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
// SPDX-License-Identifier: UNLICENSED
|
||||||
|
pragma solidity ^0.8.27;
|
||||||
|
|
||||||
|
import {IPartyPool} from "./IPartyPool.sol";
|
||||||
|
|
||||||
|
/// @title IPartyPlanner
|
||||||
|
/// @notice Interface for factory contract for creating and tracking PartyPool
|
||||||
|
/// instances
|
||||||
|
interface IPartyPlanner {
|
||||||
|
/// @notice Retrieves a page of pool addresses
|
||||||
|
/// @param offset Starting index for pagination
|
||||||
|
/// @param limit Maximum number of items to return
|
||||||
|
/// @return pools Array of pool addresses for the requested page
|
||||||
|
function getAllPools(uint256 offset, uint256 limit)
|
||||||
|
external
|
||||||
|
view
|
||||||
|
returns (IPartyPool[] memory pools);
|
||||||
|
}
|
||||||
86
evm/src/liquidityparty/IPartyPool.sol
Normal file
86
evm/src/liquidityparty/IPartyPool.sol
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
pragma solidity ^0.8.27;
|
||||||
|
|
||||||
|
/// @title PartyPool - LMSR-backed multi-asset pool with LP ERC20 token
|
||||||
|
/// @notice A multi-asset liquidity pool backed by the LMSRStabilized pricing
|
||||||
|
/// model. The pool issues an ERC20 LP token representing proportional
|
||||||
|
/// ownership.
|
||||||
|
/// It supports:
|
||||||
|
/// - Proportional minting and burning of LP _tokens,
|
||||||
|
/// - Single-token mint (swapMint) and single-asset withdrawal (burnSwap),
|
||||||
|
/// - Exact-input swaps and swaps-to-price-limits,
|
||||||
|
/// - Flash loans via a callback interface.
|
||||||
|
interface IPartyPool {
|
||||||
|
/// @notice If a security problem is found, the vault owner may call this
|
||||||
|
/// function to permanently disable swap and mint functionality, leaving
|
||||||
|
/// only burns (withdrawals) working.
|
||||||
|
function killed() external view returns (bool);
|
||||||
|
|
||||||
|
/// @notice Returns the number of tokens (n) in the pool.
|
||||||
|
function numTokens() external view returns (uint256);
|
||||||
|
|
||||||
|
/// @notice Returns the list of all token addresses in the pool (copy).
|
||||||
|
function allTokens() external view returns (address[] memory);
|
||||||
|
|
||||||
|
/// @notice External view to quote exact-in swap amounts (gross input incl.
|
||||||
|
/// fee and output), matching swap() computations @param inputTokenIndex
|
||||||
|
/// index of input token
|
||||||
|
/// @param outputTokenIndex index of output token
|
||||||
|
/// @param maxAmountIn maximum gross input allowed (inclusive of fee)
|
||||||
|
/// @param limitPrice maximum acceptable marginal price (pass 0 to ignore)
|
||||||
|
/// @return amountIn gross input amount to transfer (includes fee),
|
||||||
|
/// amountOut output amount user would receive, inFee fee taken from input
|
||||||
|
/// amount
|
||||||
|
function swapAmounts(
|
||||||
|
uint256 inputTokenIndex,
|
||||||
|
uint256 outputTokenIndex,
|
||||||
|
uint256 maxAmountIn,
|
||||||
|
int128 limitPrice
|
||||||
|
) external view returns (uint256 amountIn, uint256 amountOut, uint256 inFee);
|
||||||
|
|
||||||
|
/// @notice Swap input token inputTokenIndex -> token outputTokenIndex.
|
||||||
|
/// Payer must approve token inputTokenIndex. @param payer address of the
|
||||||
|
/// account that pays for the swap
|
||||||
|
/// @param fundingSelector If set to USE_APPROVALS, then the payer must use
|
||||||
|
/// regular ERC20 approvals to authorize the pool to move the required input
|
||||||
|
/// amount. If this fundingSelector is USE_PREFUNDING, then all of the input
|
||||||
|
/// amount is expected to have already been sent to the pool and no
|
||||||
|
/// additional transfers are needed. Refunds of excess input amount are NOT
|
||||||
|
/// provided and it is illegal to use this funding method with a limit
|
||||||
|
/// price. Otherwise, for any other fundingSelector value, a callback style
|
||||||
|
/// funding mechanism is used where the given selector is invoked on the
|
||||||
|
/// payer, passing the arguments of (address inputToken, uint256
|
||||||
|
/// inputAmount). The callback function must send the given amount of input
|
||||||
|
/// coin to the pool in order to continue the swap transaction, otherwise
|
||||||
|
/// "Insufficient funds" is thrown. @param receiver address that will
|
||||||
|
/// receive the output tokens
|
||||||
|
/// @param inputTokenIndex index of input asset
|
||||||
|
/// @param outputTokenIndex index of output asset
|
||||||
|
/// @param maxAmountIn maximum amount of token inputTokenIndex (uint256) to
|
||||||
|
/// transfer in (inclusive of fees) @param limitPrice maximum acceptable
|
||||||
|
/// marginal price (64.64 fixed point). Pass 0 to ignore.
|
||||||
|
/// @param deadline timestamp after which the transaction will revert. Pass
|
||||||
|
/// 0 to ignore. @param unwrap If true, then any output of wrapper token
|
||||||
|
/// will be unwrapped and native ETH sent to the receiver.
|
||||||
|
/// @param cbData callback data if fundingSelector is of the callback type.
|
||||||
|
/// @return amountIn actual input used (uint256), amountOut actual output
|
||||||
|
/// sent (uint256), inFee fee taken from the input (uint256)
|
||||||
|
function swap(
|
||||||
|
address payer,
|
||||||
|
bytes4 fundingSelector,
|
||||||
|
address receiver,
|
||||||
|
uint256 inputTokenIndex,
|
||||||
|
uint256 outputTokenIndex,
|
||||||
|
uint256 maxAmountIn,
|
||||||
|
int128 limitPrice,
|
||||||
|
uint256 deadline,
|
||||||
|
bool unwrap,
|
||||||
|
bytes memory cbData
|
||||||
|
)
|
||||||
|
external
|
||||||
|
payable
|
||||||
|
returns (uint256 amountIn, uint256 amountOut, uint256 inFee);
|
||||||
|
|
||||||
|
/// @notice Effective combined fee in ppm for the given asset pair (i as
|
||||||
|
/// input, j as output).
|
||||||
|
function fee(uint256 i, uint256 j) external view returns (uint256);
|
||||||
|
}
|
||||||
258
evm/src/liquidityparty/LiquidityPartySwapAdapter.sol
Normal file
258
evm/src/liquidityparty/LiquidityPartySwapAdapter.sol
Normal file
@@ -0,0 +1,258 @@
|
|||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
pragma solidity ^0.8.27;
|
||||||
|
|
||||||
|
import {
|
||||||
|
IERC20
|
||||||
|
} from "../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
|
||||||
|
import {
|
||||||
|
SafeERC20
|
||||||
|
} from "../../lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
|
||||||
|
import {ISwapAdapter} from "../interfaces/ISwapAdapter.sol";
|
||||||
|
import {Funding} from "./Funding.sol";
|
||||||
|
import {IPartyInfo} from "./IPartyInfo.sol";
|
||||||
|
import {IPartyPlanner} from "./IPartyPlanner.sol";
|
||||||
|
import {IPartyPool} from "./IPartyPool.sol";
|
||||||
|
import {console2} from "../../lib/forge-std/src/console2.sol";
|
||||||
|
|
||||||
|
contract LiquidityPartySwapAdapter is ISwapAdapter {
|
||||||
|
using SafeERC20 for IERC20;
|
||||||
|
|
||||||
|
// Forge lint wants immutables to be all caps. Slither wants them to be
|
||||||
|
// mixed case. Why do we care about pedantic linters? The Solidity style
|
||||||
|
// guide mentions "constants" but never "immutables." Faced with an
|
||||||
|
// irresolvable linter conflict, I chose to disable the slither linter,
|
||||||
|
// since its detection of immutables as constants seems to be broken.
|
||||||
|
// slither-disable-next-line naming-convention
|
||||||
|
IPartyPlanner public immutable PLANNER;
|
||||||
|
// slither-disable-next-line naming-convention
|
||||||
|
IPartyInfo public immutable INFO;
|
||||||
|
|
||||||
|
constructor(IPartyPlanner planner, IPartyInfo info) {
|
||||||
|
PLANNER = planner;
|
||||||
|
INFO = info;
|
||||||
|
}
|
||||||
|
|
||||||
|
function price(
|
||||||
|
bytes32 poolId,
|
||||||
|
address sellToken,
|
||||||
|
address buyToken,
|
||||||
|
uint256[] memory specifiedAmounts
|
||||||
|
) external view override returns (Fraction[] memory prices) {
|
||||||
|
IPartyPool pool = _poolFromId(poolId);
|
||||||
|
(uint256 indexIn, uint256 indexOut) =
|
||||||
|
_tokenIndexes(pool, sellToken, buyToken);
|
||||||
|
prices = new Fraction[](specifiedAmounts.length);
|
||||||
|
for (uint256 i = 0; i < specifiedAmounts.length; i++) {
|
||||||
|
uint256 amount = specifiedAmounts[i];
|
||||||
|
if (amount == 0) {
|
||||||
|
// Marginal price support
|
||||||
|
prices[i] = _marginalPrice(pool, indexIn, indexOut);
|
||||||
|
} else {
|
||||||
|
// Regular slippage calculation
|
||||||
|
// slither-disable-next-line unused-return calls-loop
|
||||||
|
(
|
||||||
|
uint256 amountIn,
|
||||||
|
uint256 amountOut, /*uint256 inFee*/
|
||||||
|
) = pool.swapAmounts(indexIn, indexOut, amount, 0);
|
||||||
|
prices[i].numerator = amountOut;
|
||||||
|
prices[i].denominator = amountIn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function swap(
|
||||||
|
bytes32 poolId,
|
||||||
|
address sellToken,
|
||||||
|
address buyToken,
|
||||||
|
OrderSide,
|
||||||
|
/*side*/
|
||||||
|
uint256 specifiedAmount
|
||||||
|
) external returns (Trade memory trade) {
|
||||||
|
// Setup
|
||||||
|
address swapper = msg.sender;
|
||||||
|
IPartyPool pool = _poolFromId(poolId);
|
||||||
|
// This require should never trigger if the substreams module correctly
|
||||||
|
// removes components that were killed.
|
||||||
|
if (!INFO.working(pool)) {
|
||||||
|
revert Unavailable("LiqP pool not working");
|
||||||
|
}
|
||||||
|
(uint256 indexIn, uint256 indexOut) =
|
||||||
|
_tokenIndexes(pool, sellToken, buyToken);
|
||||||
|
|
||||||
|
// Transfer and Swap
|
||||||
|
uint256 startingGas = gasleft();
|
||||||
|
IERC20(sellToken)
|
||||||
|
.safeTransferFrom(swapper, address(pool), specifiedAmount);
|
||||||
|
// slither-disable-next-line unused-return
|
||||||
|
try pool.swap(
|
||||||
|
address(0),
|
||||||
|
Funding.PREFUNDING,
|
||||||
|
swapper,
|
||||||
|
indexIn,
|
||||||
|
indexOut,
|
||||||
|
specifiedAmount,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
""
|
||||||
|
) returns (
|
||||||
|
uint256 amountIn, uint256 amountOut, uint256 inFee
|
||||||
|
) {
|
||||||
|
uint256 endingGas = gasleft();
|
||||||
|
uint256 gasUsed = startingGas - endingGas;
|
||||||
|
Fraction memory poolPrice = _marginalPrice(pool, indexIn, indexOut);
|
||||||
|
console2.log("Successfully swapped", amountOut);
|
||||||
|
// forge-lint: disable-next-line(named-struct-fields)
|
||||||
|
return Trade(amountOut, gasUsed, poolPrice);
|
||||||
|
} catch (bytes memory reason) {
|
||||||
|
bytes32 hash = keccak256(reason);
|
||||||
|
if (hash == keccak256("swap: input too small after fee")) {
|
||||||
|
revert TooSmall(0);
|
||||||
|
} else if (
|
||||||
|
hash == keccak256("swap: transfer exceeds max")
|
||||||
|
|| hash
|
||||||
|
== keccak256("LMSR: a/b too large (would overflow exp)")
|
||||||
|
|| hash == keccak256("swap: transfer exceeds max")
|
||||||
|
) {
|
||||||
|
revert LimitExceeded(0); // todo size
|
||||||
|
} else if (hash == keccak256("killed")) {
|
||||||
|
revert Unavailable("pool has been permanently killed");
|
||||||
|
} else if (hash == keccak256("LMSR: size metric zero")) {
|
||||||
|
revert Unavailable("pool currently has no LP assets");
|
||||||
|
} else if (hash == keccak256("LMSR: limitPrice <= current price")) {
|
||||||
|
revert InvalidOrder("limit price is below current price");
|
||||||
|
} else if (
|
||||||
|
hash == keccak256("LMSR: ratio<=0") // invalid limit price
|
||||||
|
) {
|
||||||
|
revert InvalidOrder("limit price cannot be negative");
|
||||||
|
} else {
|
||||||
|
console2.log("Unhandled error", string(reason));
|
||||||
|
// re-raise
|
||||||
|
assembly {
|
||||||
|
revert(add(reason, 0x20), mload(reason))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLimits(bytes32 poolId, address sellToken, address buyToken)
|
||||||
|
external
|
||||||
|
view
|
||||||
|
returns (uint256[] memory limits)
|
||||||
|
{
|
||||||
|
// We arbitrarily limit the amounts like Uniswap V2 does, to make the
|
||||||
|
// test cases work. There is no theoretical limit on the input amount.
|
||||||
|
// forge-lint: disable-next-line(unsafe-typecast)
|
||||||
|
address pool = address(bytes20(poolId));
|
||||||
|
limits = new uint256[](2);
|
||||||
|
|
||||||
|
// input token limit: Theoretically unlimited, but artificially limited
|
||||||
|
// here to appease Tycho's test cases. Instead of estimating actual
|
||||||
|
// input limits based on a maximum target slippage, we merely return the
|
||||||
|
// current
|
||||||
|
// inventory of input token. Even for large stablecoin pools with a
|
||||||
|
// kappa near 1, this input amount should result in an "unreasonably"
|
||||||
|
// high slippage:
|
||||||
|
// Pool Size => Slippage for inputAmount=reserveBalance and kappa=1
|
||||||
|
// 2 => 33.7%
|
||||||
|
// 10 => 9.2%
|
||||||
|
// 25 => 3.9%
|
||||||
|
// 50 => 2.1%
|
||||||
|
// See the commented-out method below for an exact computation of the
|
||||||
|
// maximum input amount for a given pool configuration and target
|
||||||
|
// slippage.
|
||||||
|
limits[0] = IERC20(sellToken).balanceOf(pool);
|
||||||
|
|
||||||
|
// output token limit: the pool's current balance (an overestimate)
|
||||||
|
limits[1] = IERC20(buyToken).balanceOf(pool);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCapabilities(
|
||||||
|
bytes32,
|
||||||
|
/*poolId*/
|
||||||
|
address,
|
||||||
|
/*sellToken*/
|
||||||
|
address /*buyToken*/
|
||||||
|
)
|
||||||
|
external
|
||||||
|
pure
|
||||||
|
returns (Capability[] memory capabilities)
|
||||||
|
{
|
||||||
|
capabilities = new Capability[](3);
|
||||||
|
capabilities[0] = Capability.SellOrder;
|
||||||
|
capabilities[1] = Capability.PriceFunction;
|
||||||
|
capabilities[2] = Capability.MarginalPrice;
|
||||||
|
return capabilities;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTokens(bytes32 poolId)
|
||||||
|
external
|
||||||
|
view
|
||||||
|
returns (address[] memory tokens)
|
||||||
|
{
|
||||||
|
IPartyPool pool = _poolFromId(poolId);
|
||||||
|
return pool.allTokens();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPoolIds(uint256 offset, uint256 limit)
|
||||||
|
external
|
||||||
|
view
|
||||||
|
returns (bytes32[] memory ids)
|
||||||
|
{
|
||||||
|
IPartyPool[] memory pools = PLANNER.getAllPools(offset, limit);
|
||||||
|
ids = new bytes32[](pools.length);
|
||||||
|
for (uint256 i = 0; i < pools.length; i++) {
|
||||||
|
ids[i] = bytes32(uint256(uint160(address(pools[i]))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Internal Helpers
|
||||||
|
//
|
||||||
|
|
||||||
|
uint256 private constant NONE = type(uint256).max;
|
||||||
|
|
||||||
|
/// @dev Liquidity Party pools identify tokens by index rather than address,
|
||||||
|
/// saving 5200 gas per swap.
|
||||||
|
function _tokenIndexes(IPartyPool pool, address sellToken, address buyToken)
|
||||||
|
internal
|
||||||
|
view
|
||||||
|
returns (uint256 indexIn, uint256 indexOut)
|
||||||
|
{
|
||||||
|
indexIn = NONE;
|
||||||
|
indexOut = NONE;
|
||||||
|
address[] memory tokens = pool.allTokens();
|
||||||
|
uint256 numTokens = pool.numTokens();
|
||||||
|
for (uint256 i = 0; i < numTokens; i++) {
|
||||||
|
if (tokens[i] == sellToken) {
|
||||||
|
indexIn = i;
|
||||||
|
} else if (tokens[i] == buyToken) {
|
||||||
|
indexOut = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// This should never happen if the token metadata was correctly loaded
|
||||||
|
// by substreams
|
||||||
|
require(indexIn != NONE && indexOut != NONE, "tokens not in pool");
|
||||||
|
}
|
||||||
|
|
||||||
|
function _marginalPrice(IPartyPool pool, uint256 indexIn, uint256 indexOut)
|
||||||
|
internal
|
||||||
|
view
|
||||||
|
returns (Fraction memory poolPrice)
|
||||||
|
{
|
||||||
|
// Liquidity Party prices are Q128.128 fixed point format
|
||||||
|
// slither-disable-next-line calls-loop
|
||||||
|
uint256 price128x128 = INFO.price(pool, indexIn, indexOut);
|
||||||
|
uint256 feePpm = pool.fee(indexIn, indexOut);
|
||||||
|
// price128x128 *= 1_000_000 - feePpm;
|
||||||
|
// price128x128 /= 1_000_000;
|
||||||
|
// forge-lint: disable-next-line(unsafe-typecast,named-struct-fields)
|
||||||
|
return Fraction(price128x128, 1 << 128);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _poolFromId(bytes32 poolId) internal pure returns (IPartyPool) {
|
||||||
|
// forge-lint: disable-next-line(unsafe-typecast)
|
||||||
|
return IPartyPool(address(bytes20(poolId)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
25
evm/src/liquidityparty/manifest.yaml
Normal file
25
evm/src/liquidityparty/manifest.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
author:
|
||||||
|
name: Tim Olson
|
||||||
|
email: tim@dexorder.com
|
||||||
|
|
||||||
|
constants:
|
||||||
|
# This is our median gas cost for a 20-asset pool. Gas varies by pool size from 120k to 200k.
|
||||||
|
protocol_gas: 147238
|
||||||
|
capabilities:
|
||||||
|
- SellSide
|
||||||
|
- PriceFunction
|
||||||
|
- MarginalPrice
|
||||||
|
|
||||||
|
contract: LiquidityPartySwapAdapter.sol
|
||||||
|
|
||||||
|
# Deployment instances used to generate chain specific bytecode.
|
||||||
|
instances:
|
||||||
|
- chain:
|
||||||
|
name: sepolia
|
||||||
|
id: 11155111
|
||||||
|
arguments:
|
||||||
|
- "0x77C29B1790D18A3AD269BcE09b7dB1074911Dcb6" # PartyPlanner
|
||||||
|
- "0x784BA6cD19B484bEE9Cee880B18b57fC6e8b2D5c" # PartyInfo
|
||||||
|
|
||||||
|
# We do implement getPoolIds() and getTokens(), so explicit swap tests are not needed.
|
||||||
|
tests: {}
|
||||||
@@ -12,7 +12,6 @@ contract AdapterTest is Test, ISwapAdapterTypes {
|
|||||||
using FractionMath for Fraction;
|
using FractionMath for Fraction;
|
||||||
using EfficientERC20 for IERC20;
|
using EfficientERC20 for IERC20;
|
||||||
|
|
||||||
uint256 constant pricePrecision = 10e24;
|
|
||||||
string[] public stringPctgs = ["0%", "0.1%", "50%", "100%"];
|
string[] public stringPctgs = ["0%", "0.1%", "50%", "100%"];
|
||||||
|
|
||||||
// @notice Test the behavior of a swap adapter for a list of pools
|
// @notice Test the behavior of a swap adapter for a list of pools
|
||||||
@@ -44,9 +43,10 @@ contract AdapterTest is Test, ISwapAdapterTypes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Prices should:
|
// Prices should:
|
||||||
// 1. Be monotonic decreasing
|
// 1. Be monotonic decreasing (within rounding tolerance)
|
||||||
// 2. Be positive
|
// 2. Be positive
|
||||||
// 3. Always be >= the executed price and >= the price after the swap
|
// 3. Always be >= the executed price and >= the price after the swap
|
||||||
|
// (within rounding tolerance)
|
||||||
function testPricesForPair(
|
function testPricesForPair(
|
||||||
ISwapAdapter adapter,
|
ISwapAdapter adapter,
|
||||||
bytes32 poolId,
|
bytes32 poolId,
|
||||||
@@ -76,7 +76,10 @@ contract AdapterTest is Test, ISwapAdapterTypes {
|
|||||||
Fraction[] memory prices =
|
Fraction[] memory prices =
|
||||||
adapter.price(poolId, tokenIn, tokenOut, amounts);
|
adapter.price(poolId, tokenIn, tokenOut, amounts);
|
||||||
assertGt(
|
assertGt(
|
||||||
fractionToInt(prices[0]),
|
fractionToInt(prices[0])
|
||||||
|
// within rounding tolerance
|
||||||
|
* (amounts[amounts.length - 1] + 1)
|
||||||
|
/ amounts[amounts.length - 1],
|
||||||
fractionToInt(prices[prices.length - 1]),
|
fractionToInt(prices[prices.length - 1]),
|
||||||
"Price at limit should be smaller than price at 0"
|
"Price at limit should be smaller than price at 0"
|
||||||
);
|
);
|
||||||
@@ -92,7 +95,6 @@ contract AdapterTest is Test, ISwapAdapterTypes {
|
|||||||
uint256 priceAtZero = fractionToInt(prices[0]);
|
uint256 priceAtZero = fractionToInt(prices[0]);
|
||||||
console2.log("TEST: Price at 0: %d", priceAtZero);
|
console2.log("TEST: Price at 0: %d", priceAtZero);
|
||||||
|
|
||||||
Trade memory trade;
|
|
||||||
deal(tokenIn, address(this), 5 * amounts[amounts.length - 1]);
|
deal(tokenIn, address(this), 5 * amounts[amounts.length - 1]);
|
||||||
|
|
||||||
uint256 initialState = vm.snapshot();
|
uint256 initialState = vm.snapshot();
|
||||||
@@ -104,50 +106,93 @@ contract AdapterTest is Test, ISwapAdapterTypes {
|
|||||||
amounts[j]
|
amounts[j]
|
||||||
);
|
);
|
||||||
uint256 priceAtAmount = fractionToInt(prices[j]);
|
uint256 priceAtAmount = fractionToInt(prices[j]);
|
||||||
|
// We allow the assertions to tolerate rounding errors
|
||||||
|
// not greater than `1/amounts[j]`
|
||||||
|
uint256 toleranceDenominator = amounts[j];
|
||||||
|
|
||||||
console2.log("TEST: Swapping %d of %s", amounts[j], tokenIn);
|
console2.log("TEST: Swapping %d of %s", amounts[j], tokenIn);
|
||||||
trade = adapter.swap(
|
try adapter.swap(
|
||||||
poolId, tokenIn, tokenOut, OrderSide.Sell, amounts[j]
|
poolId, tokenIn, tokenOut, OrderSide.Sell, amounts[j]
|
||||||
);
|
) returns (
|
||||||
uint256 executedPrice =
|
Trade memory trade
|
||||||
trade.calculatedAmount * pricePrecision / amounts[j];
|
) {
|
||||||
uint256 priceAfterSwap = fractionToInt(trade.price);
|
uint256 executedPrice = Fraction(
|
||||||
console2.log("TEST: - Executed price: %d", executedPrice);
|
trade.calculatedAmount, amounts[j]
|
||||||
console2.log("TEST: - Price at amount: %d", priceAtAmount);
|
).toQ128x128();
|
||||||
console2.log("TEST: - Price after swap: %d", priceAfterSwap);
|
uint256 priceAfterSwap = fractionToInt(trade.price);
|
||||||
|
console2.log("TEST: - Executed price: %d", executedPrice);
|
||||||
|
console2.log("TEST: - Price at amount: %d", priceAtAmount);
|
||||||
|
console2.log("TEST: - Price after swap: %d", priceAfterSwap);
|
||||||
|
|
||||||
if (hasPriceImpact) {
|
if (hasPriceImpact) {
|
||||||
assertGe(
|
assertGeTol(
|
||||||
executedPrice,
|
executedPrice,
|
||||||
priceAtAmount,
|
priceAtAmount,
|
||||||
"Price should be greated than executed price."
|
toleranceDenominator,
|
||||||
);
|
"Price should be greater than executed price."
|
||||||
assertGt(
|
);
|
||||||
executedPrice,
|
assertGtTol(
|
||||||
priceAfterSwap,
|
executedPrice,
|
||||||
"Executed price should be greater than price after swap."
|
priceAfterSwap,
|
||||||
);
|
toleranceDenominator,
|
||||||
assertGt(
|
"Executed price should be greater than price after swap."
|
||||||
priceAtZero,
|
);
|
||||||
executedPrice,
|
assertGtTol(
|
||||||
"Price should be greated than price after swap."
|
priceAtZero,
|
||||||
);
|
executedPrice,
|
||||||
} else {
|
toleranceDenominator,
|
||||||
assertGe(
|
"Price should be greater than price after swap."
|
||||||
priceAtZero,
|
);
|
||||||
priceAfterSwap,
|
} else {
|
||||||
"Executed price should be or equal to price after swap."
|
assertGeTol(
|
||||||
);
|
priceAtZero,
|
||||||
assertGe(
|
priceAfterSwap,
|
||||||
priceAtZero,
|
toleranceDenominator,
|
||||||
priceAtAmount,
|
"Executed price should be or equal to price after swap."
|
||||||
"Executed price should be or equal to price after swap."
|
);
|
||||||
);
|
assertGeTol(
|
||||||
assertGe(
|
priceAtZero,
|
||||||
priceAtZero,
|
priceAtAmount,
|
||||||
executedPrice,
|
toleranceDenominator,
|
||||||
"Price should be or equal to price after swap."
|
"Executed price should be or equal to price after swap."
|
||||||
);
|
);
|
||||||
|
assertGeTol(
|
||||||
|
priceAtZero,
|
||||||
|
executedPrice,
|
||||||
|
toleranceDenominator,
|
||||||
|
"Price should be or equal to price after swap."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (bytes memory reason) {
|
||||||
|
(bool isTooSmall, uint256 lowerLimit) =
|
||||||
|
decodeTooSmallError(reason);
|
||||||
|
(bool isLimitExceeded, uint256 limit) =
|
||||||
|
decodeLimitExceededError(reason);
|
||||||
|
|
||||||
|
if (isTooSmall) {
|
||||||
|
// We allow a TooSmall exception to occur for the smallest
|
||||||
|
// amount only.
|
||||||
|
if (j == 1) {
|
||||||
|
console2.log(
|
||||||
|
"TEST: TooSmall exception tolerated for smallest amount"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
revert(
|
||||||
|
"TEST: TooSmall thrown for a significantly sized amount"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (isLimitExceeded) {
|
||||||
|
// We never allow LimitExceeded to be thrown, since all
|
||||||
|
// amounts should be within the stated limits.
|
||||||
|
revert(
|
||||||
|
"TEST: LimitExceeded thrown for an amount within limits"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// any other revert reason bubbles up
|
||||||
|
assembly {
|
||||||
|
revert(add(reason, 32), mload(reason))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
vm.revertTo(initialState);
|
vm.revertTo(initialState);
|
||||||
@@ -185,24 +230,87 @@ contract AdapterTest is Test, ISwapAdapterTypes {
|
|||||||
);
|
);
|
||||||
uint256[] memory aboveLimitArray = new uint256[](1);
|
uint256[] memory aboveLimitArray = new uint256[](1);
|
||||||
aboveLimitArray[0] = amountAboveLimit;
|
aboveLimitArray[0] = amountAboveLimit;
|
||||||
|
bool supportsLimitExceeded = false;
|
||||||
|
|
||||||
try adapter.price(poolId, tokenIn, tokenOut, aboveLimitArray) {
|
try adapter.price(poolId, tokenIn, tokenOut, aboveLimitArray) {
|
||||||
revert(
|
revert(
|
||||||
"Pool shouldn't be able to fetch prices above the sell limit"
|
"Pool shouldn't be able to fetch prices above the sell limit"
|
||||||
);
|
);
|
||||||
} catch Error(string memory s) {
|
} catch (bytes memory reason) {
|
||||||
console2.log(
|
(bool isTooSmall, uint256 lowerLimit) = decodeTooSmallError(reason);
|
||||||
"TEST: Expected error when fetching price above limit: %s", s
|
(bool isLimitExceeded, uint256 limit) =
|
||||||
);
|
decodeLimitExceededError(reason);
|
||||||
|
|
||||||
|
if (isLimitExceeded) {
|
||||||
|
supportsLimitExceeded = true;
|
||||||
|
console2.log(
|
||||||
|
"TEST: LimitExceeded supported! Thrown when fetching price above limit: %i",
|
||||||
|
limit
|
||||||
|
);
|
||||||
|
} else if (isTooSmall) {
|
||||||
|
console2.log(
|
||||||
|
"TEST: UNEXPECTED TooSmall error when fetching price below limit: %i",
|
||||||
|
lowerLimit
|
||||||
|
);
|
||||||
|
revert TooSmall(lowerLimit);
|
||||||
|
} else if (
|
||||||
|
reason.length >= 4
|
||||||
|
&& bytes4(reason) == bytes4(keccak256("Error(string)"))
|
||||||
|
) {
|
||||||
|
string memory s = abi.decode(
|
||||||
|
sliceBytes(reason, 4, reason.length - 4), (string)
|
||||||
|
);
|
||||||
|
console2.log(
|
||||||
|
"TEST: Expected error when fetching price above limit: %s",
|
||||||
|
s
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Unexpected error type: re-raise.
|
||||||
|
assembly {
|
||||||
|
revert(add(reason, 32), mload(reason))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
try adapter.swap(
|
try adapter.swap(
|
||||||
poolId, tokenIn, tokenOut, OrderSide.Sell, aboveLimitArray[0]
|
poolId, tokenIn, tokenOut, OrderSide.Sell, aboveLimitArray[0]
|
||||||
) {
|
) {
|
||||||
revert("Pool shouldn't be able to swap above the sell limit");
|
revert("Pool shouldn't be able to swap above the sell limit");
|
||||||
} catch Error(string memory s) {
|
} catch (bytes memory reason) {
|
||||||
console2.log(
|
(bool isTooSmall, uint256 lowerLimit) = decodeTooSmallError(reason);
|
||||||
"TEST: Expected error when swapping above limit: %s", s
|
(bool isLimitExceeded, uint256 limit) =
|
||||||
);
|
decodeLimitExceededError(reason);
|
||||||
|
|
||||||
|
if (isLimitExceeded) {
|
||||||
|
supportsLimitExceeded = true;
|
||||||
|
console2.log(
|
||||||
|
"TEST: LimitExceeded supported! Thrown when swapping above limit: %i",
|
||||||
|
limit
|
||||||
|
);
|
||||||
|
} else if (isTooSmall) {
|
||||||
|
console2.log(
|
||||||
|
"TEST: UNEXPECTED TooSmall error when swapping above limit: %i",
|
||||||
|
lowerLimit
|
||||||
|
);
|
||||||
|
revert TooSmall(lowerLimit);
|
||||||
|
} else if (
|
||||||
|
reason.length >= 4
|
||||||
|
&& bytes4(reason) == bytes4(keccak256("Error(string)"))
|
||||||
|
) {
|
||||||
|
string memory s = abi.decode(
|
||||||
|
sliceBytes(reason, 4, reason.length - 4), (string)
|
||||||
|
);
|
||||||
|
console2.log(
|
||||||
|
"TEST: Expected error when swapping above limit: %s", s
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Unexpected error type: re-raise.
|
||||||
|
assembly {
|
||||||
|
revert(add(reason, 32), mload(reason))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (supportsLimitExceeded) {
|
||||||
|
console.log(unicode"Adapter supports LimitExceeded ✓");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,7 +352,7 @@ contract AdapterTest is Test, ISwapAdapterTypes {
|
|||||||
pure
|
pure
|
||||||
returns (uint256)
|
returns (uint256)
|
||||||
{
|
{
|
||||||
return price.numerator * pricePrecision / price.denominator;
|
return price.toQ128x128();
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasCapability(
|
function hasCapability(
|
||||||
@@ -259,4 +367,85 @@ contract AdapterTest is Test, ISwapAdapterTypes {
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Custom Error Helper Functions
|
||||||
|
// TODO should we expose these in a better location / library for solvers to
|
||||||
|
// also leverage?
|
||||||
|
|
||||||
|
// Helper function to check if error is TooSmall and decode it
|
||||||
|
function decodeTooSmallError(bytes memory reason)
|
||||||
|
internal
|
||||||
|
pure
|
||||||
|
returns (bool, uint256)
|
||||||
|
{
|
||||||
|
if (reason.length >= 4 && bytes4(reason) == TooSmall.selector) {
|
||||||
|
if (reason.length == 36) {
|
||||||
|
uint256 lowerLimit =
|
||||||
|
abi.decode(sliceBytes(reason, 4, 32), (uint256));
|
||||||
|
return (true, lowerLimit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (false, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to check if error is LimitExceeded and decode it
|
||||||
|
function decodeLimitExceededError(bytes memory reason)
|
||||||
|
internal
|
||||||
|
pure
|
||||||
|
returns (bool, uint256)
|
||||||
|
{
|
||||||
|
if (reason.length >= 4 && bytes4(reason) == LimitExceeded.selector) {
|
||||||
|
if (reason.length == 36) {
|
||||||
|
uint256 limit = abi.decode(sliceBytes(reason, 4, 32), (uint256));
|
||||||
|
return (true, limit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (false, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to slice bytes
|
||||||
|
function sliceBytes(bytes memory data, uint256 start, uint256 length)
|
||||||
|
internal
|
||||||
|
pure
|
||||||
|
returns (bytes memory)
|
||||||
|
{
|
||||||
|
bytes memory result = new bytes(length);
|
||||||
|
for (uint256 i = 0; i < length; i++) {
|
||||||
|
result[i] = data[start + i];
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Helper functions to assert with tolerance
|
||||||
|
//
|
||||||
|
|
||||||
|
function assertGeTol(
|
||||||
|
uint256 a,
|
||||||
|
uint256 b,
|
||||||
|
uint256 toleranceDenominator,
|
||||||
|
string memory errorMessage
|
||||||
|
) internal {
|
||||||
|
// The tolerance is `1 / toleranceDenominator`, so we increase the value
|
||||||
|
// of `a` by this amount. adjustedA = a * (denom+1) / denom
|
||||||
|
uint256 adjustedA = FractionMath.mulDiv(
|
||||||
|
a, toleranceDenominator + 1, toleranceDenominator
|
||||||
|
);
|
||||||
|
assertGe(adjustedA, b, errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertGtTol(
|
||||||
|
uint256 a,
|
||||||
|
uint256 b,
|
||||||
|
uint256 toleranceDenominator,
|
||||||
|
string memory errorMessage
|
||||||
|
) internal {
|
||||||
|
// The tolerance is `1 / toleranceDenominator`, so we increase the value
|
||||||
|
// of `a` by this amount. adjustedA = a * (denom+1) / denom
|
||||||
|
uint256 adjustedA = FractionMath.mulDiv(
|
||||||
|
a, toleranceDenominator + 1, toleranceDenominator
|
||||||
|
);
|
||||||
|
assertGt(adjustedA, b, errorMessage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
224
evm/test/LiquidityPartySwapAdapter.t.sol
Normal file
224
evm/test/LiquidityPartySwapAdapter.t.sol
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
// SPDX-License-Identifier: UNLICENSED
|
||||||
|
pragma solidity ^0.8.27;
|
||||||
|
|
||||||
|
import "forge-std/console2.sol"; // todo
|
||||||
|
|
||||||
|
import {
|
||||||
|
IERC20
|
||||||
|
} from "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
|
||||||
|
import {
|
||||||
|
IERC20Metadata
|
||||||
|
} from "../lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol";
|
||||||
|
import {FractionMath} from "../src/libraries/FractionMath.sol";
|
||||||
|
import {IPartyInfo} from "../src/liquidityparty/IPartyInfo.sol";
|
||||||
|
import {IPartyPlanner} from "../src/liquidityparty/IPartyPlanner.sol";
|
||||||
|
import {IPartyPool} from "../src/liquidityparty/IPartyPool.sol";
|
||||||
|
import {
|
||||||
|
LiquidityPartySwapAdapter
|
||||||
|
} from "../src/liquidityparty/LiquidityPartySwapAdapter.sol";
|
||||||
|
import {AdapterTest} from "./AdapterTest.sol";
|
||||||
|
|
||||||
|
contract LiquidityPartyFunctionTest is AdapterTest {
|
||||||
|
using FractionMath for Fraction;
|
||||||
|
|
||||||
|
IPartyPlanner internal constant PLANNER =
|
||||||
|
IPartyPlanner(0x42977f565971F6D288a05ddEbC87A17276F71A29);
|
||||||
|
IPartyInfo internal constant INFO =
|
||||||
|
IPartyInfo(0x605F803cD27F5c1fa01440B2cbd5D3E4Cf7EE850);
|
||||||
|
address internal constant MINT_IMPL =
|
||||||
|
0xA0375403921e9B357E1BeD57bef3fA3FCE80acd0;
|
||||||
|
address internal constant SWAP_IMPL =
|
||||||
|
0x6aA001e87F86E83bc4D569883332882cb47E2A13;
|
||||||
|
IPartyPool internal constant POOL =
|
||||||
|
IPartyPool(0x2A804e94500AE379ee0CcC423a67B07cc0aF548C);
|
||||||
|
bytes32 internal constant POOL_ID = bytes32(bytes20(address(POOL)));
|
||||||
|
uint256 internal constant FORK_BLOCK = 23978797; // block in which the pool
|
||||||
|
// was created
|
||||||
|
|
||||||
|
LiquidityPartySwapAdapter internal adapter;
|
||||||
|
uint256 internal constant TEST_ITERATIONS = 10;
|
||||||
|
|
||||||
|
address[] internal tokens;
|
||||||
|
address internal constant USDT = 0xdAC17F958D2ee523a2206206994597C13D831ec7;
|
||||||
|
address internal constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
|
||||||
|
address internal constant WBTC = 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599;
|
||||||
|
address internal constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
|
||||||
|
address internal constant UNI = 0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984;
|
||||||
|
address internal constant WSOL = 0xD31a59c85aE9D8edEFeC411D448f90841571b89c;
|
||||||
|
address internal constant TRX = 0x50327c6c5a14DCaDE707ABad2E27eB517df87AB5;
|
||||||
|
address internal constant AAVE = 0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9;
|
||||||
|
address internal constant PEPE = 0x6982508145454Ce325dDbE47a25d4ec3d2311933;
|
||||||
|
address internal constant SHIB = 0x95aD61b0a150d79219dCF64E1E6Cc01f0B64C4cE;
|
||||||
|
|
||||||
|
address private constant INPUT_TOKEN = WBTC;
|
||||||
|
uint8 private constant INPUT_INDEX = 2;
|
||||||
|
address private constant OUTPUT_TOKEN = SHIB;
|
||||||
|
uint8 private constant OUTPUT_INDEX = 9;
|
||||||
|
|
||||||
|
function setUp() public {
|
||||||
|
tokens = new address[](10);
|
||||||
|
tokens[0] = USDT;
|
||||||
|
tokens[1] = USDC;
|
||||||
|
tokens[2] = WBTC;
|
||||||
|
tokens[3] = WETH;
|
||||||
|
tokens[4] = UNI;
|
||||||
|
tokens[5] = WSOL;
|
||||||
|
tokens[6] = TRX;
|
||||||
|
tokens[7] = AAVE;
|
||||||
|
tokens[8] = PEPE;
|
||||||
|
tokens[9] = SHIB;
|
||||||
|
|
||||||
|
vm.createSelectFork(vm.rpcUrl("mainnet"), FORK_BLOCK);
|
||||||
|
|
||||||
|
adapter = new LiquidityPartySwapAdapter(PLANNER, INFO);
|
||||||
|
|
||||||
|
vm.label(address(PLANNER), "PartyPlanner");
|
||||||
|
vm.label(address(INFO), "PartyInfo");
|
||||||
|
vm.label(address(MINT_IMPL), "PartyPoolMintImpl");
|
||||||
|
vm.label(address(SWAP_IMPL), "PartyPoolSwapImpl");
|
||||||
|
vm.label(address(POOL), "PartyPool");
|
||||||
|
vm.label(address(adapter), "LiquidityPartySwapAdapter");
|
||||||
|
for (uint256 i = 0; i < tokens.length; i++) {
|
||||||
|
vm.label(address(tokens[i]), IERC20Metadata(tokens[i]).symbol());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function testPrice() public view {
|
||||||
|
uint256[] memory amounts = new uint256[](3);
|
||||||
|
uint256 balance = IERC20(INPUT_TOKEN).balanceOf(address(POOL));
|
||||||
|
amounts[0] = 2; // cannot use 1: the fee will round up and take
|
||||||
|
// everything, resulting in a zero-output reversion
|
||||||
|
amounts[1] = balance;
|
||||||
|
amounts[2] = balance * 2;
|
||||||
|
|
||||||
|
Fraction[] memory prices =
|
||||||
|
adapter.price(POOL_ID, INPUT_TOKEN, OUTPUT_TOKEN, amounts);
|
||||||
|
|
||||||
|
for (uint256 i = 0; i < prices.length; i++) {
|
||||||
|
assertGt(prices[i].numerator, 0);
|
||||||
|
assertGt(prices[i].denominator, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function testPriceDecreasing() public view {
|
||||||
|
uint256[] memory limits =
|
||||||
|
adapter.getLimits(POOL_ID, INPUT_TOKEN, OUTPUT_TOKEN);
|
||||||
|
|
||||||
|
uint256[] memory amounts = new uint256[](TEST_ITERATIONS);
|
||||||
|
|
||||||
|
for (uint256 i = 0; i < TEST_ITERATIONS; i++) {
|
||||||
|
// The first entry will be a zero amount which returns the current
|
||||||
|
// marginal price.
|
||||||
|
amounts[i] = limits[0] * i / (TEST_ITERATIONS - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
Fraction[] memory prices =
|
||||||
|
adapter.price(POOL_ID, INPUT_TOKEN, OUTPUT_TOKEN, amounts);
|
||||||
|
|
||||||
|
for (uint256 i = 0; i < TEST_ITERATIONS - 1; i++) {
|
||||||
|
console2.log("compare price", prices[i].numerator);
|
||||||
|
console2.log(" ", prices[i].denominator);
|
||||||
|
console2.log(" > ", prices[i + 1].numerator);
|
||||||
|
console2.log(" ", prices[i + 1].denominator);
|
||||||
|
console2.log();
|
||||||
|
assertEq(prices[i].compareFractions(prices[i + 1]), 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function testSwapFuzz(uint256 amount) public {
|
||||||
|
uint256[] memory limits =
|
||||||
|
adapter.getLimits(POOL_ID, INPUT_TOKEN, OUTPUT_TOKEN);
|
||||||
|
vm.assume(amount > 1); // 1 will not work because we take fee-on-input
|
||||||
|
// and round up, leaving nothing to trade
|
||||||
|
vm.assume(amount <= limits[0]);
|
||||||
|
|
||||||
|
deal(INPUT_TOKEN, address(this), amount);
|
||||||
|
IERC20(INPUT_TOKEN).approve(address(adapter), amount);
|
||||||
|
|
||||||
|
uint256 usdtBalance = IERC20(INPUT_TOKEN).balanceOf(address(this));
|
||||||
|
uint256 wethBalance = IERC20(OUTPUT_TOKEN).balanceOf(address(this));
|
||||||
|
|
||||||
|
Trade memory trade = adapter.swap(
|
||||||
|
POOL_ID, INPUT_TOKEN, OUTPUT_TOKEN, OrderSide.Sell, amount
|
||||||
|
);
|
||||||
|
|
||||||
|
if (trade.calculatedAmount > 0) {
|
||||||
|
assertEq(
|
||||||
|
amount,
|
||||||
|
usdtBalance - IERC20(INPUT_TOKEN).balanceOf(address(this))
|
||||||
|
);
|
||||||
|
assertEq(
|
||||||
|
trade.calculatedAmount,
|
||||||
|
IERC20(OUTPUT_TOKEN).balanceOf(address(this)) - wethBalance
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function testSwapSellIncreasing() public {
|
||||||
|
uint256[] memory limits =
|
||||||
|
adapter.getLimits(POOL_ID, INPUT_TOKEN, OUTPUT_TOKEN);
|
||||||
|
uint256[] memory amounts = new uint256[](TEST_ITERATIONS);
|
||||||
|
Trade[] memory trades = new Trade[](TEST_ITERATIONS);
|
||||||
|
|
||||||
|
for (uint256 i = 0; i < TEST_ITERATIONS; i++) {
|
||||||
|
amounts[i] = limits[0] * (i + 1) / (TEST_ITERATIONS - 1);
|
||||||
|
|
||||||
|
uint256 beforeSwap = vm.snapshot();
|
||||||
|
|
||||||
|
deal(INPUT_TOKEN, address(this), amounts[i]);
|
||||||
|
IERC20(INPUT_TOKEN).approve(address(adapter), amounts[i]);
|
||||||
|
trades[i] = adapter.swap(
|
||||||
|
POOL_ID, INPUT_TOKEN, OUTPUT_TOKEN, OrderSide.Sell, amounts[i]
|
||||||
|
);
|
||||||
|
|
||||||
|
vm.revertTo(beforeSwap);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint256 i = 0; i < TEST_ITERATIONS - 1; i++) {
|
||||||
|
assertLe(trades[i].calculatedAmount, trades[i + 1].calculatedAmount);
|
||||||
|
assertEq(
|
||||||
|
trades[i].price.denominator, trades[i + 1].price.denominator
|
||||||
|
); // must share a basis
|
||||||
|
assertGe(trades[i].price.numerator, trades[i + 1].price.numerator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function testGetLimits() public view {
|
||||||
|
uint256[] memory limits =
|
||||||
|
adapter.getLimits(POOL_ID, INPUT_TOKEN, OUTPUT_TOKEN);
|
||||||
|
|
||||||
|
assert(limits.length == 2);
|
||||||
|
assert(limits[0] > 0);
|
||||||
|
assert(limits[1] > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testGetTokens() public view {
|
||||||
|
address[] memory adapterTokens = adapter.getTokens(POOL_ID);
|
||||||
|
for (uint256 i = 0; i < tokens.length; i++) {
|
||||||
|
assertEq(adapterTokens[i], tokens[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function testGetPoolIds() public view {
|
||||||
|
uint256 offset = 0;
|
||||||
|
uint256 limit = 10;
|
||||||
|
bytes32[] memory poolIds = adapter.getPoolIds(offset, limit);
|
||||||
|
|
||||||
|
assertLe(
|
||||||
|
poolIds.length,
|
||||||
|
limit,
|
||||||
|
"Number of pool IDs should be less than or equal to limit"
|
||||||
|
);
|
||||||
|
if (poolIds.length > 0) {
|
||||||
|
assertGt(uint256(poolIds[0]), 0, "Pool ID should be greater than 0");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Many of the tests above seem entirely redundant with runPoolBehaviorTest
|
||||||
|
// :shrug:
|
||||||
|
function testLiquidityPartyPoolBehaviour() public {
|
||||||
|
bytes32[] memory poolIds = new bytes32[](1);
|
||||||
|
poolIds[0] = POOL_ID;
|
||||||
|
runPoolBehaviourTest(adapter, poolIds);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,2 +1,11 @@
|
|||||||
|
export PATH := /home/linuxbrew/.linuxbrew/bin:$(PATH)
|
||||||
|
|
||||||
|
all: ethereum
|
||||||
|
|
||||||
build:
|
build:
|
||||||
cargo build --target wasm32-unknown-unknown --release
|
cargo build --target wasm32-unknown-unknown --release
|
||||||
|
|
||||||
|
ethereum: build
|
||||||
|
substreams pack ethereum-liquidityparty.yaml -o ethereum-liquidityparty.spkg
|
||||||
|
|
||||||
|
.PHONY: build ethereum
|
||||||
|
|||||||
@@ -2,15 +2,15 @@
|
|||||||
{
|
{
|
||||||
"type": "constructor",
|
"type": "constructor",
|
||||||
"inputs": [
|
"inputs": [
|
||||||
{
|
|
||||||
"name": "swapImpl_",
|
|
||||||
"type": "address",
|
|
||||||
"internalType": "contract PartyPoolSwapImpl"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "mintImpl",
|
"name": "mintImpl",
|
||||||
"type": "address",
|
"type": "address",
|
||||||
"internalType": "contract PartyPoolMintImpl"
|
"internalType": "contract PartyPoolMintImpl"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "swapImpl_",
|
||||||
|
"type": "address",
|
||||||
|
"internalType": "contract PartyPoolSwapImpl"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"stateMutability": "nonpayable"
|
"stateMutability": "nonpayable"
|
||||||
@@ -54,7 +54,7 @@
|
|||||||
"internalType": "uint256"
|
"internalType": "uint256"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "inputTokenIndex",
|
"name": "outputTokenIndex",
|
||||||
"type": "uint256",
|
"type": "uint256",
|
||||||
"internalType": "uint256"
|
"internalType": "uint256"
|
||||||
}
|
}
|
||||||
@@ -64,6 +64,11 @@
|
|||||||
"name": "amountOut",
|
"name": "amountOut",
|
||||||
"type": "uint256",
|
"type": "uint256",
|
||||||
"internalType": "uint256"
|
"internalType": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "outFee",
|
||||||
|
"type": "uint256",
|
||||||
|
"internalType": "uint256"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"stateMutability": "view"
|
"stateMutability": "view"
|
||||||
@@ -97,30 +102,6 @@
|
|||||||
],
|
],
|
||||||
"stateMutability": "view"
|
"stateMutability": "view"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "function",
|
|
||||||
"name": "flashRepaymentAmounts",
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "pool",
|
|
||||||
"type": "address",
|
|
||||||
"internalType": "contract IPartyPool"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "loanAmounts",
|
|
||||||
"type": "uint256[]",
|
|
||||||
"internalType": "uint256[]"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "repaymentAmounts",
|
|
||||||
"type": "uint256[]",
|
|
||||||
"internalType": "uint256[]"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"stateMutability": "view"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "function",
|
"type": "function",
|
||||||
"name": "maxFlashLoan",
|
"name": "maxFlashLoan",
|
||||||
@@ -216,8 +197,8 @@
|
|||||||
"outputs": [
|
"outputs": [
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
"type": "int128",
|
"type": "uint256",
|
||||||
"internalType": "int128"
|
"internalType": "uint256"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"stateMutability": "view"
|
"stateMutability": "view"
|
||||||
@@ -249,12 +230,12 @@
|
|||||||
"internalType": "uint256"
|
"internalType": "uint256"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "fee",
|
"name": "lpMinted",
|
||||||
"type": "uint256",
|
"type": "uint256",
|
||||||
"internalType": "uint256"
|
"internalType": "uint256"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "lpMinted",
|
"name": "inFee",
|
||||||
"type": "uint256",
|
"type": "uint256",
|
||||||
"internalType": "uint256"
|
"internalType": "uint256"
|
||||||
}
|
}
|
||||||
@@ -298,11 +279,30 @@
|
|||||||
"internalType": "uint256"
|
"internalType": "uint256"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "fee",
|
"name": "inFee",
|
||||||
"type": "uint256",
|
"type": "uint256",
|
||||||
"internalType": "uint256"
|
"internalType": "uint256"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"stateMutability": "view"
|
"stateMutability": "view"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "working",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "pool",
|
||||||
|
"type": "address",
|
||||||
|
"internalType": "contract IPartyPool"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "bool",
|
||||||
|
"internalType": "bool"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stateMutability": "view"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -23,14 +23,14 @@
|
|||||||
"internalType": "contract PartyPoolMintImpl"
|
"internalType": "contract PartyPoolMintImpl"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "deployer_",
|
"name": "poolInitCodeStorage_",
|
||||||
"type": "address",
|
"type": "address",
|
||||||
"internalType": "contract IPartyPoolDeployer"
|
"internalType": "contract PartyPoolInitCode"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "balancedPairDeployer_",
|
"name": "balancedPairInitCodeStorage_",
|
||||||
"type": "address",
|
"type": "address",
|
||||||
"internalType": "contract IPartyPoolDeployer"
|
"internalType": "contract PartyPoolBalancedPairInitCode"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "protocolFeePpm_",
|
"name": "protocolFeePpm_",
|
||||||
@@ -174,12 +174,91 @@
|
|||||||
"internalType": "contract IERC20[]"
|
"internalType": "contract IERC20[]"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "bases_",
|
"name": "kappa_",
|
||||||
|
"type": "int128",
|
||||||
|
"internalType": "int128"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "swapFeesPpm_",
|
||||||
"type": "uint256[]",
|
"type": "uint256[]",
|
||||||
"internalType": "uint256[]"
|
"internalType": "uint256[]"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "kappa_",
|
"name": "flashFeePpm_",
|
||||||
|
"type": "uint256",
|
||||||
|
"internalType": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "stable_",
|
||||||
|
"type": "bool",
|
||||||
|
"internalType": "bool"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "payer",
|
||||||
|
"type": "address",
|
||||||
|
"internalType": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "receiver",
|
||||||
|
"type": "address",
|
||||||
|
"internalType": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "initialDeposits",
|
||||||
|
"type": "uint256[]",
|
||||||
|
"internalType": "uint256[]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "initialLpAmount",
|
||||||
|
"type": "uint256",
|
||||||
|
"internalType": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "deadline",
|
||||||
|
"type": "uint256",
|
||||||
|
"internalType": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "pool",
|
||||||
|
"type": "address",
|
||||||
|
"internalType": "contract IPartyPool"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "lpAmount",
|
||||||
|
"type": "uint256",
|
||||||
|
"internalType": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stateMutability": "nonpayable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "newPool",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "name_",
|
||||||
|
"type": "string",
|
||||||
|
"internalType": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "symbol_",
|
||||||
|
"type": "string",
|
||||||
|
"internalType": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "tokens_",
|
||||||
|
"type": "address[]",
|
||||||
|
"internalType": "contract IERC20[]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "tradeFrac_",
|
||||||
|
"type": "int128",
|
||||||
|
"internalType": "int128"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "targetSlippage_",
|
||||||
"type": "int128",
|
"type": "int128",
|
||||||
"internalType": "int128"
|
"internalType": "int128"
|
||||||
},
|
},
|
||||||
@@ -258,17 +337,7 @@
|
|||||||
"internalType": "contract IERC20[]"
|
"internalType": "contract IERC20[]"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "bases_",
|
"name": "kappa_",
|
||||||
"type": "uint256[]",
|
|
||||||
"internalType": "uint256[]"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "tradeFrac_",
|
|
||||||
"type": "int128",
|
|
||||||
"internalType": "int128"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "targetSlippage_",
|
|
||||||
"type": "int128",
|
"type": "int128",
|
||||||
"internalType": "int128"
|
"internalType": "int128"
|
||||||
},
|
},
|
||||||
@@ -340,6 +409,86 @@
|
|||||||
],
|
],
|
||||||
"stateMutability": "view"
|
"stateMutability": "view"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "params",
|
||||||
|
"inputs": [],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "tuple",
|
||||||
|
"internalType": "struct IPartyPoolDeployer.DeployParams",
|
||||||
|
"components": [
|
||||||
|
{
|
||||||
|
"name": "nonce",
|
||||||
|
"type": "bytes32",
|
||||||
|
"internalType": "bytes32"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "owner",
|
||||||
|
"type": "address",
|
||||||
|
"internalType": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"type": "string",
|
||||||
|
"internalType": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "symbol",
|
||||||
|
"type": "string",
|
||||||
|
"internalType": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "tokens",
|
||||||
|
"type": "address[]",
|
||||||
|
"internalType": "contract IERC20[]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "kappa",
|
||||||
|
"type": "int128",
|
||||||
|
"internalType": "int128"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "fees",
|
||||||
|
"type": "uint256[]",
|
||||||
|
"internalType": "uint256[]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "flashFeePpm",
|
||||||
|
"type": "uint256",
|
||||||
|
"internalType": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "protocolFeePpm",
|
||||||
|
"type": "uint256",
|
||||||
|
"internalType": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "protocolFeeAddress",
|
||||||
|
"type": "address",
|
||||||
|
"internalType": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "wrapper",
|
||||||
|
"type": "address",
|
||||||
|
"internalType": "contract NativeWrapper"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "swapImpl",
|
||||||
|
"type": "address",
|
||||||
|
"internalType": "contract PartyPoolSwapImpl"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "mintImpl",
|
||||||
|
"type": "address",
|
||||||
|
"internalType": "contract PartyPoolMintImpl"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stateMutability": "view"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "function",
|
"type": "function",
|
||||||
"name": "poolCount",
|
"name": "poolCount",
|
||||||
@@ -405,6 +554,19 @@
|
|||||||
"outputs": [],
|
"outputs": [],
|
||||||
"stateMutability": "nonpayable"
|
"stateMutability": "nonpayable"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "setProtocolFeeAddress",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "feeAddress",
|
||||||
|
"type": "address",
|
||||||
|
"internalType": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [],
|
||||||
|
"stateMutability": "nonpayable"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "function",
|
"type": "function",
|
||||||
"name": "swapImpl",
|
"name": "swapImpl",
|
||||||
|
|||||||
@@ -1,73 +1,7 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"type": "constructor",
|
"type": "constructor",
|
||||||
"inputs": [
|
"inputs": [],
|
||||||
{
|
|
||||||
"name": "owner_",
|
|
||||||
"type": "address",
|
|
||||||
"internalType": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "name_",
|
|
||||||
"type": "string",
|
|
||||||
"internalType": "string"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "symbol_",
|
|
||||||
"type": "string",
|
|
||||||
"internalType": "string"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "tokens_",
|
|
||||||
"type": "address[]",
|
|
||||||
"internalType": "contract IERC20[]"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "bases_",
|
|
||||||
"type": "uint256[]",
|
|
||||||
"internalType": "uint256[]"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "kappa_",
|
|
||||||
"type": "int128",
|
|
||||||
"internalType": "int128"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "swapFeePpm_",
|
|
||||||
"type": "uint256",
|
|
||||||
"internalType": "uint256"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "flashFeePpm_",
|
|
||||||
"type": "uint256",
|
|
||||||
"internalType": "uint256"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "protocolFeePpm_",
|
|
||||||
"type": "uint256",
|
|
||||||
"internalType": "uint256"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "protocolFeeAddress_",
|
|
||||||
"type": "address",
|
|
||||||
"internalType": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "wrapperToken_",
|
|
||||||
"type": "address",
|
|
||||||
"internalType": "contract NativeWrapper"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "swapImpl_",
|
|
||||||
"type": "address",
|
|
||||||
"internalType": "contract PartyPoolSwapImpl"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "mintImpl_",
|
|
||||||
"type": "address",
|
|
||||||
"internalType": "contract PartyPoolMintImpl"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"stateMutability": "nonpayable"
|
"stateMutability": "nonpayable"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -84,11 +18,6 @@
|
|||||||
"type": "tuple",
|
"type": "tuple",
|
||||||
"internalType": "struct LMSRStabilized.State",
|
"internalType": "struct LMSRStabilized.State",
|
||||||
"components": [
|
"components": [
|
||||||
{
|
|
||||||
"name": "nAssets",
|
|
||||||
"type": "uint256",
|
|
||||||
"internalType": "uint256"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "kappa",
|
"name": "kappa",
|
||||||
"type": "int128",
|
"type": "int128",
|
||||||
@@ -256,7 +185,7 @@
|
|||||||
"internalType": "uint256"
|
"internalType": "uint256"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "inputTokenIndex",
|
"name": "outputTokenIndex",
|
||||||
"type": "uint256",
|
"type": "uint256",
|
||||||
"internalType": "uint256"
|
"internalType": "uint256"
|
||||||
},
|
},
|
||||||
@@ -273,7 +202,12 @@
|
|||||||
],
|
],
|
||||||
"outputs": [
|
"outputs": [
|
||||||
{
|
{
|
||||||
"name": "amountOutUint",
|
"name": "amountOut",
|
||||||
|
"type": "uint256",
|
||||||
|
"internalType": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "outFee",
|
||||||
"type": "uint256",
|
"type": "uint256",
|
||||||
"internalType": "uint256"
|
"internalType": "uint256"
|
||||||
}
|
}
|
||||||
@@ -313,6 +247,43 @@
|
|||||||
],
|
],
|
||||||
"stateMutability": "view"
|
"stateMutability": "view"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "fee",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "i",
|
||||||
|
"type": "uint256",
|
||||||
|
"internalType": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "j",
|
||||||
|
"type": "uint256",
|
||||||
|
"internalType": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "uint256",
|
||||||
|
"internalType": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stateMutability": "view"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "fees",
|
||||||
|
"inputs": [],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "uint256[]",
|
||||||
|
"internalType": "uint256[]"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stateMutability": "view"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "function",
|
"type": "function",
|
||||||
"name": "flashFeePpm",
|
"name": "flashFeePpm",
|
||||||
@@ -360,25 +331,6 @@
|
|||||||
],
|
],
|
||||||
"stateMutability": "nonpayable"
|
"stateMutability": "nonpayable"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "function",
|
|
||||||
"name": "getToken",
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "i",
|
|
||||||
"type": "uint256",
|
|
||||||
"internalType": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "address",
|
|
||||||
"internalType": "contract IERC20"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"stateMutability": "view"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "function",
|
"type": "function",
|
||||||
"name": "initialMint",
|
"name": "initialMint",
|
||||||
@@ -478,7 +430,7 @@
|
|||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
"type": "address",
|
"type": "address",
|
||||||
"internalType": "contract PartyPoolMintImpl"
|
"internalType": "address"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"stateMutability": "view"
|
"stateMutability": "view"
|
||||||
@@ -555,6 +507,19 @@
|
|||||||
"outputs": [],
|
"outputs": [],
|
||||||
"stateMutability": "nonpayable"
|
"stateMutability": "nonpayable"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "setProtocolFeeAddress",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "feeAddress",
|
||||||
|
"type": "address",
|
||||||
|
"internalType": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [],
|
||||||
|
"stateMutability": "nonpayable"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "function",
|
"type": "function",
|
||||||
"name": "swap",
|
"name": "swap",
|
||||||
@@ -564,6 +529,11 @@
|
|||||||
"type": "address",
|
"type": "address",
|
||||||
"internalType": "address"
|
"internalType": "address"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "fundingSelector",
|
||||||
|
"type": "bytes4",
|
||||||
|
"internalType": "bytes4"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "receiver",
|
"name": "receiver",
|
||||||
"type": "address",
|
"type": "address",
|
||||||
@@ -598,6 +568,11 @@
|
|||||||
"name": "unwrap",
|
"name": "unwrap",
|
||||||
"type": "bool",
|
"type": "bool",
|
||||||
"internalType": "bool"
|
"internalType": "bool"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cbData",
|
||||||
|
"type": "bytes",
|
||||||
|
"internalType": "bytes"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"outputs": [
|
"outputs": [
|
||||||
@@ -612,7 +587,7 @@
|
|||||||
"internalType": "uint256"
|
"internalType": "uint256"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "fee",
|
"name": "inFee",
|
||||||
"type": "uint256",
|
"type": "uint256",
|
||||||
"internalType": "uint256"
|
"internalType": "uint256"
|
||||||
}
|
}
|
||||||
@@ -656,7 +631,7 @@
|
|||||||
"internalType": "uint256"
|
"internalType": "uint256"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "fee",
|
"name": "inFee",
|
||||||
"type": "uint256",
|
"type": "uint256",
|
||||||
"internalType": "uint256"
|
"internalType": "uint256"
|
||||||
}
|
}
|
||||||
@@ -665,13 +640,13 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "function",
|
"type": "function",
|
||||||
"name": "swapFeePpm",
|
"name": "swapImpl",
|
||||||
"inputs": [],
|
"inputs": [],
|
||||||
"outputs": [
|
"outputs": [
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
"type": "uint256",
|
"type": "address",
|
||||||
"internalType": "uint256"
|
"internalType": "address"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"stateMutability": "view"
|
"stateMutability": "view"
|
||||||
@@ -707,27 +682,24 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"outputs": [
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "amountInUsed",
|
||||||
|
"type": "uint256",
|
||||||
|
"internalType": "uint256"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "lpMinted",
|
"name": "lpMinted",
|
||||||
"type": "uint256",
|
"type": "uint256",
|
||||||
"internalType": "uint256"
|
"internalType": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "inFee",
|
||||||
|
"type": "uint256",
|
||||||
|
"internalType": "uint256"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"stateMutability": "payable"
|
"stateMutability": "payable"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "function",
|
|
||||||
"name": "swapMintImpl",
|
|
||||||
"inputs": [],
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "address",
|
|
||||||
"internalType": "contract PartyPoolSwapImpl"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"stateMutability": "view"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "function",
|
"type": "function",
|
||||||
"name": "swapToLimit",
|
"name": "swapToLimit",
|
||||||
@@ -737,6 +709,11 @@
|
|||||||
"type": "address",
|
"type": "address",
|
||||||
"internalType": "address"
|
"internalType": "address"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "fundingSelector",
|
||||||
|
"type": "bytes4",
|
||||||
|
"internalType": "bytes4"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "receiver",
|
"name": "receiver",
|
||||||
"type": "address",
|
"type": "address",
|
||||||
@@ -766,6 +743,11 @@
|
|||||||
"name": "unwrap",
|
"name": "unwrap",
|
||||||
"type": "bool",
|
"type": "bool",
|
||||||
"internalType": "bool"
|
"internalType": "bool"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cbData",
|
||||||
|
"type": "bytes",
|
||||||
|
"internalType": "bytes"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"outputs": [
|
"outputs": [
|
||||||
@@ -780,7 +762,7 @@
|
|||||||
"internalType": "uint256"
|
"internalType": "uint256"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "fee",
|
"name": "inFee",
|
||||||
"type": "uint256",
|
"type": "uint256",
|
||||||
"internalType": "uint256"
|
"internalType": "uint256"
|
||||||
}
|
}
|
||||||
@@ -800,6 +782,25 @@
|
|||||||
],
|
],
|
||||||
"stateMutability": "view"
|
"stateMutability": "view"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "token",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "i",
|
||||||
|
"type": "uint256",
|
||||||
|
"internalType": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "address",
|
||||||
|
"internalType": "contract IERC20"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stateMutability": "view"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "function",
|
"type": "function",
|
||||||
"name": "totalSupply",
|
"name": "totalSupply",
|
||||||
@@ -1333,6 +1334,22 @@
|
|||||||
"name": "FailedCall",
|
"name": "FailedCall",
|
||||||
"inputs": []
|
"inputs": []
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "error",
|
||||||
|
"name": "InsufficientBalance",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "balance",
|
||||||
|
"type": "uint256",
|
||||||
|
"internalType": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "needed",
|
||||||
|
"type": "uint256",
|
||||||
|
"internalType": "uint256"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "error",
|
"type": "error",
|
||||||
"name": "OwnableInvalidOwner",
|
"name": "OwnableInvalidOwner",
|
||||||
|
|||||||
11
substreams/ethereum-liquidityparty/bin/generate_abi
Executable file
11
substreams/ethereum-liquidityparty/bin/generate_abi
Executable file
@@ -0,0 +1,11 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
LMSR_HOME=../../../lmsr-amm
|
||||||
|
CHAIN_ID=11155111
|
||||||
|
abi() {
|
||||||
|
jq '.abi' $LMSR_HOME/deployment/$CHAIN_ID/v1/out/$1.sol/$1.json > abi/$2.abi.json
|
||||||
|
echo abi/$2.abi.json
|
||||||
|
}
|
||||||
|
|
||||||
|
abi PartyPlanner party_planner
|
||||||
|
abi PartyPool party_pool
|
||||||
|
abi PartyInfo party_info
|
||||||
@@ -16,18 +16,17 @@ binaries:
|
|||||||
type: wasm/rust-v1
|
type: wasm/rust-v1
|
||||||
file: ../target/wasm32-unknown-unknown/release/ethereum_liquidityparty.wasm
|
file: ../target/wasm32-unknown-unknown/release/ethereum_liquidityparty.wasm
|
||||||
|
|
||||||
network: sepolia
|
network: mainnet
|
||||||
networks:
|
networks:
|
||||||
sepolia:
|
mainnet:
|
||||||
initialBlock:
|
initialBlock:
|
||||||
map_protocol_components: 9460804
|
map_protocol_components: 23978797
|
||||||
params:
|
params:
|
||||||
map_protocol_components: planner=0x0ad06C08ab5049e6Fd4d7f5AF457115A1475326b&viewer=0x750d63a39a4ccfCfB69D2f5aFDa065909C717cAB&mint_impl=0x25bb10BA84944F8aAEf1fD247C3B7Fe7271C23F9&swap_impl=0x69b4F102e0747f61F8529b3bbFf2FC4b27438d0F&deployer=0x0939F93BAa3c96226853F9F39A95beF48eA8fF04&bp_deployer=0xfda454fF7876aad9408517Ed2F0d11AA229Ad0a4
|
map_protocol_components: planner=0x42977f565971F6D288a05ddEbC87A17276F71A29&info=0x605F803cD27F5c1fa01440B2cbd5D3E4Cf7EE850&mint_impl=0xA0375403921e9B357E1BeD57bef3fA3FCE80acd0&swap_impl=0x6aA001e87F86E83bc4D569883332882cb47E2A13
|
||||||
|
|
||||||
modules:
|
modules:
|
||||||
- name: map_protocol_components
|
- name: map_protocol_components
|
||||||
kind: map
|
kind: map
|
||||||
initialBlock: 1
|
|
||||||
inputs:
|
inputs:
|
||||||
- params: string
|
- params: string
|
||||||
- source: sf.ethereum.type.v2.Block
|
- source: sf.ethereum.type.v2.Block
|
||||||
@@ -36,7 +35,6 @@ modules:
|
|||||||
|
|
||||||
- name: store_protocol_components
|
- name: store_protocol_components
|
||||||
kind: store
|
kind: store
|
||||||
initialBlock: 1
|
|
||||||
updatePolicy: set
|
updatePolicy: set
|
||||||
valueType: string
|
valueType: string
|
||||||
inputs:
|
inputs:
|
||||||
@@ -44,7 +42,6 @@ modules:
|
|||||||
|
|
||||||
- name: map_relative_component_balance
|
- name: map_relative_component_balance
|
||||||
kind: map
|
kind: map
|
||||||
initialBlock: 1
|
|
||||||
inputs:
|
inputs:
|
||||||
- source: sf.ethereum.type.v2.Block
|
- source: sf.ethereum.type.v2.Block
|
||||||
- store: store_protocol_components
|
- store: store_protocol_components
|
||||||
@@ -53,7 +50,6 @@ modules:
|
|||||||
|
|
||||||
- name: store_balances
|
- name: store_balances
|
||||||
kind: store
|
kind: store
|
||||||
initialBlock: 1
|
|
||||||
updatePolicy: add
|
updatePolicy: add
|
||||||
valueType: bigint
|
valueType: bigint
|
||||||
inputs:
|
inputs:
|
||||||
@@ -61,7 +57,6 @@ modules:
|
|||||||
|
|
||||||
- name: map_protocol_changes
|
- name: map_protocol_changes
|
||||||
kind: map
|
kind: map
|
||||||
initialBlock: 1
|
|
||||||
inputs:
|
inputs:
|
||||||
- source: sf.ethereum.type.v2.Block
|
- source: sf.ethereum.type.v2.Block
|
||||||
- map: map_protocol_components
|
- map: map_protocol_components
|
||||||
@@ -1,69 +1,40 @@
|
|||||||
# Name of the substreams config file in your substreams module. Usually "./substreams.yaml"
|
substreams_yaml_path: ./sepolia-liquidityparty.yaml
|
||||||
substreams_yaml_path: ./substreams.yaml
|
adapter_contract: "LiquidityPartySwapAdapter"
|
||||||
# Name of the adapter contract, usually: ProtocolSwapAdapter"
|
adapter_build_signature: "constructor(address,address)"
|
||||||
adapter_contract: "SwapAdapter"
|
# PartyPlanner, PartyInfo
|
||||||
# Constructor signature of the Adapter contract"
|
adapter_build_args: "0x42977f565971F6D288a05ddEbC87A17276F71A29,0x605F803cD27F5c1fa01440B2cbd5D3E4Cf7EE850"
|
||||||
adapter_build_signature: "constructor(address)"
|
# Liquidity Party protocol fees are retained in the pool until collection, so the pool balance does not match the TVL.
|
||||||
# A comma separated list of args to be passed to the contructor of the Adapter contract"
|
skip_balance_check: true
|
||||||
adapter_build_args: "0x0000000000000000000000000000000000000000"
|
|
||||||
# Whether the testing script should skip checking balances of the protocol components.
|
|
||||||
# If set to `true` please always add a reason why it's skipped.
|
|
||||||
skip_balance_check: false
|
|
||||||
# Accounts that will be automatically initialized at test start
|
|
||||||
# IMPORTANT: These are TEST FIXTURES ONLY. Your actual code must still properly
|
|
||||||
# initialize these accounts. This configuration only eliminates the need to include
|
|
||||||
# historical blocks containing the initialization events in your test data.
|
|
||||||
#
|
|
||||||
# Example use case:
|
|
||||||
# - Your substream would normally initialize account XYZ at block 10000
|
|
||||||
# - Your test only includes blocks 20000-21000 for efficiency
|
|
||||||
# - You list XYZ here so the test environment will automatically initialize the account XYZ with the state it had at block 20000
|
|
||||||
# - Your actual substream code MUST STILL contain the initialization and state tracking logic for this contract
|
|
||||||
#
|
|
||||||
# Without this, you would need to include block 10000 in your test data or your
|
|
||||||
# test would fail because the account appears uninitialized to your code.
|
|
||||||
initialized_accounts:
|
|
||||||
- "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84" # Needed for ....
|
|
||||||
# A list of protocol types names created by your Substreams module.
|
|
||||||
protocol_type_names:
|
protocol_type_names:
|
||||||
- "type_name_1"
|
- "liquidityparty_pool"
|
||||||
- "type_name_2"
|
protocol_system: "vm:liquidityparty"
|
||||||
# A list of tests.
|
initialized_accounts: [
|
||||||
# The name of the protocol system
|
"0x42977f565971F6D288a05ddEbC87A17276F71A29", # PartyPlanner
|
||||||
protocol_system: "protocol_name"
|
"0x605F803cD27F5c1fa01440B2cbd5D3E4Cf7EE850", # PartyInfo
|
||||||
|
"0xA0375403921e9B357E1BeD57bef3fA3FCE80acd0", # PartyPoolMintImpl
|
||||||
|
"0x6aA001e87F86E83bc4D569883332882cb47E2A13", # PartyPoolSwapImpl
|
||||||
|
]
|
||||||
|
|
||||||
tests:
|
tests:
|
||||||
# Name of the test
|
|
||||||
- name: test_pool_creation
|
- name: test_pool_creation
|
||||||
# Indexed block range
|
start_block: 23978797
|
||||||
start_block: 123
|
stop_block: 23978798
|
||||||
stop_block: 456
|
initialized_accounts: []
|
||||||
# Same as global `initialized_accounts` but only scoped to this test.
|
|
||||||
initialized_accounts:
|
|
||||||
- "0x0c0e5f2fF0ff18a3be9b835635039256dC4B4963" # Needed for ....
|
|
||||||
# A list of expected component indexed in the block range. Each component must match perfectly the `ProtocolComponent` indexed by your subtreams module.
|
|
||||||
expected_components:
|
expected_components:
|
||||||
- id: "0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7"
|
- id: "0x2A804e94500AE379ee0CcC423a67B07cc0aF548C"
|
||||||
tokens:
|
tokens:
|
||||||
- "0xdac17f958d2ee523a2206206994597c13d831ec7"
|
# Does this test whether the tokens are in the correct order?
|
||||||
- "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
|
- "0xdAC17F958D2ee523a2206206994597C13D831ec7" # USDT
|
||||||
- "0x6b175474e89094c44da98b954eedeac495271d0f"
|
- "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" # USDC
|
||||||
static_attributes: { }
|
- "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599" # WBTC
|
||||||
creation_tx: "0x20793bbf260912aae189d5d261ff003c9b9166da8191d8f9d63ff1c7722f3ac6"
|
- "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" # WETH
|
||||||
# Whether the script should skip trying to simulate a swap on this component.
|
- "0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984" # UNI
|
||||||
# If set to `true` please always add a reason why it's skipped.
|
- "0xD31a59c85aE9D8edEFeC411D448f90841571b89c" # SOL
|
||||||
|
- "0x50327c6c5a14DCaDE707ABad2E27eB517df87AB5" # TRX
|
||||||
|
- "0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9" # AAVE
|
||||||
|
- "0x6982508145454Ce325dDbE47a25d4ec3d2311933" # PEPE
|
||||||
|
- "0x95aD61b0a150d79219dCF64E1E6Cc01f0B64C4cE" # SHIB
|
||||||
|
static_attributes: {}
|
||||||
|
creation_tx: "0x5c8b1e1e6ec10143a1252799d14df09c7e84f6a99ccde95fc11295a61c20060e"
|
||||||
skip_simulation: false
|
skip_simulation: false
|
||||||
# Whether the script should skip trying to simulate execution of a swap on this component.
|
|
||||||
# If set to `true` please always add a reason why it's skipped.
|
|
||||||
skip_execution: false
|
skip_execution: false
|
||||||
- name: test_something_else
|
|
||||||
start_block: 123
|
|
||||||
stop_block: 456
|
|
||||||
expected_components:
|
|
||||||
- id: "0xdc24316b9ae028f1497c275eb9192a3ea0f67022"
|
|
||||||
tokens:
|
|
||||||
- "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"
|
|
||||||
- "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84"
|
|
||||||
static_attributes: { }
|
|
||||||
creation_tx: "0xfac67ecbd423a5b915deff06045ec9343568edaec34ae95c43d35f2c018afdaa"
|
|
||||||
skip_simulation: true # If true, always add a reason
|
|
||||||
skip_execution: true # If true, always add a reason
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
mod.rs
|
mod.rs
|
||||||
party_planner.rs
|
party_planner.rs
|
||||||
party_pool.rs
|
party_pool.rs
|
||||||
party_pool_viewer.rs
|
party_info.rs
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ fn map_protocol_components(
|
|||||||
param_string: String,
|
param_string: String,
|
||||||
block: eth::v2::Block
|
block: eth::v2::Block
|
||||||
) -> Result<BlockTransactionProtocolComponents> {
|
) -> Result<BlockTransactionProtocolComponents> {
|
||||||
|
substreams::log::debug!("Processing block {} for protocol components", block.number);
|
||||||
let params = Params::parse(¶m_string)?;
|
let params = Params::parse(¶m_string)?;
|
||||||
Ok(BlockTransactionProtocolComponents {
|
Ok(BlockTransactionProtocolComponents {
|
||||||
tx_components: block
|
tx_components: block
|
||||||
@@ -94,6 +95,7 @@ fn store_protocol_components(
|
|||||||
map_protocol_components: BlockTransactionProtocolComponents,
|
map_protocol_components: BlockTransactionProtocolComponents,
|
||||||
store: StoreSetRaw,
|
store: StoreSetRaw,
|
||||||
) {
|
) {
|
||||||
|
substreams::log::debug!("Storing {} protocol components", map_protocol_components.tx_components.len());
|
||||||
map_protocol_components
|
map_protocol_components
|
||||||
.tx_components
|
.tx_components
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -129,6 +131,7 @@ fn map_relative_component_balance(
|
|||||||
block: eth::v2::Block,
|
block: eth::v2::Block,
|
||||||
store: StoreGetRaw,
|
store: StoreGetRaw,
|
||||||
) -> Result<BlockBalanceDeltas, anyhow::Error> {
|
) -> Result<BlockBalanceDeltas, anyhow::Error> {
|
||||||
|
substreams::log::debug!("Processing block {} for balance changes", block.number);
|
||||||
let mut res = Vec::new();
|
let mut res = Vec::new();
|
||||||
|
|
||||||
for log in block.logs() {
|
for log in block.logs() {
|
||||||
@@ -239,6 +242,12 @@ fn map_protocol_changes(
|
|||||||
balance_store: StoreDeltas,
|
balance_store: StoreDeltas,
|
||||||
deltas: BlockBalanceDeltas,
|
deltas: BlockBalanceDeltas,
|
||||||
) -> Result<BlockChanges, substreams::errors::Error> {
|
) -> Result<BlockChanges, substreams::errors::Error> {
|
||||||
|
substreams::log::debug!(
|
||||||
|
"Processing block {} changes: {} new components, {} balance deltas",
|
||||||
|
block.number,
|
||||||
|
new_components.tx_components.len(),
|
||||||
|
deltas.balance_deltas.len()
|
||||||
|
);
|
||||||
// We merge contract changes by transaction (identified by transaction index)
|
// We merge contract changes by transaction (identified by transaction index)
|
||||||
// making it easy to sort them at the very end.
|
// making it easy to sort them at the very end.
|
||||||
let mut transaction_changes: HashMap<_, TransactionChangesBuilder> = HashMap::new();
|
let mut transaction_changes: HashMap<_, TransactionChangesBuilder> = HashMap::new();
|
||||||
|
|||||||
@@ -4,20 +4,17 @@ use serde::Deserialize;
|
|||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct StringParams {
|
struct StringParams {
|
||||||
planner: String,
|
planner: String,
|
||||||
viewer: String,
|
info: String,
|
||||||
mint_impl: String,
|
mint_impl: String,
|
||||||
swap_impl: String,
|
swap_impl: String,
|
||||||
deployer: String,
|
|
||||||
bp_deployer: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct Params {
|
pub(crate) struct Params {
|
||||||
pub planner: Vec<u8>,
|
pub planner: Vec<u8>,
|
||||||
pub viewer: Vec<u8>,
|
#[allow(dead_code)] // We keep the unused info field for future pricing/view operations
|
||||||
|
pub info: Vec<u8>,
|
||||||
pub mint_impl: Vec<u8>,
|
pub mint_impl: Vec<u8>,
|
||||||
pub swap_impl: Vec<u8>,
|
pub swap_impl: Vec<u8>,
|
||||||
pub deployer: Vec<u8>,
|
|
||||||
pub bp_deployer: Vec<u8>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StringParams {
|
impl StringParams {
|
||||||
@@ -44,11 +41,9 @@ impl Params {
|
|||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
planner: decode_addr(¶ms.planner)?,
|
planner: decode_addr(¶ms.planner)?,
|
||||||
viewer: decode_addr(¶ms.viewer)?,
|
info: decode_addr(¶ms.info)?,
|
||||||
mint_impl: decode_addr(¶ms.mint_impl)?,
|
mint_impl: decode_addr(¶ms.mint_impl)?,
|
||||||
swap_impl: decode_addr(¶ms.swap_impl)?,
|
swap_impl: decode_addr(¶ms.swap_impl)?,
|
||||||
deployer: decode_addr(¶ms.deployer)?,
|
|
||||||
bp_deployer: decode_addr(¶ms.bp_deployer)?,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
|
use crate::abi;
|
||||||
|
use crate::params::Params;
|
||||||
use substreams_ethereum::pb::eth::v2::{Call, Log, TransactionTrace};
|
use substreams_ethereum::pb::eth::v2::{Call, Log, TransactionTrace};
|
||||||
use substreams_ethereum::Event;
|
use substreams_ethereum::Event;
|
||||||
use tycho_substreams::models::{
|
use tycho_substreams::models::{
|
||||||
ChangeType, FinancialType, ImplementationType, ProtocolComponent, ProtocolType,
|
ImplementationType, ProtocolComponent,
|
||||||
};
|
};
|
||||||
use crate::abi;
|
|
||||||
use crate::params::Params;
|
|
||||||
|
|
||||||
/// Potentially constructs a new ProtocolComponent given a call
|
/// Potentially constructs a new ProtocolComponent given a call
|
||||||
///
|
///
|
||||||
@@ -21,19 +21,10 @@ pub fn maybe_create_component(
|
|||||||
) -> Option<ProtocolComponent> {
|
) -> Option<ProtocolComponent> {
|
||||||
if call.address.as_slice() == params.planner {
|
if call.address.as_slice() == params.planner {
|
||||||
if let Some(event) = abi::party_planner::events::PartyStarted::match_and_decode(_log) {
|
if let Some(event) = abi::party_planner::events::PartyStarted::match_and_decode(_log) {
|
||||||
return Some(ProtocolComponent {
|
return Some(ProtocolComponent::new(&format!("0x{}", hex::encode(&event.pool)))
|
||||||
id: hex::encode(&event.pool),
|
.with_tokens(&event.tokens.clone())
|
||||||
tokens: event.tokens,
|
.with_contracts(&vec![event.pool.clone(), params.mint_impl.clone(), params.swap_impl.clone()])
|
||||||
contracts: vec![event.pool.clone(), params.mint_impl.clone(), params.swap_impl.clone()],
|
.as_swap_type("liquidityparty_pool", ImplementationType::Vm));
|
||||||
static_att: vec![],
|
|
||||||
change: ChangeType::Creation.into(),
|
|
||||||
protocol_type: Some(ProtocolType {
|
|
||||||
name: "liquidity_party".to_string(),
|
|
||||||
financial_type: FinancialType::Swap.into(),
|
|
||||||
attribute_schema: vec![],
|
|
||||||
implementation_type: ImplementationType::Vm.into(),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
|
|||||||
Reference in New Issue
Block a user