diff --git a/substreams/Cargo.lock b/substreams/Cargo.lock index df1ce22..f90d204 100644 --- a/substreams/Cargo.lock +++ b/substreams/Cargo.lock @@ -322,6 +322,24 @@ dependencies = [ "tycho-substreams 0.2.2", ] +[[package]] +name = "ethereum-liquidityparty" +version = "0.1.0" +dependencies = [ + "anyhow", + "ethabi 18.0.0", + "hex", + "itertools 0.10.5", + "num-bigint", + "prost 0.11.9", + "serde", + "serde-sibor", + "serde_qs", + "substreams", + "substreams-ethereum", + "tycho-substreams 0.2.1 (git+https://github.com/propeller-heads/tycho-protocol-sdk.git?rev=52d5021)", +] + [[package]] name = "ethereum-maverick-v2" version = "0.1.1" diff --git a/substreams/Cargo.toml b/substreams/Cargo.toml index 328d8dc..ee4ed12 100644 --- a/substreams/Cargo.toml +++ b/substreams/Cargo.toml @@ -17,6 +17,7 @@ members = [ "ethereum-ekubo-v2", "ethereum-maverick-v2", "ethereum-balancer-v3", + "ethereum-liquidityparty", ] resolver = "2" diff --git a/substreams/ethereum-liquidityparty/Cargo.toml b/substreams/ethereum-liquidityparty/Cargo.toml new file mode 100644 index 0000000..098b39a --- /dev/null +++ b/substreams/ethereum-liquidityparty/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "ethereum-liquidityparty" +version = "0.1.0" +edition = "2021" + +[lib] +name = "ethereum_liquidityparty" +crate-type = ["cdylib"] + +[dependencies] +substreams = "0.5.22" +substreams-ethereum = "0.9.9" +prost = "0.11" +tycho-substreams = { git = "https://github.com/propeller-heads/tycho-protocol-sdk.git", rev = "52d5021" } +anyhow = "1.0.95" +ethabi = "18.0.0" +num-bigint = "0.4.6" +hex = "0.4.3" +itertools = "0.10.5" +serde = "1.0.217" +serde-sibor = "0.1.0" +serde_qs = "0.13.0" + +[build-dependencies] +anyhow = "1" +substreams-ethereum = "0.9.9" diff --git a/substreams/ethereum-liquidityparty/Makefile b/substreams/ethereum-liquidityparty/Makefile new file mode 100644 index 0000000..d9be557 --- /dev/null +++ b/substreams/ethereum-liquidityparty/Makefile @@ -0,0 +1,2 @@ +build: + cargo build --target wasm32-unknown-unknown --release \ No newline at end of file diff --git a/substreams/ethereum-liquidityparty/abi/party_planner.abi.json b/substreams/ethereum-liquidityparty/abi/party_planner.abi.json new file mode 100644 index 0000000..4b6519e --- /dev/null +++ b/substreams/ethereum-liquidityparty/abi/party_planner.abi.json @@ -0,0 +1,543 @@ +[ + { + "type": "constructor", + "inputs": [ + { + "name": "owner_", + "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" + }, + { + "name": "deployer_", + "type": "address", + "internalType": "contract IPartyPoolDeployer" + }, + { + "name": "balancedPairDeployer_", + "type": "address", + "internalType": "contract IPartyPoolDeployer" + }, + { + "name": "protocolFeePpm_", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "protocolFeeAddress_", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "getAllPools", + "inputs": [ + { + "name": "offset", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "limit", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "pools", + "type": "address[]", + "internalType": "contract IPartyPool[]" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getAllTokens", + "inputs": [ + { + "name": "offset", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "limit", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "tokens", + "type": "address[]", + "internalType": "address[]" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getPoolSupported", + "inputs": [ + { + "name": "pool", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getPoolsByToken", + "inputs": [ + { + "name": "token", + "type": "address", + "internalType": "contract IERC20" + }, + { + "name": "offset", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "limit", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "pools", + "type": "address[]", + "internalType": "contract IPartyPool[]" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "mintImpl", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "contract PartyPoolMintImpl" + } + ], + "stateMutability": "view" + }, + { + "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": "bases_", + "type": "uint256[]", + "internalType": "uint256[]" + }, + { + "name": "kappa_", + "type": "int128", + "internalType": "int128" + }, + { + "name": "swapFeePpm_", + "type": "uint256", + "internalType": "uint256" + }, + { + "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": "bases_", + "type": "uint256[]", + "internalType": "uint256[]" + }, + { + "name": "tradeFrac_", + "type": "int128", + "internalType": "int128" + }, + { + "name": "targetSlippage_", + "type": "int128", + "internalType": "int128" + }, + { + "name": "swapFeePpm_", + "type": "uint256", + "internalType": "uint256" + }, + { + "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": "owner", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "poolCount", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "poolsByTokenCount", + "inputs": [ + { + "name": "token", + "type": "address", + "internalType": "contract IERC20" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "protocolFeeAddress", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "protocolFeePpm", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "renounceOwnership", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "swapImpl", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "contract PartyPoolSwapImpl" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "tokenCount", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "transferOwnership", + "inputs": [ + { + "name": "newOwner", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "wrapper", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "contract NativeWrapper" + } + ], + "stateMutability": "view" + }, + { + "type": "event", + "name": "OwnershipTransferred", + "inputs": [ + { + "name": "previousOwner", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "newOwner", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "PartyStarted", + "inputs": [ + { + "name": "pool", + "type": "address", + "indexed": true, + "internalType": "contract IPartyPool" + }, + { + "name": "name", + "type": "string", + "indexed": false, + "internalType": "string" + }, + { + "name": "symbol", + "type": "string", + "indexed": false, + "internalType": "string" + }, + { + "name": "tokens", + "type": "address[]", + "indexed": false, + "internalType": "contract IERC20[]" + } + ], + "anonymous": false + }, + { + "type": "error", + "name": "OwnableInvalidOwner", + "inputs": [ + { + "name": "owner", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "OwnableUnauthorizedAccount", + "inputs": [ + { + "name": "account", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "SafeERC20FailedOperation", + "inputs": [ + { + "name": "token", + "type": "address", + "internalType": "address" + } + ] + } +] diff --git a/substreams/ethereum-liquidityparty/abi/party_pool.abi.json b/substreams/ethereum-liquidityparty/abi/party_pool.abi.json new file mode 100644 index 0000000..7c745e0 --- /dev/null +++ b/substreams/ethereum-liquidityparty/abi/party_pool.abi.json @@ -0,0 +1,1374 @@ +[ + { + "type": "constructor", + "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" + }, + { + "type": "receive", + "stateMutability": "payable" + }, + { + "type": "function", + "name": "LMSR", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "tuple", + "internalType": "struct LMSRStabilized.State", + "components": [ + { + "name": "nAssets", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "kappa", + "type": "int128", + "internalType": "int128" + }, + { + "name": "qInternal", + "type": "int128[]", + "internalType": "int128[]" + } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "allProtocolFeesOwed", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256[]", + "internalType": "uint256[]" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "allTokens", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address[]", + "internalType": "contract IERC20[]" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "allowance", + "inputs": [ + { + "name": "owner", + "type": "address", + "internalType": "address" + }, + { + "name": "spender", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "approve", + "inputs": [ + { + "name": "spender", + "type": "address", + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "balanceOf", + "inputs": [ + { + "name": "account", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "burn", + "inputs": [ + { + "name": "payer", + "type": "address", + "internalType": "address" + }, + { + "name": "receiver", + "type": "address", + "internalType": "address" + }, + { + "name": "lpAmount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "deadline", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "unwrap", + "type": "bool", + "internalType": "bool" + } + ], + "outputs": [ + { + "name": "withdrawAmounts", + "type": "uint256[]", + "internalType": "uint256[]" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "burnSwap", + "inputs": [ + { + "name": "payer", + "type": "address", + "internalType": "address" + }, + { + "name": "receiver", + "type": "address", + "internalType": "address" + }, + { + "name": "lpAmount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "inputTokenIndex", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "deadline", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "unwrap", + "type": "bool", + "internalType": "bool" + } + ], + "outputs": [ + { + "name": "amountOutUint", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "collectProtocolFees", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "decimals", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint8", + "internalType": "uint8" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "denominators", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256[]", + "internalType": "uint256[]" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "flashFeePpm", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "flashLoan", + "inputs": [ + { + "name": "receiver", + "type": "address", + "internalType": "contract IERC3156FlashBorrower" + }, + { + "name": "tokenAddr", + "type": "address", + "internalType": "address" + }, + { + "name": "amount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "getToken", + "inputs": [ + { + "name": "i", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "contract IERC20" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "initialMint", + "inputs": [ + { + "name": "receiver", + "type": "address", + "internalType": "address" + }, + { + "name": "lpTokens", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "lpMinted", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "kappa", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "int128", + "internalType": "int128" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "kill", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "killed", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "mint", + "inputs": [ + { + "name": "payer", + "type": "address", + "internalType": "address" + }, + { + "name": "receiver", + "type": "address", + "internalType": "address" + }, + { + "name": "lpTokenAmount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "deadline", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "lpMinted", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "mintImpl", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "contract PartyPoolMintImpl" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "name", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "string", + "internalType": "string" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "numTokens", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "owner", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "protocolFeeAddress", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "protocolFeePpm", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "renounceOwnership", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "swap", + "inputs": [ + { + "name": "payer", + "type": "address", + "internalType": "address" + }, + { + "name": "receiver", + "type": "address", + "internalType": "address" + }, + { + "name": "inputTokenIndex", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "outputTokenIndex", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "maxAmountIn", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "limitPrice", + "type": "int128", + "internalType": "int128" + }, + { + "name": "deadline", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "unwrap", + "type": "bool", + "internalType": "bool" + } + ], + "outputs": [ + { + "name": "amountIn", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "amountOut", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "fee", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "swapAmounts", + "inputs": [ + { + "name": "inputTokenIndex", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "outputTokenIndex", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "maxAmountIn", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "limitPrice", + "type": "int128", + "internalType": "int128" + } + ], + "outputs": [ + { + "name": "amountIn", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "amountOut", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "fee", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "swapFeePpm", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "swapMint", + "inputs": [ + { + "name": "payer", + "type": "address", + "internalType": "address" + }, + { + "name": "receiver", + "type": "address", + "internalType": "address" + }, + { + "name": "inputTokenIndex", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "maxAmountIn", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "deadline", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "lpMinted", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "swapMintImpl", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "contract PartyPoolSwapImpl" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "swapToLimit", + "inputs": [ + { + "name": "payer", + "type": "address", + "internalType": "address" + }, + { + "name": "receiver", + "type": "address", + "internalType": "address" + }, + { + "name": "inputTokenIndex", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "outputTokenIndex", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "limitPrice", + "type": "int128", + "internalType": "int128" + }, + { + "name": "deadline", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "unwrap", + "type": "bool", + "internalType": "bool" + } + ], + "outputs": [ + { + "name": "amountInUsed", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "amountOut", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "fee", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "symbol", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "string", + "internalType": "string" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "totalSupply", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "transfer", + "inputs": [ + { + "name": "to", + "type": "address", + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "transferFrom", + "inputs": [ + { + "name": "from", + "type": "address", + "internalType": "address" + }, + { + "name": "to", + "type": "address", + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "transferOwnership", + "inputs": [ + { + "name": "newOwner", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "wrapperToken", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "contract NativeWrapper" + } + ], + "stateMutability": "view" + }, + { + "type": "event", + "name": "Approval", + "inputs": [ + { + "name": "owner", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "spender", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Burn", + "inputs": [ + { + "name": "payer", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "receiver", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "amounts", + "type": "uint256[]", + "indexed": false, + "internalType": "uint256[]" + }, + { + "name": "lpBurned", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "BurnSwap", + "inputs": [ + { + "name": "payer", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "receiver", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "tokenOut", + "type": "address", + "indexed": true, + "internalType": "contract IERC20" + }, + { + "name": "amountIn", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "amountOut", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "lpFee", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "protocolFee", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Flash", + "inputs": [ + { + "name": "initiator", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "receiver", + "type": "address", + "indexed": true, + "internalType": "contract IERC3156FlashBorrower" + }, + { + "name": "token", + "type": "address", + "indexed": true, + "internalType": "contract IERC20" + }, + { + "name": "amount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "lpFee", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "protocolFee", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Killed", + "inputs": [], + "anonymous": false + }, + { + "type": "event", + "name": "Mint", + "inputs": [ + { + "name": "payer", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "receiver", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "amounts", + "type": "uint256[]", + "indexed": false, + "internalType": "uint256[]" + }, + { + "name": "lpMinted", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "OwnershipTransferred", + "inputs": [ + { + "name": "previousOwner", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "newOwner", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "ProtocolFeesCollected", + "inputs": [], + "anonymous": false + }, + { + "type": "event", + "name": "Swap", + "inputs": [ + { + "name": "payer", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "receiver", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "tokenIn", + "type": "address", + "indexed": true, + "internalType": "contract IERC20" + }, + { + "name": "tokenOut", + "type": "address", + "indexed": true, + "internalType": "contract IERC20" + }, + { + "name": "amountIn", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "amountOut", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "lpFee", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "protocolFee", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "SwapMint", + "inputs": [ + { + "name": "payer", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "receiver", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "tokenIn", + "type": "address", + "indexed": true, + "internalType": "contract IERC20" + }, + { + "name": "amountIn", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "amountOut", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "lpFee", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "protocolFee", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Transfer", + "inputs": [ + { + "name": "from", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "to", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "error", + "name": "AddressEmptyCode", + "inputs": [ + { + "name": "target", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "ERC20InsufficientAllowance", + "inputs": [ + { + "name": "spender", + "type": "address", + "internalType": "address" + }, + { + "name": "allowance", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "needed", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "ERC20InsufficientBalance", + "inputs": [ + { + "name": "sender", + "type": "address", + "internalType": "address" + }, + { + "name": "balance", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "needed", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "ERC20InvalidApprover", + "inputs": [ + { + "name": "approver", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "ERC20InvalidReceiver", + "inputs": [ + { + "name": "receiver", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "ERC20InvalidSender", + "inputs": [ + { + "name": "sender", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "ERC20InvalidSpender", + "inputs": [ + { + "name": "spender", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "FailedCall", + "inputs": [] + }, + { + "type": "error", + "name": "OwnableInvalidOwner", + "inputs": [ + { + "name": "owner", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "OwnableUnauthorizedAccount", + "inputs": [ + { + "name": "account", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "ReentrancyGuardReentrantCall", + "inputs": [] + }, + { + "type": "error", + "name": "SafeERC20FailedOperation", + "inputs": [ + { + "name": "token", + "type": "address", + "internalType": "address" + } + ] + } +] diff --git a/substreams/ethereum-liquidityparty/abi/party_pool_viewer.abi.json b/substreams/ethereum-liquidityparty/abi/party_pool_viewer.abi.json new file mode 100644 index 0000000..1541cd4 --- /dev/null +++ b/substreams/ethereum-liquidityparty/abi/party_pool_viewer.abi.json @@ -0,0 +1,308 @@ +[ + { + "type": "constructor", + "inputs": [ + { + "name": "swapImpl_", + "type": "address", + "internalType": "contract PartyPoolSwapImpl" + }, + { + "name": "mintImpl", + "type": "address", + "internalType": "contract PartyPoolMintImpl" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "burnAmounts", + "inputs": [ + { + "name": "pool", + "type": "address", + "internalType": "contract IPartyPool" + }, + { + "name": "lpTokenAmount", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "withdrawAmounts", + "type": "uint256[]", + "internalType": "uint256[]" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "burnSwapAmounts", + "inputs": [ + { + "name": "pool", + "type": "address", + "internalType": "contract IPartyPool" + }, + { + "name": "lpAmount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "inputTokenIndex", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "amountOut", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "flashFee", + "inputs": [ + { + "name": "pool", + "type": "address", + "internalType": "contract IPartyPool" + }, + { + "name": "", + "type": "address", + "internalType": "address" + }, + { + "name": "amount", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "fee", + "type": "uint256", + "internalType": "uint256" + } + ], + "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", + "name": "maxFlashLoan", + "inputs": [ + { + "name": "pool", + "type": "address", + "internalType": "contract IPartyPool" + }, + { + "name": "token", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "mintAmounts", + "inputs": [ + { + "name": "pool", + "type": "address", + "internalType": "contract IPartyPool" + }, + { + "name": "lpTokenAmount", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "depositAmounts", + "type": "uint256[]", + "internalType": "uint256[]" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "poolPrice", + "inputs": [ + { + "name": "pool", + "type": "address", + "internalType": "contract IPartyPool" + }, + { + "name": "quoteTokenIndex", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "int128", + "internalType": "int128" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "price", + "inputs": [ + { + "name": "pool", + "type": "address", + "internalType": "contract IPartyPool" + }, + { + "name": "baseTokenIndex", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "quoteTokenIndex", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "int128", + "internalType": "int128" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "swapMintAmounts", + "inputs": [ + { + "name": "pool", + "type": "address", + "internalType": "contract IPartyPool" + }, + { + "name": "inputTokenIndex", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "maxAmountIn", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "amountInUsed", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "fee", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "lpMinted", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "swapToLimitAmounts", + "inputs": [ + { + "name": "pool", + "type": "address", + "internalType": "contract IPartyPool" + }, + { + "name": "inputTokenIndex", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "outputTokenIndex", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "limitPrice", + "type": "int128", + "internalType": "int128" + } + ], + "outputs": [ + { + "name": "amountIn", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "amountOut", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "fee", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + } +] diff --git a/substreams/ethereum-liquidityparty/buf.gen.yaml b/substreams/ethereum-liquidityparty/buf.gen.yaml new file mode 100644 index 0000000..d2e6544 --- /dev/null +++ b/substreams/ethereum-liquidityparty/buf.gen.yaml @@ -0,0 +1,12 @@ + +version: v1 +plugins: +- plugin: buf.build/community/neoeinstein-prost:v0.2.2 + out: src/pb + opt: + - file_descriptor_set=false + +- plugin: buf.build/community/neoeinstein-prost-crate:v0.3.1 + out: src/pb + opt: + - no_features diff --git a/substreams/ethereum-liquidityparty/build.rs b/substreams/ethereum-liquidityparty/build.rs new file mode 100644 index 0000000..c557d5b --- /dev/null +++ b/substreams/ethereum-liquidityparty/build.rs @@ -0,0 +1,49 @@ +use anyhow::Result; +use std::{fs, io::Write}; +use substreams_ethereum::Abigen; + +fn main() -> Result<()> { + let abi_folder = "abi"; + let output_folder = "src/abi"; + + let abis = fs::read_dir(abi_folder)?; + + let mut files = abis.collect::, _>>()?; + + // Sort the files by their name + files.sort_by_key(|a| a.file_name()); + + let mut mod_rs_content = String::new(); + mod_rs_content.push_str("#![allow(clippy::all)]\n"); + + for file in files { + let file_name = file.file_name(); + let file_name = file_name.to_string_lossy(); + + if !file_name.ends_with(".json") { + continue; + } + + let contract_name = file_name.split('.').next().unwrap(); + + let input_path = format!("{abi_folder}/{file_name}"); + let output_path = format!("{output_folder}/{contract_name}.rs"); + + mod_rs_content.push_str(&format!("pub mod {contract_name};\n")); + + if std::path::Path::new(&output_path).exists() { + continue; + } + + Abigen::new(contract_name, &input_path)? + .generate()? + .write_to_file(&output_path)?; + } + + let mod_rs_path = format!("{output_folder}/mod.rs"); + let mut mod_rs_file = fs::File::create(mod_rs_path)?; + + mod_rs_file.write_all(mod_rs_content.as_bytes())?; + + Ok(()) +} diff --git a/substreams/ethereum-liquidityparty/integration_test.tycho.yaml b/substreams/ethereum-liquidityparty/integration_test.tycho.yaml new file mode 100644 index 0000000..3a5c57a --- /dev/null +++ b/substreams/ethereum-liquidityparty/integration_test.tycho.yaml @@ -0,0 +1,69 @@ +# Name of the substreams config file in your substreams module. Usually "./substreams.yaml" +substreams_yaml_path: ./substreams.yaml +# Name of the adapter contract, usually: ProtocolSwapAdapter" +adapter_contract: "SwapAdapter" +# Constructor signature of the Adapter contract" +adapter_build_signature: "constructor(address)" +# A comma separated list of args to be passed to the contructor of the Adapter contract" +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: + - "type_name_1" + - "type_name_2" +# A list of tests. +# The name of the protocol system +protocol_system: "protocol_name" +tests: + # Name of the test + - name: test_pool_creation + # Indexed block range + start_block: 123 + stop_block: 456 + # 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: + - id: "0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7" + tokens: + - "0xdac17f958d2ee523a2206206994597c13d831ec7" + - "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" + - "0x6b175474e89094c44da98b954eedeac495271d0f" + static_attributes: { } + creation_tx: "0x20793bbf260912aae189d5d261ff003c9b9166da8191d8f9d63ff1c7722f3ac6" + # Whether the script should skip trying to simulate a swap on this component. + # If set to `true` please always add a reason why it's skipped. + 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 + - 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 diff --git a/substreams/ethereum-liquidityparty/rust-toolchain.toml b/substreams/ethereum-liquidityparty/rust-toolchain.toml new file mode 100644 index 0000000..15b4897 --- /dev/null +++ b/substreams/ethereum-liquidityparty/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "1.83.0" +components = [ "rustfmt" ] +targets = [ "wasm32-unknown-unknown" ] diff --git a/substreams/ethereum-liquidityparty/sepolia-liquidityparty.yaml b/substreams/ethereum-liquidityparty/sepolia-liquidityparty.yaml new file mode 100644 index 0000000..a45a5f7 --- /dev/null +++ b/substreams/ethereum-liquidityparty/sepolia-liquidityparty.yaml @@ -0,0 +1,73 @@ +specVersion: v0.1.0 +package: + name: "ethereum_liquidityparty" + version: v0.1.0 + +protobuf: + files: + - tycho/evm/v1/vm.proto + - tycho/evm/v1/common.proto + - tycho/evm/v1/utils.proto + importPaths: + - ../../proto + +binaries: + default: + type: wasm/rust-v1 + file: ../target/wasm32-unknown-unknown/release/ethereum_liquidityparty.wasm + +network: sepolia +networks: + sepolia: + initialBlock: + map_protocol_components: 9460804 + params: + map_protocol_components: planner=0x0ad06C08ab5049e6Fd4d7f5AF457115A1475326b&viewer=0x750d63a39a4ccfCfB69D2f5aFDa065909C717cAB&mint_impl=0x25bb10BA84944F8aAEf1fD247C3B7Fe7271C23F9&swap_impl=0x69b4F102e0747f61F8529b3bbFf2FC4b27438d0F&deployer=0x0939F93BAa3c96226853F9F39A95beF48eA8fF04&bp_deployer=0xfda454fF7876aad9408517Ed2F0d11AA229Ad0a4 + +modules: + - name: map_protocol_components + kind: map + initialBlock: 1 + inputs: + - params: string + - source: sf.ethereum.type.v2.Block + output: + type: proto:tycho.evm.v1.BlockTransactionProtocolComponents + + - name: store_protocol_components + kind: store + initialBlock: 1 + updatePolicy: set + valueType: string + inputs: + - map: map_protocol_components + + - name: map_relative_component_balance + kind: map + initialBlock: 1 + inputs: + - source: sf.ethereum.type.v2.Block + - store: store_protocol_components + output: + type: proto:tycho.evm.v1.BlockBalanceDeltas + + - name: store_balances + kind: store + initialBlock: 1 + updatePolicy: add + valueType: bigint + inputs: + - map: map_relative_component_balance + + - name: map_protocol_changes + kind: map + initialBlock: 1 + inputs: + - source: sf.ethereum.type.v2.Block + - map: map_protocol_components + - map: map_relative_component_balance + - store: store_protocol_components + - store: store_balances + mode: deltas + output: + type: proto:tycho.evm.v1.BlockChanges diff --git a/substreams/ethereum-liquidityparty/src/abi/.gitignore b/substreams/ethereum-liquidityparty/src/abi/.gitignore new file mode 100644 index 0000000..a7d41f4 --- /dev/null +++ b/substreams/ethereum-liquidityparty/src/abi/.gitignore @@ -0,0 +1,4 @@ +mod.rs +party_planner.rs +party_pool.rs +party_pool_viewer.rs \ No newline at end of file diff --git a/substreams/ethereum-liquidityparty/src/lib.rs b/substreams/ethereum-liquidityparty/src/lib.rs new file mode 100644 index 0000000..66a523f --- /dev/null +++ b/substreams/ethereum-liquidityparty/src/lib.rs @@ -0,0 +1,4 @@ +mod abi; +mod modules; +mod pool_factories; +mod params; diff --git a/substreams/ethereum-liquidityparty/src/modules.rs b/substreams/ethereum-liquidityparty/src/modules.rs new file mode 100644 index 0000000..a461e79 --- /dev/null +++ b/substreams/ethereum-liquidityparty/src/modules.rs @@ -0,0 +1,314 @@ +//! Template for Protocols with contract factories +//! +//! This template provides foundational maps and store substream modules for indexing a +//! protocol where each component (e.g., pool) is deployed to a separate contract. Each +//! contract is expected to escrow its ERC-20 token balances. +//! +//! If your protocol supports native ETH, you may need to adjust the balance tracking +//! logic in `map_relative_component_balance` to account for native token handling. +//! +//! ## Assumptions +//! - Assumes each pool has a single newly deployed contract linked to it +//! - Assumes pool identifier equals the deployed contract address +//! - Assumes any price or liquidity updated correlates with a pools contract storage update. +//! +//! ## Alternative Module +//! If your protocol uses a vault-like contract to manage balances, or if pools are +//! registered within a singleton contract, refer to the `ethereum-template-singleton` +//! substream for an appropriate alternative. +//! +//! ## Warning +//! This template provides a general framework for indexing a protocol. However, it is +//! likely that you will need to adapt the steps to suit your specific use case. Use the +//! provided code with care and ensure you fully understand each step before proceeding +//! with your implementation. +//! +//! ## Example Use Case +//! For an Uniswap-like protocol where each liquidity pool is deployed as a separate +//! contract, you can use this template to: +//! - Track relative component balances (e.g., ERC-20 token balances in each pool). +//! - Index individual pool contracts as they are created by the factory contract. +//! +//! Adjustments to the template may include: +//! - Handling native ETH balances alongside token balances. +//! - Customizing indexing logic for specific factory contract behavior. +use crate::params::Params; +use crate::{abi, pool_factories}; +use anyhow::Result; +use itertools::Itertools; +use std::collections::HashMap; +use substreams::{pb::substreams::StoreDeltas, prelude::*}; +use substreams_ethereum::{pb::eth, Event}; +use tycho_substreams::{ + balances::aggregate_balances_changes, contract::extract_contract_changes_builder, + prelude::*, +}; + +/// Find and create all relevant protocol components +/// +/// This method maps over blocks and instantiates ProtocolComponents with a unique ids +/// as well as all necessary metadata for routing and encoding. +#[substreams::handlers::map] +fn map_protocol_components( + param_string: String, + block: eth::v2::Block +) -> Result { + let params = Params::parse(¶m_string)?; + Ok(BlockTransactionProtocolComponents { + tx_components: block + .transactions() + .filter_map(|tx| { + let components = tx + .logs_with_calls() + .filter_map(|(log, call)| { + pool_factories::maybe_create_component(¶ms, call.call, log, tx) + }) + .collect::>(); + + if !components.is_empty() { + Some(TransactionProtocolComponents { tx: Some(tx.into()), components }) + } else { + None + } + }) + .collect::>(), + }) +} + +/// Stores all protocol components in a store. +/// +/// Stores information about components in a key value store. This is only necessary if +/// you need to access the whole set of components within your indexing logic. +/// +/// Popular use cases are: +/// - Checking if a contract belongs to a component. In this case suggest to use an address as the +/// store key so lookup operations are O(1). +/// - Tallying up relative balances changes to calcualte absolute erc20 token balances per +/// component. +/// +/// Usually you can skip this step if: +/// - You are interested in a static set of components only +/// - Your protocol emits balance change events with absolute values +#[substreams::handlers::store] +fn store_protocol_components( + map_protocol_components: BlockTransactionProtocolComponents, + store: StoreSetRaw, +) { + map_protocol_components + .tx_components + .into_iter() + .for_each(|tx_pc| { + tx_pc + .components + .into_iter() + .for_each(|pc| { + // Assumes that the component id is a hex encoded contract address + let key = pc.id.clone(); + // we store the components tokens + // TODO: proper error handling + let val = serde_sibor::to_bytes(&pc.tokens).unwrap(); + store.set(0, key, &val); + }) + }); +} + +/// Extracts balance changes per component +/// +/// This template function uses ERC20 transfer events to extract balance changes. It +/// assumes that each component is deployed at a dedicated contract address. If a +/// transfer to the component is detected, its balance is increased and if a transfer +/// from the component is detected its balance is decreased. +/// +/// ## Note: +/// Changes are necessary if your protocol uses native ETH, uses a vault contract or if +/// your component burn or mint tokens without emitting transfer events. +/// +/// You may want to ignore LP tokens if your protocol emits transfer events for these +/// here. +#[substreams::handlers::map] +fn map_relative_component_balance( + block: eth::v2::Block, + store: StoreGetRaw, +) -> Result { + let mut res = Vec::new(); + + for log in block.logs() { + let component_id = log.address().to_vec(); + if let Some(token_enc) = store.get_last(hex::encode(&component_id)) { + let tokens = serde_sibor::from_bytes::>>(&token_enc) + .map_err(|e| anyhow::anyhow!("Failed to deserialize tokens: {}", e))?; + + if let Some(event) = abi::party_pool::events::Mint::match_and_decode(log) { + for (i, amount) in event.amounts.iter().enumerate() { + if !amount.is_zero() { + res.push(BalanceDelta { + ord: log.ordinal(), + tx: Some(log.receipt.transaction.into()), + token: tokens[i].clone(), + delta: amount.to_signed_bytes_be(), + component_id: component_id.clone(), + }); + } + } + } else if let Some(event) = abi::party_pool::events::Burn::match_and_decode(log) { + for (i, amount) in event.amounts.iter().enumerate() { + if !amount.is_zero() { + res.push(BalanceDelta { + ord: log.ordinal(), + tx: Some(log.receipt.transaction.into()), + token: tokens[i].clone(), + delta: amount.neg().to_signed_bytes_be(), + component_id: component_id.clone(), + }); + } + } + } else if let Some(event) = abi::party_pool::events::Swap::match_and_decode(log) { + // increase by amount_in + res.push(BalanceDelta { + ord: log.ordinal(), + tx: Some(log.receipt.transaction.into()), + token: event.token_in.to_vec(), + delta: (event.amount_in - event.protocol_fee).to_signed_bytes_be(), + component_id: component_id.clone(), + }); + // decrease by amount_out plus protocol fees + res.push(BalanceDelta { + ord: log.ordinal(), + tx: Some(log.receipt.transaction.into()), + token: event.token_out.to_vec(), + delta: event.amount_out.neg().to_signed_bytes_be(), + component_id: component_id.clone(), + }); + } else if let Some(event) = abi::party_pool::events::SwapMint::match_and_decode(log) { + // increase by amount_in less the protocol fee + res.push(BalanceDelta { + ord: log.ordinal(), + tx: Some(log.receipt.transaction.into()), + token: event.token_in.to_vec(), + delta: (event.amount_in - event.protocol_fee).to_signed_bytes_be(), + component_id: component_id.clone(), + }); + } else if let Some(event) = abi::party_pool::events::BurnSwap::match_and_decode(log) { + // decrease by amount_out plus the protocol fee + res.push(BalanceDelta { + ord: log.ordinal(), + tx: Some(log.receipt.transaction.into()), + token: event.token_out.to_vec(), + delta: (event.amount_out + event.protocol_fee).neg().to_signed_bytes_be(), + component_id: component_id.clone(), + }); + } else if let Some(event) = abi::party_pool::events::Flash::match_and_decode(log) { + // increase by LP fees + res.push(BalanceDelta { + ord: log.ordinal(), + tx: Some(log.receipt.transaction.into()), + token: event.token.to_vec(), + delta: event.lp_fee.to_signed_bytes_be(), + component_id: component_id.clone(), + }); + } + } + } + Ok(BlockBalanceDeltas { balance_deltas: res }) +} + +/// Aggregates relative balances values into absolute values +/// +/// Aggregate the relative balances in an additive store since tycho-indexer expects +/// absolute balance inputs. +/// +/// ## Note: +/// This method should usually not require any changes. +#[substreams::handlers::store] +pub fn store_balances(deltas: BlockBalanceDeltas, store: StoreAddBigInt) { + tycho_substreams::balances::store_balance_changes(deltas, store); +} + +/// Aggregates protocol components and balance changes by transaction. +/// +/// This is the main method that will aggregate all changes as well as extract all +/// relevant contract storage deltas. +/// +/// ## Note: +/// You may have to change this method if your components have any default dynamic +/// attributes, or if you need any additional static contracts indexed. +#[substreams::handlers::map] +fn map_protocol_changes( + block: eth::v2::Block, + new_components: BlockTransactionProtocolComponents, + components_store: StoreGetRaw, + balance_store: StoreDeltas, + deltas: BlockBalanceDeltas, +) -> Result { + // We merge contract changes by transaction (identified by transaction index) + // making it easy to sort them at the very end. + let mut transaction_changes: HashMap<_, TransactionChangesBuilder> = HashMap::new(); + + // Aggregate newly created components per tx + new_components + .tx_components + .iter() + .for_each(|tx_component| { + // initialise builder if not yet present for this tx + let tx = tx_component.tx.as_ref().unwrap(); + let builder = transaction_changes + .entry(tx.index) + .or_insert_with(|| TransactionChangesBuilder::new(tx)); + + // iterate over individual components created within this tx + tx_component + .components + .iter() + .for_each(|component| { + builder.add_protocol_component(component); + // TODO: In case you require to add any dynamic attributes to the + // component you can do so here: + /* + builder.add_entity_change(&EntityChanges { + component_id: component.id.clone(), + attributes: default_attributes.clone(), + }); + */ + }); + }); + + // Aggregate absolute balances per transaction. + aggregate_balances_changes(balance_store, deltas) + .into_iter() + .for_each(|(_, (tx, balances))| { + let builder = transaction_changes + .entry(tx.index) + .or_insert_with(|| TransactionChangesBuilder::new(&tx)); + balances + .values() + .for_each(|token_bc_map| { + token_bc_map + .values() + .for_each(|bc| builder.add_balance_change(bc)) + }); + }); + + // Extract and insert any storage changes that happened for any of the components. + extract_contract_changes_builder( + &block, + |addr| { + // we assume that the store holds contract addresses as keys and if it + // contains a value, that contract is of relevance. + components_store + .get_last(hex::encode(addr)) + .is_some() + }, + &mut transaction_changes, + ); + + // Process all `transaction_changes` for final output in the `BlockChanges`, + // sorted by transaction index (the key). + Ok(BlockChanges { + block: Some((&block).into()), + changes: transaction_changes + .drain() + .sorted_unstable_by_key(|(index, _)| *index) + .filter_map(|(_, builder)| builder.build()) + .collect::>(), + }) +} diff --git a/substreams/ethereum-liquidityparty/src/params.rs b/substreams/ethereum-liquidityparty/src/params.rs new file mode 100644 index 0000000..a285223 --- /dev/null +++ b/substreams/ethereum-liquidityparty/src/params.rs @@ -0,0 +1,54 @@ +use anyhow::anyhow; +use serde::Deserialize; + +#[derive(Debug, Deserialize)] +struct StringParams { + planner: String, + viewer: String, + mint_impl: String, + swap_impl: String, + deployer: String, + bp_deployer: String, +} + +pub(crate) struct Params { + pub planner: Vec, + pub viewer: Vec, + pub mint_impl: Vec, + pub swap_impl: Vec, + pub deployer: Vec, + pub bp_deployer: Vec, +} + +impl StringParams { + pub fn parse(input: &str) -> anyhow::Result { + serde_qs::from_str(input).map_err(|e| anyhow!("Failed to parse query params: {}", e)) + } +} + +impl Params { + pub fn parse(input: &str) -> anyhow::Result { + let params = StringParams::parse(input)?; + + fn decode_addr(s: &str) -> anyhow::Result> { + let s = s.strip_prefix("0x").unwrap_or(s); + if s.len() != 40 { + return Err(anyhow!("address must be 20 bytes (40 hex chars), got len={}", s.len())); + } + let bytes = hex::decode(s)?; + if bytes.len() != 20 { + return Err(anyhow!("decoded address is not 20 bytes")); + } + Ok(bytes) + } + + Ok(Self { + planner: decode_addr(¶ms.planner)?, + viewer: decode_addr(¶ms.viewer)?, + mint_impl: decode_addr(¶ms.mint_impl)?, + swap_impl: decode_addr(¶ms.swap_impl)?, + deployer: decode_addr(¶ms.deployer)?, + bp_deployer: decode_addr(¶ms.bp_deployer)?, + }) + } +} diff --git a/substreams/ethereum-liquidityparty/src/pool_factories.rs b/substreams/ethereum-liquidityparty/src/pool_factories.rs new file mode 100644 index 0000000..99407db --- /dev/null +++ b/substreams/ethereum-liquidityparty/src/pool_factories.rs @@ -0,0 +1,40 @@ +use substreams_ethereum::pb::eth::v2::{Call, Log, TransactionTrace}; +use substreams_ethereum::Event; +use tycho_substreams::models::{ + ChangeType, FinancialType, ImplementationType, ProtocolComponent, ProtocolType, +}; +use crate::abi; +use crate::params::Params; + +/// Potentially constructs a new ProtocolComponent given a call +/// +/// This method is given each individual call within a transaction, the corresponding +/// logs emitted during that call as well as the full transaction trace. +/// +/// If this call creates a component in your protocol please construct and return it +/// here. Otherwise, simply return None. +pub fn maybe_create_component( + params: &Params, + call: &Call, + _log: &Log, + _tx: &TransactionTrace, +) -> Option { + if call.address.as_slice() == params.planner { + if let Some(event) = abi::party_planner::events::PartyStarted::match_and_decode(_log) { + return Some(ProtocolComponent { + id: hex::encode(&event.pool), + tokens: event.tokens, + contracts: vec![event.pool.clone(), params.mint_impl.clone(), params.swap_impl.clone()], + 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 +}