/**
* Created by Pragma Labs
* SPDX-License-Identifier: MIT
*/
pragma solidity 0.8.22;
import { ActionData } from "../../../lib/accounts-v2/src/interfaces/IActionBase.sol";
import { AssetValueAndRiskFactors } from "../../../lib/accounts-v2/src/libraries/AssetValuationLib.sol";
import { IFactory } from "../interfaces/IFactory.sol";
import { IPermit2 } from "../../../lib/accounts-v2/src/interfaces/IPermit2.sol";
import { IRegistry } from "../interfaces/IRegistry.sol";
library ArcadiaLogic {
// The contract address of the Arcadia Factory.
IFactory internal constant FACTORY = IFactory(0xDa14Fdd72345c4d2511357214c5B89A919768e59);
// The contract address of the Arcadia Registry.
IRegistry internal constant REGISTRY = IRegistry(0xd0690557600eb8Be8391D1d97346e2aab5300d5f);
/**
* @notice Returns the trusted USD prices for 1e18 gwei of token0 and token1.
* @param token0 The contract address of token0.
* @param token1 The contract address of token1.
* @param usdPriceToken0 The USD price of 1e18 gwei of token0, with 18 decimals precision.
* @param usdPriceToken1 The USD price of 1e18 gwei of token1, with 18 decimals precision.
*/
function _getValuesInUsd(address token0, address token1)
internal
view
returns (uint256 usdPriceToken0, uint256 usdPriceToken1)
{
address[] memory assets = new address[](2);
assets[0] = token0;
assets[1] = token1;
uint256[] memory assetAmounts = new uint256[](2);
assetAmounts[0] = 1e18;
assetAmounts[1] = 1e18;
AssetValueAndRiskFactors[] memory valuesAndRiskFactors =
REGISTRY.getValuesInUsd(address(0), assets, new uint256[](2), assetAmounts);
(usdPriceToken0, usdPriceToken1) = (valuesAndRiskFactors[0].assetValue, valuesAndRiskFactors[1].assetValue);
}
/**
* @notice Encodes the action data for the flash-action used to compound a Uniswap V3 Liquidity Position.
* @param initiator The address of the initiator.
* @param nonfungiblePositionManager The contract address of the UniswapV3 NonfungiblePositionManager.
* @param id The id of the Liquidity Position.
* @return actionData Bytes string with the encoded actionData.
*/
function _encodeActionData(address initiator, address nonfungiblePositionManager, uint256 id)
internal
pure
returns (bytes memory actionData)
{
// Encode Uniswap V3 position that has to be withdrawn from and deposited back into the Account.
address[] memory assets_ = new address[](1);
assets_[0] = nonfungiblePositionManager;
uint256[] memory assetIds_ = new uint256[](1);
assetIds_[0] = id;
uint256[] memory assetAmounts_ = new uint256[](1);
assetAmounts_[0] = 1;
uint256[] memory assetTypes_ = new uint256[](1);
assetTypes_[0] = 2;
ActionData memory assetData =
ActionData({ assets: assets_, assetIds: assetIds_, assetAmounts: assetAmounts_, assetTypes: assetTypes_ });
// Empty data objects that have to be encoded when calling flashAction(), but that are not used for this specific flash-action.
bytes memory signature;
ActionData memory transferFromOwner;
IPermit2.PermitBatchTransferFrom memory permit;
// Data required by this contract when Account does the executeAction() callback during the flash-action.
bytes memory compoundData = abi.encode(assetData, initiator);
// Encode the actionData.
actionData = abi.encode(assetData, transferFromOwner, permit, signature, compoundData);
}
}
/**
* Created by Pragma Labs
* SPDX-License-Identifier: BUSL-1.1
*/
pragma solidity 0.8.22;
// Struct with risk and valuation related information for a certain asset.
struct AssetValueAndRiskFactors {
// The value of the asset.
uint256 assetValue;
// The collateral factor of the asset, for a given creditor.
uint256 collateralFactor;
// The liquidation factor of the asset, for a given creditor.
uint256 liquidationFactor;
}
/**
* @title Asset Valuation Library
* @author Pragma Labs
* @notice The Asset Valuation Library is responsible for calculating the risk weighted values of combinations of assets.
*/
library AssetValuationLib {
/*///////////////////////////////////////////////////////////////
CONSTANTS
///////////////////////////////////////////////////////////////*/
uint256 internal constant ONE_4 = 10_000;
/*///////////////////////////////////////////////////////////////
RISK FACTORS
///////////////////////////////////////////////////////////////*/
/**
* @notice Calculates the collateral value given a combination of asset values and corresponding collateral factors.
* @param valuesAndRiskFactors Array of asset values and corresponding collateral factors.
* @return collateralValue The collateral value of the given assets.
*/
function _calculateCollateralValue(AssetValueAndRiskFactors[] memory valuesAndRiskFactors)
internal
pure
returns (uint256 collateralValue)
{
for (uint256 i; i < valuesAndRiskFactors.length; ++i) {
collateralValue += valuesAndRiskFactors[i].assetValue * valuesAndRiskFactors[i].collateralFactor;
}
collateralValue = collateralValue / ONE_4;
}
/**
* @notice Calculates the liquidation value given a combination of asset values and corresponding liquidation factors.
* @param valuesAndRiskFactors List of asset values and corresponding liquidation factors.
* @return liquidationValue The liquidation value of the given assets.
*/
function _calculateLiquidationValue(AssetValueAndRiskFactors[] memory valuesAndRiskFactors)
internal
pure
returns (uint256 liquidationValue)
{
for (uint256 i; i < valuesAndRiskFactors.length; ++i) {
liquidationValue += valuesAndRiskFactors[i].assetValue * valuesAndRiskFactors[i].liquidationFactor;
}
liquidationValue = liquidationValue / ONE_4;
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)
/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.
abstract contract ERC20 {
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event Transfer(address indexed from, address indexed to, uint256 amount);
event Approval(address indexed owner, address indexed spender, uint256 amount);
/*//////////////////////////////////////////////////////////////
METADATA STORAGE
//////////////////////////////////////////////////////////////*/
string public name;
string public symbol;
uint8 public immutable decimals;
/*//////////////////////////////////////////////////////////////
ERC20 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
/*//////////////////////////////////////////////////////////////
EIP-2612 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 internal immutable INITIAL_CHAIN_ID;
bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;
mapping(address => uint256) public nonces;
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
constructor(
string memory _name,
string memory _symbol,
uint8 _decimals
) {
name = _name;
symbol = _symbol;
decimals = _decimals;
INITIAL_CHAIN_ID = block.chainid;
INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
}
/*//////////////////////////////////////////////////////////////
ERC20 LOGIC
//////////////////////////////////////////////////////////////*/
function approve(address spender, uint256 amount) public virtual returns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function transfer(address to, uint256 amount) public virtual returns (bool) {
balanceOf[msg.sender] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(msg.sender, to, amount);
return true;
}
function transferFrom(
address from,
address to,
uint256 amount
) public virtual returns (bool) {
uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.
if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;
balanceOf[from] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(from, to, amount);
return true;
}
/*//////////////////////////////////////////////////////////////
EIP-2612 LOGIC
//////////////////////////////////////////////////////////////*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public virtual {
require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");
// Unchecked because the only math done is incrementing
// the owner's nonce which cannot realistically overflow.
unchecked {
address recoveredAddress = ecrecover(
keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR(),
keccak256(
abi.encode(
keccak256(
"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
),
owner,
spender,
value,
nonces[owner]++,
deadline
)
)
)
),
v,
r,
s
);
require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");
allowance[recoveredAddress][spender] = value;
}
emit Approval(owner, spender, value);
}
function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
}
function computeDomainSeparator() internal view virtual returns (bytes32) {
return
keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes(name)),
keccak256("1"),
block.chainid,
address(this)
)
);
}
/*//////////////////////////////////////////////////////////////
INTERNAL MINT/BURN LOGIC
//////////////////////////////////////////////////////////////*/
function _mint(address to, uint256 amount) internal virtual {
totalSupply += amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(address(0), to, amount);
}
function _burn(address from, uint256 amount) internal virtual {
balanceOf[from] -= amount;
// Cannot underflow because a user's balance
// will never be larger than the total supply.
unchecked {
totalSupply -= amount;
}
emit Transfer(from, address(0), amount);
}
}
// https://github.com/Uniswap/v3-core/blob/main/contracts/libraries/FixedPoint96.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.22;
/// @title FixedPoint96
/// @notice A library for handling binary fixed point numbers, see https://en.wikipedia.org/wiki/Q_(number_format)
/// @dev Used in SqrtPriceMath.sol
library FixedPoint96 {
uint8 internal constant RESOLUTION = 96;
uint256 internal constant Q96 = 0x1000000000000000000000000;
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Arithmetic library with operations for fixed-point numbers.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/FixedPointMathLib.sol)
/// @author Inspired by USM (https://github.com/usmfum/USM/blob/master/contracts/WadMath.sol)
library FixedPointMathLib {
/*//////////////////////////////////////////////////////////////
SIMPLIFIED FIXED POINT OPERATIONS
//////////////////////////////////////////////////////////////*/
uint256 internal constant MAX_UINT256 = 2**256 - 1;
uint256 internal constant WAD = 1e18; // The scalar of ETH and most ERC20s.
function mulWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivDown(x, y, WAD); // Equivalent to (x * y) / WAD rounded down.
}
function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivUp(x, y, WAD); // Equivalent to (x * y) / WAD rounded up.
}
function divWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivDown(x, WAD, y); // Equivalent to (x * WAD) / y rounded down.
}
function divWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivUp(x, WAD, y); // Equivalent to (x * WAD) / y rounded up.
}
/*//////////////////////////////////////////////////////////////
LOW LEVEL FIXED POINT OPERATIONS
//////////////////////////////////////////////////////////////*/
function mulDivDown(
uint256 x,
uint256 y,
uint256 denominator
) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))
if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
revert(0, 0)
}
// Divide x * y by the denominator.
z := div(mul(x, y), denominator)
}
}
function mulDivUp(
uint256 x,
uint256 y,
uint256 denominator
) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))
if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
revert(0, 0)
}
// If x * y modulo the denominator is strictly greater than 0,
// 1 is added to round up the division of x * y by the denominator.
z := add(gt(mod(mul(x, y), denominator), 0), div(mul(x, y), denominator))
}
}
function rpow(
uint256 x,
uint256 n,
uint256 scalar
) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
switch x
case 0 {
switch n
case 0 {
// 0 ** 0 = 1
z := scalar
}
default {
// 0 ** n = 0
z := 0
}
}
default {
switch mod(n, 2)
case 0 {
// If n is even, store scalar in z for now.
z := scalar
}
default {
// If n is odd, store x in z for now.
z := x
}
// Shifting right by 1 is like dividing by 2.
let half := shr(1, scalar)
for {
// Shift n right by 1 before looping to halve it.
n := shr(1, n)
} n {
// Shift n right by 1 each iteration to halve it.
n := shr(1, n)
} {
// Revert immediately if x ** 2 would overflow.
// Equivalent to iszero(eq(div(xx, x), x)) here.
if shr(128, x) {
revert(0, 0)
}
// Store x squared.
let xx := mul(x, x)
// Round to the nearest number.
let xxRound := add(xx, half)
// Revert if xx + half overflowed.
if lt(xxRound, xx) {
revert(0, 0)
}
// Set x to scaled xxRound.
x := div(xxRound, scalar)
// If n is even:
if mod(n, 2) {
// Compute z * x.
let zx := mul(z, x)
// If z * x overflowed:
if iszero(eq(div(zx, x), z)) {
// Revert if x is non-zero.
if iszero(iszero(x)) {
revert(0, 0)
}
}
// Round to the nearest number.
let zxRound := add(zx, half)
// Revert if zx + half overflowed.
if lt(zxRound, zx) {
revert(0, 0)
}
// Return properly scaled zxRound.
z := div(zxRound, scalar)
}
}
}
}
}
/*//////////////////////////////////////////////////////////////
GENERAL NUMBER UTILITIES
//////////////////////////////////////////////////////////////*/
function sqrt(uint256 x) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
let y := x // We start y at x, which will help us make our initial estimate.
z := 181 // The "correct" value is 1, but this saves a multiplication later.
// This segment is to get a reasonable initial estimate for the Babylonian method. With a bad
// start, the correct # of bits increases ~linearly each iteration instead of ~quadratically.
// We check y >= 2^(k + 8) but shift right by k bits
// each branch to ensure that if x >= 256, then y >= 256.
if iszero(lt(y, 0x10000000000000000000000000000000000)) {
y := shr(128, y)
z := shl(64, z)
}
if iszero(lt(y, 0x1000000000000000000)) {
y := shr(64, y)
z := shl(32, z)
}
if iszero(lt(y, 0x10000000000)) {
y := shr(32, y)
z := shl(16, z)
}
if iszero(lt(y, 0x1000000)) {
y := shr(16, y)
z := shl(8, z)
}
// Goal was to get z*z*y within a small factor of x. More iterations could
// get y in a tighter range. Currently, we will have y in [256, 256*2^16).
// We ensured y >= 256 so that the relative difference between y and y+1 is small.
// That's not possible if x < 256 but we can just verify those cases exhaustively.
// Now, z*z*y <= x < z*z*(y+1), and y <= 2^(16+8), and either y >= 256, or x < 256.
// Correctness can be checked exhaustively for x < 256, so we assume y >= 256.
// Then z*sqrt(y) is within sqrt(257)/sqrt(256) of sqrt(x), or about 20bps.
// For s in the range [1/256, 256], the estimate f(s) = (181/1024) * (s+1) is in the range
// (1/2.84 * sqrt(s), 2.84 * sqrt(s)), with largest error when s = 1 and when s = 256 or 1/256.
// Since y is in [256, 256*2^16), let a = y/65536, so that a is in [1/256, 256). Then we can estimate
// sqrt(y) using sqrt(65536) * 181/1024 * (a + 1) = 181/4 * (y + 65536)/65536 = 181 * (y + 65536)/2^18.
// There is no overflow risk here since y < 2^136 after the first branch above.
z := shr(18, mul(z, add(y, 65536))) // A mul() is saved from starting z at 181.
// Given the worst case multiplicative error of 2.84 above, 7 iterations should be enough.
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
// If x+1 is a perfect square, the Babylonian method cycles between
// floor(sqrt(x)) and ceil(sqrt(x)). This statement ensures we return floor.
// See: https://en.wikipedia.org/wiki/Integer_square_root#Using_only_integer_division
// Since the ceil is rare, we save gas on the assignment and repeat division in the rare case.
// If you don't care whether the floor or ceil square root is returned, you can remove this statement.
z := sub(z, lt(div(x, z), z))
}
}
function unsafeMod(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Mod x by y. Note this will return
// 0 instead of reverting if y is zero.
z := mod(x, y)
}
}
function unsafeDiv(uint256 x, uint256 y) internal pure returns (uint256 r) {
/// @solidity memory-safe-assembly
assembly {
// Divide x by y. Note this will return
// 0 instead of reverting if y is zero.
r := div(x, y)
}
}
function unsafeDivUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Add 1 to x * y if x % y > 0. Note this will
// return 0 instead of reverting if y is zero.
z := add(gt(mod(x, y), 0), div(x, y))
}
}
}
// https://github.com/Uniswap/v3-core/blob/main/contracts/libraries/FullMath.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.22;
/// @title Contains 512-bit math functions
/// @notice Facilitates multiplication and division that can have overflow of an intermediate value without any loss of precision
/// @dev Handles "phantom overflow" i.e., allows multiplication and division where an intermediate value overflows 256 bits
library FullMath {
/// @notice Calculates floor(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
/// @param a The multiplicand
/// @param b The multiplier
/// @param denominator The divisor
/// @return result The 256-bit result
/// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv
function mulDiv(uint256 a, uint256 b, uint256 denominator) internal pure returns (uint256 result) {
unchecked {
// 512-bit multiply [prod1 prod0] = a * b
// Compute the product mod 2**256 and mod 2**256 - 1
// then use the Chinese Remainder Theorem to reconstruct
// the 512 bit result. The result is stored in two 256
// variables such that product = prod1 * 2**256 + prod0
uint256 prod0; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly {
let mm := mulmod(a, b, not(0))
prod0 := mul(a, b)
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
// Handle non-overflow cases, 256 by 256 division
if (prod1 == 0) {
require(denominator > 0);
assembly {
result := div(prod0, denominator)
}
return result;
}
// Make sure the result is less than 2**256.
// Also prevents denominator == 0
require(denominator > prod1);
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from [prod1 prod0]
// Compute remainder using mulmod
uint256 remainder;
assembly {
remainder := mulmod(a, b, denominator)
}
// Subtract 256 bit number from 512 bit number
assembly {
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
// Factor powers of two out of denominator
// Compute largest power of two divisor of denominator.
// Always >= 1.
uint256 twos = (type(uint256).max - denominator + 1) & denominator;
// Divide denominator by power of two
assembly {
denominator := div(denominator, twos)
}
// Divide [prod1 prod0] by the factors of two
assembly {
prod0 := div(prod0, twos)
}
// Shift in bits from prod1 into prod0. For this we need
// to flip `twos` such that it is 2**256 / twos.
// If twos is zero, then it becomes one
assembly {
twos := add(div(sub(0, twos), twos), 1)
}
prod0 |= prod1 * twos;
// Invert denominator mod 2**256
// Now that denominator is an odd number, it has an inverse
// modulo 2**256 such that denominator * inv = 1 mod 2**256.
// Compute the inverse by starting with a seed that is correct
// correct for four bits. That is, denominator * inv = 1 mod 2**4
uint256 inv = (3 * denominator) ^ 2;
// Now use Newton-Raphson iteration to improve the precision.
// Thanks to Hensel's lifting lemma, this also works in modular
// arithmetic, doubling the correct bits in each step.
inv *= 2 - denominator * inv; // inverse mod 2**8
inv *= 2 - denominator * inv; // inverse mod 2**16
inv *= 2 - denominator * inv; // inverse mod 2**32
inv *= 2 - denominator * inv; // inverse mod 2**64
inv *= 2 - denominator * inv; // inverse mod 2**128
inv *= 2 - denominator * inv; // inverse mod 2**256
// Because the division is now exact we can divide by multiplying
// with the modular inverse of denominator. This will give us the
// correct result modulo 2**256. Since the precoditions guarantee
// that the outcome is less than 2**256, this is the final result.
// We don't need to compute the high bits of the result and prod1
// is no longer required.
result = prod0 * inv;
}
return result;
}
}
/**
* Created by Pragma Labs
* SPDX-License-Identifier: MIT
*/
pragma solidity 0.8.22;
interface IAccount {
function flashAction(address actionTarget, bytes calldata actionData) external;
}
/**
* Created by Pragma Labs
* SPDX-License-Identifier: BUSL-1.1
*/
pragma solidity 0.8.22;
// Struct with information to pass to and from the actionTarget.
struct ActionData {
// Array of the contract addresses of the assets.
address[] assets;
// Array of the IDs of the assets.
uint256[] assetIds;
// Array with the amounts of the assets.
uint256[] assetAmounts;
// Array with the types of the assets.
uint256[] assetTypes;
}
interface IActionBase {
/**
* @notice Calls an external target contract with arbitrary calldata.
* @param actionTargetData A bytes object containing the encoded input for the actionTarget.
* @return resultData An actionAssetData struct with the final balances of this actionTarget contract.
*/
function executeAction(bytes calldata actionTargetData) external returns (ActionData memory);
}
/**
* Created by Pragma Labs
* SPDX-License-Identifier: MIT
*/
pragma solidity 0.8.22;
interface IFactory {
/**
* @notice Checks if a contract is an Account.
* @param account The contract address of the Account.
* @return bool indicating if the address is an Account or not.
*/
function isAccount(address account) external view returns (bool);
}
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.22;
/// @title Non-fungible token for positions
/// @notice Wraps Uniswap V3 positions in a non-fungible token interface which allows for them to be transferred
/// and authorized.
struct CollectParams {
uint256 tokenId;
address recipient;
uint128 amount0Max;
uint128 amount1Max;
}
struct IncreaseLiquidityParams {
uint256 tokenId;
uint256 amount0Desired;
uint256 amount1Desired;
uint256 amount0Min;
uint256 amount1Min;
uint256 deadline;
}
interface INonfungiblePositionManager {
function approve(address spender, uint256 tokenId) external;
function collect(CollectParams calldata params) external payable returns (uint256 amount0, uint256 amount1);
function positions(uint256 tokenId)
external
view
returns (
uint96 nonce,
address operator,
address token0,
address token1,
uint24 fee,
int24 tickLower,
int24 tickUpper,
uint128 liquidity,
uint256 feeGrowthInside0LastX128,
uint256 feeGrowthInside1LastX128,
uint128 tokensOwed0,
uint128 tokensOwed1
);
function increaseLiquidity(IncreaseLiquidityParams calldata params)
external
payable
returns (uint128 liquidity, uint256 amount0, uint256 amount1);
}
/**
* Created by Pragma Labs
* SPDX-License-Identifier: MIT
*/
pragma solidity 0.8.22;
interface IPermit2 {
/**
* @notice The token and amount details for a transfer signed in the permit transfer signature
*/
struct TokenPermissions {
// ERC20 token address
address token;
// the maximum amount that can be spent
uint256 amount;
}
/**
* @notice Used to reconstruct the signed permit message for multiple token transfers
* @dev Do not need to pass in spender address as it is required that it is msg.sender
* @dev Note that a user still signs over a spender address
*/
struct PermitBatchTransferFrom {
// the tokens and corresponding amounts permitted for a transfer
TokenPermissions[] permitted;
// a unique value for every token owner's signature to prevent signature replays
uint256 nonce;
// deadline on the permit signature
uint256 deadline;
}
/**
* @notice Specifies the recipient address and amount for batched transfers.
* @dev Recipients and amounts correspond to the index of the signed token permissions array.
* @dev Reverts if the requested amount is greater than the permitted signed amount.
*/
struct SignatureTransferDetails {
// recipient address
address to;
// spender requested amount
uint256 requestedAmount;
}
/**
* @notice Transfers multiple tokens using a signed permit message
* @param permit The permit data signed over by the owner
* @param owner The owner of the tokens to transfer
* @param transferDetails Specifies the recipient and requested amount for the token transfer
* @param signature The signature to verify
*/
function permitTransferFrom(
PermitBatchTransferFrom memory permit,
SignatureTransferDetails[] calldata transferDetails,
address owner,
bytes calldata signature
) external;
}
/**
* Created by Pragma Labs
* SPDX-License-Identifier: MIT
*/
pragma solidity 0.8.22;
import { AssetValueAndRiskFactors } from "../../../lib/accounts-v2/src/libraries/AssetValuationLib.sol";
interface IRegistry {
function getValuesInUsd(
address creditor,
address[] calldata assets,
uint256[] calldata assetIds,
uint256[] calldata assetAmounts
) external view returns (AssetValueAndRiskFactors[] memory valuesAndRiskFactors);
}
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.22;
interface IUniswapV3Pool {
function slot0()
external
view
returns (
uint160 sqrtPriceX96,
int24 tick,
uint16 observationIndex,
uint16 observationCardinality,
uint16 observationCardinalityNext,
uint8 feeProtocol,
bool unlocked
);
function swap(
address recipient,
bool zeroForOne,
int256 amountSpecified,
uint160 sqrtPriceLimitX96,
bytes calldata data
) external returns (int256 amount0, int256 amount1);
function ticks(int24 tick)
external
view
returns (
uint128 liquidityGross,
int128 liquidityNet,
uint256 feeGrowthOutside0X128,
uint256 feeGrowthOutside1X128,
int56 tickCumulativeOutside,
uint160 secondsPerLiquidityOutsideX128,
uint32 secondsOutside,
bool initialized
);
function feeGrowthGlobal0X128() external view returns (uint256 feeGrowthGlobal0X128);
function feeGrowthGlobal1X128() external view returns (uint256 feeGrowthGlobal1X128);
}
// https://github.com/Uniswap/v3-periphery/blob/main/contracts/libraries/PoolAddress.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.22;
/// @title Provides functions for deriving a pool address from the factory, tokens, and the fee
library PoolAddress {
bytes32 internal constant POOL_INIT_CODE_HASH = 0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54;
/// @notice The identifying key of the pool
struct PoolKey {
address token0;
address token1;
uint24 fee;
}
/// @notice Deterministically computes the pool address given the factory and PoolKey
/// @param factory The Uniswap V3 factory contract address
/// @param token0 Contract address of token0.
/// @param token1 Contract address of token1.
/// @param fee The fee of the pool.
/// @return pool The contract address of the V3 pool
function computeAddress(address factory, address token0, address token1, uint24 fee)
internal
pure
returns (address pool)
{
require(token0 < token1);
pool = address(
uint160(
uint256(
keccak256(
abi.encodePacked(
hex"ff", factory, keccak256(abi.encode(token0, token1, fee)), POOL_INIT_CODE_HASH
)
)
)
)
);
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
import {ERC20} from "../tokens/ERC20.sol";
/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer.
/// @dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller.
library SafeTransferLib {
/*//////////////////////////////////////////////////////////////
ETH OPERATIONS
//////////////////////////////////////////////////////////////*/
function safeTransferETH(address to, uint256 amount) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
// Transfer the ETH and store if it succeeded or not.
success := call(gas(), to, amount, 0, 0, 0, 0)
}
require(success, "ETH_TRANSFER_FAILED");
}
/*//////////////////////////////////////////////////////////////
ERC20 OPERATIONS
//////////////////////////////////////////////////////////////*/
function safeTransferFrom(
ERC20 token,
address from,
address to,
uint256 amount
) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), and(from, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "from" argument.
mstore(add(freeMemoryPointer, 36), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 100 because the length of our calldata totals up like so: 4 + 32 * 3.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 100, 0, 32)
)
}
require(success, "TRANSFER_FROM_FAILED");
}
function safeTransfer(
ERC20 token,
address to,
uint256 amount
) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
)
}
require(success, "TRANSFER_FAILED");
}
function safeApprove(
ERC20 token,
address to,
uint256 amount
) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
)
}
require(success, "APPROVE_FAILED");
}
}
//https://github.com/Uniswap/v3-core/blob/main/contracts/libraries/TickMath.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.22;
library TickMath {
/// @dev The minimum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**-128
int24 internal constant MIN_TICK = -887_272;
/// @dev The maximum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**128
int24 internal constant MAX_TICK = -MIN_TICK;
/// @dev The minimum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MIN_TICK)
uint160 internal constant MIN_SQRT_RATIO = 4_295_128_739;
/// @dev The maximum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MAX_TICK)
uint160 internal constant MAX_SQRT_RATIO = 1_461_446_703_485_210_103_287_273_052_203_988_822_378_723_970_342;
/// @notice Calculates sqrt(1.0001^tick) * 2^96
/// @dev Throws if |tick| > max tick
/// @param tick The input tick for the above formula
/// @return sqrtPriceX96 A Fixed point Q64.96 number representing the sqrt of the ratio of the two assets (token1/token0)
/// at the given tick
function getSqrtRatioAtTick(int24 tick) internal pure returns (uint160 sqrtPriceX96) {
unchecked {
uint256 absTick = tick < 0 ? uint256(-int256(tick)) : uint256(int256(tick));
require(absTick <= uint256(uint24(MAX_TICK)), "T");
uint256 ratio =
absTick & 0x1 != 0 ? 0xfffcb933bd6fad37aa2d162d1a594001 : 0x100000000000000000000000000000000;
if (absTick & 0x2 != 0) ratio = (ratio * 0xfff97272373d413259a46990580e213a) >> 128;
if (absTick & 0x4 != 0) ratio = (ratio * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128;
if (absTick & 0x8 != 0) ratio = (ratio * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128;
if (absTick & 0x10 != 0) ratio = (ratio * 0xffcb9843d60f6159c9db58835c926644) >> 128;
if (absTick & 0x20 != 0) ratio = (ratio * 0xff973b41fa98c081472e6896dfb254c0) >> 128;
if (absTick & 0x40 != 0) ratio = (ratio * 0xff2ea16466c96a3843ec78b326b52861) >> 128;
if (absTick & 0x80 != 0) ratio = (ratio * 0xfe5dee046a99a2a811c461f1969c3053) >> 128;
if (absTick & 0x100 != 0) ratio = (ratio * 0xfcbe86c7900a88aedcffc83b479aa3a4) >> 128;
if (absTick & 0x200 != 0) ratio = (ratio * 0xf987a7253ac413176f2b074cf7815e54) >> 128;
if (absTick & 0x400 != 0) ratio = (ratio * 0xf3392b0822b70005940c7a398e4b70f3) >> 128;
if (absTick & 0x800 != 0) ratio = (ratio * 0xe7159475a2c29b7443b29c7fa6e889d9) >> 128;
if (absTick & 0x1000 != 0) ratio = (ratio * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128;
if (absTick & 0x2000 != 0) ratio = (ratio * 0xa9f746462d870fdf8a65dc1f90e061e5) >> 128;
if (absTick & 0x4000 != 0) ratio = (ratio * 0x70d869a156d2a1b890bb3df62baf32f7) >> 128;
if (absTick & 0x8000 != 0) ratio = (ratio * 0x31be135f97d08fd981231505542fcfa6) >> 128;
if (absTick & 0x10000 != 0) ratio = (ratio * 0x9aa508b5b7a84e1c677de54f3e99bc9) >> 128;
if (absTick & 0x20000 != 0) ratio = (ratio * 0x5d6af8dedb81196699c329225ee604) >> 128;
if (absTick & 0x40000 != 0) ratio = (ratio * 0x2216e584f5fa1ea926041bedfe98) >> 128;
if (absTick & 0x80000 != 0) ratio = (ratio * 0x48a170391f7dc42444e8fa2) >> 128;
if (tick > 0) ratio = type(uint256).max / ratio;
// this divides by 1<<32 rounding up to go from a Q128.128 to a Q128.96.
// we then downcast because we know the result always fits within 160 bits due to our tick input constraint
// we round up in the division so getTickAtSqrtRatio of the output price is always consistent
sqrtPriceX96 = uint160((ratio >> 32) + (ratio % (1 << 32) == 0 ? 0 : 1));
}
}
/// @notice Calculates the greatest tick value such that getRatioAtTick(tick) <= ratio
/// @dev Throws in case sqrtPriceX96 < MIN_SQRT_RATIO, as MIN_SQRT_RATIO is the lowest value getRatioAtTick may
/// ever return.
/// @param sqrtPriceX96 The sqrt ratio for which to compute the tick as a Q64.96
/// @return tick The greatest tick for which the ratio is less than or equal to the input ratio
function getTickAtSqrtRatio(uint160 sqrtPriceX96) internal pure returns (int24 tick) {
unchecked {
// second inequality must be < because the price can never reach the price at the max tick
require(sqrtPriceX96 >= MIN_SQRT_RATIO && sqrtPriceX96 < MAX_SQRT_RATIO, "R");
uint256 ratio = uint256(sqrtPriceX96) << 32;
uint256 r = ratio;
uint256 msb = 0;
assembly {
let f := shl(7, gt(r, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF))
msb := or(msb, f)
r := shr(f, r)
}
assembly {
let f := shl(6, gt(r, 0xFFFFFFFFFFFFFFFF))
msb := or(msb, f)
r := shr(f, r)
}
assembly {
let f := shl(5, gt(r, 0xFFFFFFFF))
msb := or(msb, f)
r := shr(f, r)
}
assembly {
let f := shl(4, gt(r, 0xFFFF))
msb := or(msb, f)
r := shr(f, r)
}
assembly {
let f := shl(3, gt(r, 0xFF))
msb := or(msb, f)
r := shr(f, r)
}
assembly {
let f := shl(2, gt(r, 0xF))
msb := or(msb, f)
r := shr(f, r)
}
assembly {
let f := shl(1, gt(r, 0x3))
msb := or(msb, f)
r := shr(f, r)
}
assembly {
let f := gt(r, 0x1)
msb := or(msb, f)
}
if (msb >= 128) r = ratio >> (msb - 127);
else r = ratio << (127 - msb);
int256 log_2 = (int256(msb) - 128) << 64;
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(63, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(62, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(61, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(60, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(59, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(58, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(57, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(56, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(55, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(54, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(53, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(52, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(51, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(50, f))
}
int256 log_sqrt10001 = log_2 * 255_738_958_999_603_826_347_141; // 128.128 number
int24 tickLow = int24((log_sqrt10001 - 3_402_992_956_809_132_418_596_140_100_660_247_210) >> 128);
int24 tickHi = int24((log_sqrt10001 + 291_339_464_771_989_622_907_027_621_153_398_088_495) >> 128);
tick = tickLow == tickHi ? tickLow : getSqrtRatioAtTick(tickHi) <= sqrtPriceX96 ? tickHi : tickLow;
}
}
}
/**
* Created by Pragma Labs
* SPDX-License-Identifier: BUSL-1.1
*/
pragma solidity 0.8.22;
import { ActionData, IActionBase } from "../../../lib/accounts-v2/src/interfaces/IActionBase.sol";
import { ArcadiaLogic } from "../libraries/ArcadiaLogic.sol";
import { CollectParams, IncreaseLiquidityParams } from "./interfaces/INonfungiblePositionManager.sol";
import { ERC20, SafeTransferLib } from "../../../lib/accounts-v2/lib/solmate/src/utils/SafeTransferLib.sol";
import { FixedPointMathLib } from "../../../lib/accounts-v2/lib/solmate/src/utils/FixedPointMathLib.sol";
import { IAccount } from "../interfaces/IAccount.sol";
import { IUniswapV3Pool } from "./interfaces/IUniswapV3Pool.sol";
import { TickMath } from "../../../lib/accounts-v2/src/asset-modules/UniswapV3/libraries/TickMath.sol";
import { UniswapV3Logic } from "./libraries/UniswapV3Logic.sol";
/**
* @title Permissionless and Stateless Compounder for UniswapV3 Liquidity Positions.
* @author Pragma Labs
* @notice The Compounder will act as an Asset Manager for Arcadia Accounts.
* It will allow third parties to trigger the compounding functionality for a Uniswap V3 Liquidity Position in the Account.
* Compounding can only be triggered if certain conditions are met and the initiator will get a small fee for the service provided.
* The compounding will collect the fees earned by a position and increase the liquidity of the position by those fees.
* Depending on current tick of the pool and the position range, fees will be deposited in appropriate ratio.
* @dev The contract prevents frontrunning/sandwiching by comparing the actual pool price with a pool price calculated from trusted
* price feeds (oracles).
* Some oracles can however deviate from the actual price by a few percent points, this could potentially open attack vectors by manipulating
* pools and sandwiching the swap and/or increase liquidity. This asset manager should not be used for Arcadia Account that have/will have
* Uniswap V3 Liquidity Positions where one of the underlying assets is priced with such low precision oracles.
*/
contract UniswapV3Compounder is IActionBase {
using FixedPointMathLib for uint256;
using SafeTransferLib for ERC20;
/* //////////////////////////////////////////////////////////////
CONSTANTS
////////////////////////////////////////////////////////////// */
// Minimum fees value in USD to trigger the compounding of a position, with 18 decimals precision.
uint256 public immutable COMPOUND_THRESHOLD;
// The share of the fees that are paid as reward to the initiator, with 18 decimals precision.
uint256 public immutable INITIATOR_SHARE;
// The maximum lower deviation of the pools actual sqrtPriceX96,
// relative to the sqrtPriceX96 calculated with trusted price feeds, with 18 decimals precision.
uint256 public immutable LOWER_SQRT_PRICE_DEVIATION;
// The maximum upper deviation of the pools actual sqrtPriceX96,
// relative to the sqrtPriceX96 calculated with trusted price feeds, with 18 decimals precision.
uint256 public immutable UPPER_SQRT_PRICE_DEVIATION;
/* //////////////////////////////////////////////////////////////
STORAGE
////////////////////////////////////////////////////////////// */
// The Account to compound the fees for, used as transient storage.
address internal account;
// A struct with the state of a specific position, only used in memory.
struct PositionState {
address pool;
address token0;
address token1;
uint24 fee;
uint256 sqrtPriceX96;
uint256 sqrtRatioLower;
uint256 sqrtRatioUpper;
uint256 lowerBoundSqrtPriceX96;
uint256 upperBoundSqrtPriceX96;
uint256 usdPriceToken0;
uint256 usdPriceToken1;
}
// A struct with variables to track the fee balances, only used in memory.
struct Fees {
uint256 amount0;
uint256 amount1;
}
/* //////////////////////////////////////////////////////////////
ERRORS
////////////////////////////////////////////////////////////// */
error BelowThreshold();
error NotAnAccount();
error OnlyAccount();
error OnlyPool();
error Reentered();
error UnbalancedPool();
/* //////////////////////////////////////////////////////////////
EVENTS
////////////////////////////////////////////////////////////// */
event Compound(address indexed account, uint256 id);
/* //////////////////////////////////////////////////////////////
CONSTRUCTOR
////////////////////////////////////////////////////////////// */
/**
* @param compoundThreshold The minimum USD value that the compounded fees should have
* before a compoundFees() can be called, with 18 decimals precision.
* @param initiatorShare The share of the fees paid to the initiator as reward, with 18 decimals precision.
* @param tolerance The maximum deviation of the actual pool price,
* relative to the price calculated with trusted external prices of both assets, with 18 decimals precision.
* @dev The tolerance for the pool price will be converted to an upper and lower max sqrtPrice deviation,
* using the square root of the basis (one with 18 decimals precision) +- tolerance (18 decimals precision).
* The tolerance boundaries are symmetric around the price, but taking the square root will result in a different
* allowed deviation of the sqrtPriceX96 for the lower and upper boundaries.
*/
constructor(uint256 compoundThreshold, uint256 initiatorShare, uint256 tolerance) {
COMPOUND_THRESHOLD = compoundThreshold;
INITIATOR_SHARE = initiatorShare;
// SQRT_PRICE_DEVIATION is the square root of maximum/minimum price deviation.
// Sqrt halves the number of decimals.
LOWER_SQRT_PRICE_DEVIATION = FixedPointMathLib.sqrt((1e18 - tolerance) * 1e18);
UPPER_SQRT_PRICE_DEVIATION = FixedPointMathLib.sqrt((1e18 + tolerance) * 1e18);
}
/* ///////////////////////////////////////////////////////////////
COMPOUNDING LOGIC
/////////////////////////////////////////////////////////////// */
/**
* @notice Compounds the fees earned by a UniswapV3 Liquidity Position owned by an Arcadia Account.
* @param account_ The Arcadia Account owning the position.
* @param id The id of the Liquidity Position.
*/
function compoundFees(address account_, uint256 id) external {
// Store Account address, used to validate the caller of the executeAction() callback.
if (account != address(0)) revert Reentered();
if (!ArcadiaLogic.FACTORY.isAccount(account_)) revert NotAnAccount();
account = account_;
// Encode data for the flash-action.
bytes memory actionData =
ArcadiaLogic._encodeActionData(msg.sender, address(UniswapV3Logic.POSITION_MANAGER), id);
// Call flashAction() with this contract as actionTarget.
IAccount(account_).flashAction(address(this), actionData);
// Reset account.
account = address(0);
emit Compound(account_, id);
}
/**
* @notice Callback function called by the Arcadia Account during a flashAction.
* @param compoundData A bytes object containing a struct with the assetData of the position and the address of the initiator.
* @return assetData A struct with the asset data of the Liquidity Position.
* @dev The Liquidity Position is already transferred to this contract before executeAction() is called.
* @dev This function will trigger the following actions:
* - Verify that the pool's current price is initially within the defined tolerance price range.
* - Collects the fees earned by the position.
* - Verify that the fee value is bigger than the threshold required to trigger a compoundFees.
* - Rebalance the fee amounts so that the maximum amount of liquidity can be added, swaps one token to another if needed.
* - Verify that the pool's price is still within the defined tolerance price range after the swap.
* - Increases the liquidity of the current position with those fees.
* - Transfers a reward + dust amounts to the initiator.
*/
function executeAction(bytes calldata compoundData) external override returns (ActionData memory assetData) {
// Caller should be the Account, provided as input in compoundFees().
if (msg.sender != account) revert OnlyAccount();
// Decode compoundData.
address initiator;
(assetData, initiator) = abi.decode(compoundData, (ActionData, address));
uint256 id = assetData.assetIds[0];
// Fetch and cache all position related data.
PositionState memory position = getPositionState(id);
// Check that pool is initially balanced.
// Prevents sandwiching attacks when swapping and/or adding liquidity.
if (isPoolUnbalanced(position)) revert UnbalancedPool();
// Collect fees.
Fees memory fees;
(fees.amount0, fees.amount1) = UniswapV3Logic.POSITION_MANAGER.collect(
CollectParams({
tokenId: id,
recipient: address(this),
amount0Max: type(uint128).max,
amount1Max: type(uint128).max
})
);
// Total value of the fees must be greater than the threshold.
if (isBelowThreshold(position, fees)) revert BelowThreshold();
// Subtract initiator reward from fees, these will be send to the initiator.
fees.amount0 -= fees.amount0.mulDivDown(INITIATOR_SHARE, 1e18);
fees.amount1 -= fees.amount1.mulDivDown(INITIATOR_SHARE, 1e18);
// Rebalance the fee amounts so that the maximum amount of liquidity can be added.
// The Pool must still be balanced after the swap.
(bool zeroToOne, uint256 amountOut) = getSwapParameters(position, fees);
if (_swap(position, zeroToOne, amountOut)) revert UnbalancedPool();
// We increase the fee amount of tokenOut, but we do not decrease the fee amount of tokenIn.
// This guarantees that tokenOut is the limiting factor when increasing liquidity and not tokenIn.
// As a consequence, slippage will result in less tokenIn going to the initiator,
// instead of more tokenOut going to the initiator.
if (zeroToOne) fees.amount1 += amountOut;
else fees.amount0 += amountOut;
// Increase liquidity of the position.
// The approval for at least one token after increasing liquidity will remain non-zero.
// We have to set approval first to 0 for ERC20 tokens that require the approval to be set to zero
// before setting it to a non-zero value.
ERC20(position.token0).safeApprove(address(UniswapV3Logic.POSITION_MANAGER), 0);
ERC20(position.token0).safeApprove(address(UniswapV3Logic.POSITION_MANAGER), fees.amount0);
ERC20(position.token1).safeApprove(address(UniswapV3Logic.POSITION_MANAGER), 0);
ERC20(position.token1).safeApprove(address(UniswapV3Logic.POSITION_MANAGER), fees.amount1);
UniswapV3Logic.POSITION_MANAGER.increaseLiquidity(
IncreaseLiquidityParams({
tokenId: id,
amount0Desired: fees.amount0,
amount1Desired: fees.amount1,
amount0Min: 0,
amount1Min: 0,
deadline: block.timestamp
})
);
// Initiator rewards are transferred to the initiator.
uint256 balance0 = ERC20(position.token0).balanceOf(address(this));
uint256 balance1 = ERC20(position.token1).balanceOf(address(this));
if (balance0 > 0) ERC20(position.token0).safeTransfer(initiator, balance0);
if (balance1 > 0) ERC20(position.token1).safeTransfer(initiator, balance1);
// Approve Account to deposit Liquidity Position back into the Account.
UniswapV3Logic.POSITION_MANAGER.approve(msg.sender, id);
}
/* ///////////////////////////////////////////////////////////////
SWAPPING LOGIC
/////////////////////////////////////////////////////////////// */
/**
* @notice returns the swap parameters to optimize the total value of fees that can be added as liquidity.
* @param position Struct with the position data.
* @param fees Struct with the fee balances.
* @return zeroToOne Bool indicating if token0 has to be swapped to token1 or opposite.
* @return amountOut The amount of tokenOut.
* @dev We assume the fees amount are small compared to the liquidity of the pool,
* hence we neglect slippage when optimizing the swap amounts.
* Slippage must be limited, the contract enforces that the pool is still balanced after the swap and
* since we use swaps with amountOut, slippage will result in less reward of tokenIn for the initiator,
* not less liquidity increased.
*/
function getSwapParameters(PositionState memory position, Fees memory fees)
public
pure
returns (bool zeroToOne, uint256 amountOut)
{
if (position.sqrtPriceX96 >= position.sqrtRatioUpper) {
// Position is out of range and fully in token 1.
// Swap full amount of token0 to token1.
zeroToOne = true;
amountOut = UniswapV3Logic._getAmountOut(position.sqrtPriceX96, true, fees.amount0);
} else if (position.sqrtPriceX96 <= position.sqrtRatioLower) {
// Position is out of range and fully in token 0.
// Swap full amount of token1 to token0.
zeroToOne = false;
amountOut = UniswapV3Logic._getAmountOut(position.sqrtPriceX96, false, fees.amount1);
} else {
// Position is in range.
// Rebalance fees so that the ratio of the fee values matches with ratio of the position.
uint256 targetRatio =
UniswapV3Logic._getTargetRatio(position.sqrtPriceX96, position.sqrtRatioLower, position.sqrtRatioUpper);
// Calculate the total fee value in token1 equivalent:
uint256 fee0ValueInToken1 = UniswapV3Logic._getAmountOut(position.sqrtPriceX96, true, fees.amount0);
uint256 totalFeeValueInToken1 = fees.amount1 + fee0ValueInToken1;
uint256 currentRatio = fees.amount1.mulDivDown(1e18, totalFeeValueInToken1);
if (currentRatio < targetRatio) {
// Swap token0 partially to token1.
zeroToOne = true;
amountOut = (targetRatio - currentRatio).mulDivDown(totalFeeValueInToken1, 1e18);
} else {
// Swap token1 partially to token0.
zeroToOne = false;
uint256 amountIn = (currentRatio - targetRatio).mulDivDown(totalFeeValueInToken1, 1e18);
amountOut = UniswapV3Logic._getAmountOut(position.sqrtPriceX96, false, amountIn);
}
}
}
/**
* @notice Swaps one token to the other token in the Uniswap V3 Pool of the Liquidity Position.
* @param position Struct with the position data.
* @param zeroToOne Bool indicating if token0 has to be swapped to token1 or opposite.
* @param amountOut The amount that of tokenOut that must be swapped to.
* @return isPoolUnbalanced_ Bool indicating if the pool is unbalanced due to slippage of the swap.
*/
function _swap(PositionState memory position, bool zeroToOne, uint256 amountOut)
internal
returns (bool isPoolUnbalanced_)
{
// Don't do swaps with zero amount.
if (amountOut == 0) return false;
// Pool should still be balanced (within tolerance boundaries) after the swap.
uint160 sqrtPriceLimitX96 =
uint160(zeroToOne ? position.lowerBoundSqrtPriceX96 : position.upperBoundSqrtPriceX96);
// Do the swap.
bytes memory data = abi.encode(position.token0, position.token1, position.fee);
(int256 deltaAmount0, int256 deltaAmount1) =
IUniswapV3Pool(position.pool).swap(address(this), zeroToOne, -int256(amountOut), sqrtPriceLimitX96, data);
// Check if pool is still balanced (sqrtPriceLimitX96 is reached before an amountOut of tokenOut is received).
isPoolUnbalanced_ = (amountOut > (zeroToOne ? uint256(-deltaAmount1) : uint256(-deltaAmount0)));
}
/**
* @notice Callback after executing a swap via IUniswapV3Pool.swap.
* @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by
* the end of the swap. If positive, the callback must send that amount of token0 to the pool.
* @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by
* the end of the swap. If positive, the callback must send that amount of token1 to the pool.
* @param data Any data passed by this contract via the IUniswapV3Pool.swap() call.
*/
function uniswapV3SwapCallback(int256 amount0Delta, int256 amount1Delta, bytes calldata data) external {
// Check that callback came from an actual Uniswap V3 pool.
(address token0, address token1, uint24 fee) = abi.decode(data, (address, address, uint24));
if (UniswapV3Logic._computePoolAddress(token0, token1, fee) != msg.sender) revert OnlyPool();
if (amount0Delta > 0) {
ERC20(token0).safeTransfer(msg.sender, uint256(amount0Delta));
} else if (amount1Delta > 0) {
ERC20(token1).safeTransfer(msg.sender, uint256(amount1Delta));
}
}
/* ///////////////////////////////////////////////////////////////
POSITION AND POOL VIEW FUNCTIONS
/////////////////////////////////////////////////////////////// */
/**
* @notice Fetches all required position data from external contracts.
* @param id The id of the Liquidity Position.
* @return position Struct with the position data.
*/
function getPositionState(uint256 id) public view returns (PositionState memory position) {
// Get data of the Liquidity Position.
int24 tickLower;
int24 tickUpper;
(,, position.token0, position.token1, position.fee, tickLower, tickUpper,,,,,) =
UniswapV3Logic.POSITION_MANAGER.positions(id);
position.sqrtRatioLower = TickMath.getSqrtRatioAtTick(tickLower);
position.sqrtRatioUpper = TickMath.getSqrtRatioAtTick(tickUpper);
// Get trusted USD prices for 1e18 gwei of token0 and token1.
(position.usdPriceToken0, position.usdPriceToken1) =
ArcadiaLogic._getValuesInUsd(position.token0, position.token1);
// Get data of the Liquidity Pool.
position.pool = UniswapV3Logic._computePoolAddress(position.token0, position.token1, position.fee);
(position.sqrtPriceX96,,,,,,) = IUniswapV3Pool(position.pool).slot0();
// Calculate the square root of the relative rate sqrt(token1/token0) from the trusted USD price of both tokens.
uint256 trustedSqrtPriceX96 = UniswapV3Logic._getSqrtPriceX96(position.usdPriceToken0, position.usdPriceToken1);
// Calculate the upper and lower bounds of sqrtPriceX96 for the Pool to be balanced.
position.lowerBoundSqrtPriceX96 = trustedSqrtPriceX96.mulDivDown(LOWER_SQRT_PRICE_DEVIATION, 1e18);
position.upperBoundSqrtPriceX96 = trustedSqrtPriceX96.mulDivDown(UPPER_SQRT_PRICE_DEVIATION, 1e18);
}
/**
* @notice Returns if the total fee value in USD is below the rebalancing threshold.
* @param position Struct with the position data.
* @param fees Struct with the fees accumulated by a position.
* @return isBelowThreshold_ Bool indicating if the total fee value in USD is below the threshold.
*/
function isBelowThreshold(PositionState memory position, Fees memory fees)
public
view
returns (bool isBelowThreshold_)
{
uint256 totalValueFees = position.usdPriceToken0.mulDivDown(fees.amount0, 1e18)
+ position.usdPriceToken1.mulDivDown(fees.amount1, 1e18);
isBelowThreshold_ = totalValueFees < COMPOUND_THRESHOLD;
}
/**
* @notice returns if the pool of a Liquidity Position is unbalanced.
* @param position Struct with the position data.
* @return isPoolUnbalanced_ Bool indicating if the pool is unbalanced.
*/
function isPoolUnbalanced(PositionState memory position) public pure returns (bool isPoolUnbalanced_) {
// Check if current priceX96 of the Pool is within accepted tolerance of the calculated trusted priceX96.
isPoolUnbalanced_ = position.sqrtPriceX96 < position.lowerBoundSqrtPriceX96
|| position.sqrtPriceX96 > position.upperBoundSqrtPriceX96;
}
/* ///////////////////////////////////////////////////////////////
ERC721 HANDLER FUNCTION
/////////////////////////////////////////////////////////////// */
/**
* @notice Returns the onERC721Received selector.
* @dev Required to receive ERC721 tokens via safeTransferFrom.
*/
function onERC721Received(address, address, uint256, bytes calldata) public pure returns (bytes4) {
return this.onERC721Received.selector;
}
}
/**
* Created by Pragma Labs
* SPDX-License-Identifier: MIT
*/
pragma solidity 0.8.22;
import { FixedPoint96 } from "../../../../lib/accounts-v2/src/asset-modules/UniswapV3/libraries/FixedPoint96.sol";
import { FixedPointMathLib } from "../../../../lib/accounts-v2/lib/solmate/src/utils/FixedPointMathLib.sol";
import { FullMath } from "../../../../lib/accounts-v2/src/asset-modules/UniswapV3/libraries/FullMath.sol";
import { INonfungiblePositionManager } from "../interfaces/INonfungiblePositionManager.sol";
import { PoolAddress } from "../../../../lib/accounts-v2/src/asset-modules/UniswapV3/libraries/PoolAddress.sol";
import { TickMath } from "../../../../lib/accounts-v2/src/asset-modules/UniswapV3/libraries/TickMath.sol";
library UniswapV3Logic {
using FixedPointMathLib for uint256;
// The binary precision of sqrtPriceX96 squared.
uint256 internal constant Q192 = FixedPoint96.Q96 ** 2;
// The Uniswap V3 Factory contract.
address internal constant UNISWAP_V3_FACTORY = 0x33128a8fC17869897dcE68Ed026d694621f6FDfD;
// The Uniswap V3 NonfungiblePositionManager contract.
INonfungiblePositionManager internal constant POSITION_MANAGER =
INonfungiblePositionManager(0x03a520b32C04BF3bEEf7BEb72E919cf822Ed34f1);
/**
* @notice Computes the contract address of a Uniswap V3 Pool.
* @param token0 The contract address of token0.
* @param token1 The contract address of token1.
* @param fee The fee of the Pool.
* @return pool The contract address of the Uniswap V3 Pool.
*/
function _computePoolAddress(address token0, address token1, uint24 fee) internal pure returns (address pool) {
pool = PoolAddress.computeAddress(UNISWAP_V3_FACTORY, token0, token1, fee);
}
/**
* @notice Calculates the amountOut for a given amountIn and sqrtPriceX96 for a hypothetical
* swap without slippage.
* @param sqrtPriceX96 The square root of the price (token1/token0), with 96 binary precision.
* @param zeroToOne Bool indicating if token0 has to be swapped to token1 or opposite.
* @param amountIn The amount that of tokenIn that must be swapped to tokenOut.
* @return amountOut The amount of tokenOut.
* @dev Function will revert for all pools where the sqrtPriceX96 is bigger than type(uint128).max.
* type(uint128).max is currently more than enough for all supported pools.
* If ever the sqrtPriceX96 of a pool exceeds type(uint128).max, a different auto compounder has to be deployed,
* which does two consecutive mulDivs.
*/
function _getAmountOut(uint256 sqrtPriceX96, bool zeroToOne, uint256 amountIn)
internal
pure
returns (uint256 amountOut)
{
amountOut = zeroToOne
? FullMath.mulDiv(amountIn, sqrtPriceX96 ** 2, Q192)
: FullMath.mulDiv(amountIn, Q192, sqrtPriceX96 ** 2);
}
/**
* @notice Calculates the sqrtPriceX96 (token1/token0) from trusted USD prices of both tokens.
* @param priceToken0 The price of 1e18 tokens of token0 in USD, with 18 decimals precision.
* @param priceToken1 The price of 1e18 tokens of token1 in USD, with 18 decimals precision.
* @return sqrtPriceX96 The square root of the price (token1/token0), with 96 binary precision.
* @dev The price in Uniswap V3 is defined as:
* price = amountToken1/amountToken0.
* The usdPriceToken is defined as: usdPriceToken = amountUsd/amountToken.
* => amountToken = amountUsd/usdPriceToken.
* Hence we can derive the Uniswap V3 price as:
* price = (amountUsd/usdPriceToken1)/(amountUsd/usdPriceToken0) = usdPriceToken0/usdPriceToken1.
*/
function _getSqrtPriceX96(uint256 priceToken0, uint256 priceToken1) internal pure returns (uint160 sqrtPriceX96) {
if (priceToken1 == 0) return TickMath.MAX_SQRT_RATIO;
// Both priceTokens have 18 decimals precision and result of division should have 28 decimals precision.
// -> multiply by 1e28
// priceXd28 will overflow if priceToken0 is greater than 1.158e+49.
// For WBTC (which only has 8 decimals) this would require a bitcoin price greater than 115 792 089 237 316 198 989 824 USD/BTC.
uint256 priceXd28 = priceToken0.mulDivDown(1e28, priceToken1);
// Square root of a number with 28 decimals precision has 14 decimals precision.
uint256 sqrtPriceXd14 = FixedPointMathLib.sqrt(priceXd28);
// Change sqrtPrice from a decimal fixed point number with 14 digits to a binary fixed point number with 96 digits.
// Unsafe cast: Cast will only overflow when priceToken0/priceToken1 >= 2^128.
sqrtPriceX96 = uint160((sqrtPriceXd14 << FixedPoint96.RESOLUTION) / 1e14);
}
/**
* @notice Calculates the ratio of how much of the total value of a liquidity position has to be provided in token1.
* @param sqrtPriceX96 The square root of the current pool price (token1/token0), with 96 binary precision.
* @param sqrtRatioLower The square root price of the lower tick of the liquidity position.
* @param sqrtRatioUpper The square root price of the upper tick of the liquidity position.
* @return targetRatio The ratio of the value of token1 compared to the total value of the position, with 18 decimals precision.
* @dev Function will revert for all pools where the sqrtPriceX96 is bigger than type(uint128).max.
* type(uint128).max is currently more than enough for all supported pools.
* If ever the sqrtPriceX96 of a pool exceeds type(uint128).max, a different auto compounder has to be deployed,
* which does two consecutive mulDivs.
* @dev Derivation of the formula:
* 1) The ratio is defined as:
* R = valueToken1 / (valueToken0 + valueToken1)
* If we express all values in token1 en use the current pool price to denominate token0 in token1:
* R = amount1 / (amount0 * sqrtPrice² + amount1)
* 2) Amount0 for a given liquidity position of a Uniswap V3 pool is given as:
* Amount0 = liquidity * (sqrtRatioUpper - sqrtPrice) / (sqrtRatioUpper * sqrtPrice)
* 3) Amount1 for a given liquidity position of a Uniswap V3 pool is given as:
* Amount1 = liquidity * (sqrtPrice - sqrtRatioLower)
* 4) Combining 1), 2) and 3) and simplifying we get:
* R = [sqrtPrice - sqrtRatioLower] / [2 * sqrtPrice - sqrtRatioLower - sqrtPrice² / sqrtRatioUpper]
*/
function _getTargetRatio(uint256 sqrtPriceX96, uint256 sqrtRatioLower, uint256 sqrtRatioUpper)
internal
pure
returns (uint256 targetRatio)
{
uint256 numerator = sqrtPriceX96 - sqrtRatioLower;
uint256 denominator = 2 * sqrtPriceX96 - sqrtRatioLower - sqrtPriceX96 ** 2 / sqrtRatioUpper;
targetRatio = numerator.mulDivDown(1e18, denominator);
}
}
{
"compilationTarget": {
"src/compounders/uniswap-v3/UniswapV3Compounder.sol": "UniswapV3Compounder"
},
"evmVersion": "shanghai",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": [
":@ensdomains/=lib/accounts-v2/lib/slipstream/node_modules/@ensdomains/",
":@nomad-xyz/=lib/accounts-v2/lib/slipstream/lib/ExcessivelySafeCall/",
":@openzeppelin/=lib/accounts-v2/lib/openzeppelin-contracts/",
":@solidity-parser/=lib/accounts-v2/lib/slipstream/node_modules/solhint/node_modules/@solidity-parser/",
":@uniswap/v2-core/contracts/=lib/accounts-v2/./test/utils/fixtures/swap-router-02/",
":@uniswap/v2-core/contracts/=lib/accounts-v2/test/utils/fixtures/swap-router-02/",
":@uniswap/v3-core/=lib/accounts-v2/lib/v3-core/",
":@uniswap/v3-core/contracts/=lib/accounts-v2/lib/v3-core/contracts/",
":@uniswap/v3-periphery/=lib/accounts-v2/lib/v3-periphery/",
":@uniswap/v3-periphery/contracts/=lib/accounts-v2/lib/v3-periphery/contracts/",
":ExcessivelySafeCall/=lib/accounts-v2/lib/slipstream/lib/ExcessivelySafeCall/src/",
":accounts-v2/=lib/accounts-v2/",
":base64-sol/=lib/accounts-v2/lib/slipstream/lib/base64/",
":base64/=lib/accounts-v2/lib/slipstream/lib/base64/",
":contracts/=lib/accounts-v2/lib/slipstream/contracts/",
":ds-test/=lib/accounts-v2/lib/forge-std/lib/ds-test/src/",
":forge-std/=lib/accounts-v2/lib/forge-std/src/",
":hardhat/=lib/accounts-v2/lib/slipstream/node_modules/hardhat/",
":openzeppelin-contracts/=lib/accounts-v2/lib/openzeppelin-contracts/contracts/",
":slipstream/=lib/accounts-v2/lib/slipstream/",
":solidity-lib/=lib/accounts-v2/lib/slipstream/lib/solidity-lib/contracts/",
":solmate/=lib/accounts-v2/lib/solmate/src/",
":swap-router-contracts/=lib/accounts-v2/lib/swap-router-contracts/contracts/",
":v3-core/=lib/accounts-v2/lib/v3-core/",
":v3-periphery/=lib/accounts-v2/lib/v3-periphery/contracts/"
]
}
[{"inputs":[{"internalType":"uint256","name":"compoundThreshold","type":"uint256"},{"internalType":"uint256","name":"initiatorShare","type":"uint256"},{"internalType":"uint256","name":"tolerance","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"BelowThreshold","type":"error"},{"inputs":[],"name":"NotAnAccount","type":"error"},{"inputs":[],"name":"OnlyAccount","type":"error"},{"inputs":[],"name":"OnlyPool","type":"error"},{"inputs":[],"name":"Reentered","type":"error"},{"inputs":[],"name":"UnbalancedPool","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"id","type":"uint256"}],"name":"Compound","type":"event"},{"inputs":[],"name":"COMPOUND_THRESHOLD","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"INITIATOR_SHARE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LOWER_SQRT_PRICE_DEVIATION","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"UPPER_SQRT_PRICE_DEVIATION","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account_","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"}],"name":"compoundFees","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"compoundData","type":"bytes"}],"name":"executeAction","outputs":[{"components":[{"internalType":"address[]","name":"assets","type":"address[]"},{"internalType":"uint256[]","name":"assetIds","type":"uint256[]"},{"internalType":"uint256[]","name":"assetAmounts","type":"uint256[]"},{"internalType":"uint256[]","name":"assetTypes","type":"uint256[]"}],"internalType":"struct ActionData","name":"assetData","type":"tuple"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"getPositionState","outputs":[{"components":[{"internalType":"address","name":"pool","type":"address"},{"internalType":"address","name":"token0","type":"address"},{"internalType":"address","name":"token1","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"uint256","name":"sqrtPriceX96","type":"uint256"},{"internalType":"uint256","name":"sqrtRatioLower","type":"uint256"},{"internalType":"uint256","name":"sqrtRatioUpper","type":"uint256"},{"internalType":"uint256","name":"lowerBoundSqrtPriceX96","type":"uint256"},{"internalType":"uint256","name":"upperBoundSqrtPriceX96","type":"uint256"},{"internalType":"uint256","name":"usdPriceToken0","type":"uint256"},{"internalType":"uint256","name":"usdPriceToken1","type":"uint256"}],"internalType":"struct UniswapV3Compounder.PositionState","name":"position","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"pool","type":"address"},{"internalType":"address","name":"token0","type":"address"},{"internalType":"address","name":"token1","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"uint256","name":"sqrtPriceX96","type":"uint256"},{"internalType":"uint256","name":"sqrtRatioLower","type":"uint256"},{"internalType":"uint256","name":"sqrtRatioUpper","type":"uint256"},{"internalType":"uint256","name":"lowerBoundSqrtPriceX96","type":"uint256"},{"internalType":"uint256","name":"upperBoundSqrtPriceX96","type":"uint256"},{"internalType":"uint256","name":"usdPriceToken0","type":"uint256"},{"internalType":"uint256","name":"usdPriceToken1","type":"uint256"}],"internalType":"struct UniswapV3Compounder.PositionState","name":"position","type":"tuple"},{"components":[{"internalType":"uint256","name":"amount0","type":"uint256"},{"internalType":"uint256","name":"amount1","type":"uint256"}],"internalType":"struct UniswapV3Compounder.Fees","name":"fees","type":"tuple"}],"name":"getSwapParameters","outputs":[{"internalType":"bool","name":"zeroToOne","type":"bool"},{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"pool","type":"address"},{"internalType":"address","name":"token0","type":"address"},{"internalType":"address","name":"token1","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"uint256","name":"sqrtPriceX96","type":"uint256"},{"internalType":"uint256","name":"sqrtRatioLower","type":"uint256"},{"internalType":"uint256","name":"sqrtRatioUpper","type":"uint256"},{"internalType":"uint256","name":"lowerBoundSqrtPriceX96","type":"uint256"},{"internalType":"uint256","name":"upperBoundSqrtPriceX96","type":"uint256"},{"internalType":"uint256","name":"usdPriceToken0","type":"uint256"},{"internalType":"uint256","name":"usdPriceToken1","type":"uint256"}],"internalType":"struct UniswapV3Compounder.PositionState","name":"position","type":"tuple"},{"components":[{"internalType":"uint256","name":"amount0","type":"uint256"},{"internalType":"uint256","name":"amount1","type":"uint256"}],"internalType":"struct UniswapV3Compounder.Fees","name":"fees","type":"tuple"}],"name":"isBelowThreshold","outputs":[{"internalType":"bool","name":"isBelowThreshold_","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"pool","type":"address"},{"internalType":"address","name":"token0","type":"address"},{"internalType":"address","name":"token1","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"uint256","name":"sqrtPriceX96","type":"uint256"},{"internalType":"uint256","name":"sqrtRatioLower","type":"uint256"},{"internalType":"uint256","name":"sqrtRatioUpper","type":"uint256"},{"internalType":"uint256","name":"lowerBoundSqrtPriceX96","type":"uint256"},{"internalType":"uint256","name":"upperBoundSqrtPriceX96","type":"uint256"},{"internalType":"uint256","name":"usdPriceToken0","type":"uint256"},{"internalType":"uint256","name":"usdPriceToken1","type":"uint256"}],"internalType":"struct UniswapV3Compounder.PositionState","name":"position","type":"tuple"}],"name":"isPoolUnbalanced","outputs":[{"internalType":"bool","name":"isPoolUnbalanced_","type":"bool"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC721Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"int256","name":"amount0Delta","type":"int256"},{"internalType":"int256","name":"amount1Delta","type":"int256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"uniswapV3SwapCallback","outputs":[],"stateMutability":"nonpayable","type":"function"}]