diff --git a/.gitignore b/.gitignore index 43d247a..6e0396a 100644 --- a/.gitignore +++ b/.gitignore @@ -14,7 +14,7 @@ target/ __pycache__ -substreams/ethereum-template/Cargo.lock +substreams/ethereum-template-factory/Cargo.lock .DS_Store diff --git a/substreams/Cargo.lock b/substreams/Cargo.lock index 0171b13..5ff1bbe 100644 --- a/substreams/Cargo.lock +++ b/substreams/Cargo.lock @@ -301,7 +301,7 @@ dependencies = [ ] [[package]] -name = "ethereum-template" +name = "ethereum-template-factory" version = "0.1.0" dependencies = [ "anyhow", @@ -314,7 +314,25 @@ dependencies = [ "serde-sibor", "substreams", "substreams-ethereum", - "tycho-substreams 0.2.0 (git+https://github.com/propeller-heads/tycho-protocol-sdk.git?rev=b8aeaa3)", + "tycho-substreams 0.2.0 (git+https://github.com/propeller-heads/tycho-protocol-sdk.git?rev=3c08359)", +] + +[[package]] +name = "ethereum-template-singleton" +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.0 (git+https://github.com/propeller-heads/tycho-protocol-sdk.git?rev=3c08359)", ] [[package]] @@ -461,7 +479,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -534,6 +552,9 @@ name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] [[package]] name = "hex-literal" @@ -600,7 +621,7 @@ checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -1098,7 +1119,7 @@ dependencies = [ "regex", "relative-path", "rustc_version", - "syn 2.0.90", + "syn 2.0.96", "unicode-ident", ] @@ -1158,7 +1179,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "859877cf5123a0b2c03e92b01c0b7adefb5ea0b6e6a390f2f7107b85783eee14" dependencies = [ "serde", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -1489,7 +1510,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", - "quote", "syn 2.0.96", + "quote", + "syn 2.0.96", ] [[package]] @@ -1500,7 +1522,7 @@ checksum = "e1d8749b4531af2117677a5fcd12b1348a3fe2b81e36e61ffeac5c4aa3273e36" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] [[package]] @@ -1544,6 +1566,22 @@ dependencies = [ "substreams-ethereum", ] +[[package]] +name = "tycho-substreams" +version = "0.2.0" +source = "git+https://github.com/propeller-heads/tycho-protocol-sdk.git?rev=3c08359#3c08359cf112e15c137dd5256b8dc8e9cd6c1626" +dependencies = [ + "ethabi 18.0.0", + "hex", + "itertools 0.12.1", + "num-bigint", + "prost 0.11.9", + "serde", + "serde_json", + "substreams", + "substreams-ethereum", +] + [[package]] name = "tycho-substreams" version = "0.2.0" @@ -1745,5 +1783,5 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.96", ] diff --git a/substreams/Cargo.toml b/substreams/Cargo.toml index a0d0565..c0e3e97 100644 --- a/substreams/Cargo.toml +++ b/substreams/Cargo.toml @@ -10,8 +10,9 @@ members = [ "ethereum-sfrax", "ethereum-sfraxeth", "ethereum-uniswap-v3-logs-only", - "ethereum-template", - "ethereum-uniswap-v4" + "ethereum-template-factory", + "ethereum-template-singleton", + "ethereum-uniswap-v4", ] resolver = "2" diff --git a/substreams/ethereum-template/Cargo.toml b/substreams/ethereum-template-factory/Cargo.toml similarity index 78% rename from substreams/ethereum-template/Cargo.toml rename to substreams/ethereum-template-factory/Cargo.toml index acbb0e8..9d4f94a 100644 --- a/substreams/ethereum-template/Cargo.toml +++ b/substreams/ethereum-template-factory/Cargo.toml @@ -1,17 +1,17 @@ [package] -name = "ethereum-template" +name = "ethereum-template-factory" version = "0.1.0" edition = "2021" [lib] -name = "ethereum_template" +name = "ethereum_template_factory" 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 = "b8aeaa3" } +tycho-substreams = { git = "https://github.com/propeller-heads/tycho-protocol-sdk.git", rev = "3c08359" } anyhow = "1.0.95" ethabi = "18.0.0" num-bigint = "0.4.6" diff --git a/substreams/ethereum-template/abi/erc20.json b/substreams/ethereum-template-factory/abi/erc20.json similarity index 100% rename from substreams/ethereum-template/abi/erc20.json rename to substreams/ethereum-template-factory/abi/erc20.json diff --git a/substreams/ethereum-template/buf.gen.yaml b/substreams/ethereum-template-factory/buf.gen.yaml similarity index 100% rename from substreams/ethereum-template/buf.gen.yaml rename to substreams/ethereum-template-factory/buf.gen.yaml diff --git a/substreams/ethereum-template/build.rs b/substreams/ethereum-template-factory/build.rs similarity index 100% rename from substreams/ethereum-template/build.rs rename to substreams/ethereum-template-factory/build.rs diff --git a/substreams/ethereum-template/integration_test.tycho.yaml b/substreams/ethereum-template-factory/integration_test.tycho.yaml similarity index 100% rename from substreams/ethereum-template/integration_test.tycho.yaml rename to substreams/ethereum-template-factory/integration_test.tycho.yaml diff --git a/substreams/ethereum-template/src/abi/erc20.rs b/substreams/ethereum-template-factory/src/abi/erc20.rs similarity index 100% rename from substreams/ethereum-template/src/abi/erc20.rs rename to substreams/ethereum-template-factory/src/abi/erc20.rs diff --git a/substreams/ethereum-template/src/abi/mod.rs b/substreams/ethereum-template-factory/src/abi/mod.rs similarity index 100% rename from substreams/ethereum-template/src/abi/mod.rs rename to substreams/ethereum-template-factory/src/abi/mod.rs diff --git a/substreams/ethereum-template/src/lib.rs b/substreams/ethereum-template-factory/src/lib.rs similarity index 58% rename from substreams/ethereum-template/src/lib.rs rename to substreams/ethereum-template-factory/src/lib.rs index 4e5889a..b5b1fb6 100644 --- a/substreams/ethereum-template/src/lib.rs +++ b/substreams/ethereum-template-factory/src/lib.rs @@ -1,3 +1,4 @@ -mod modules_factory; mod abi; +mod modules; mod pool_factories; + diff --git a/substreams/ethereum-template/src/modules_factory.rs b/substreams/ethereum-template-factory/src/modules.rs similarity index 100% rename from substreams/ethereum-template/src/modules_factory.rs rename to substreams/ethereum-template-factory/src/modules.rs diff --git a/substreams/ethereum-template/src/pool_factories.rs b/substreams/ethereum-template-factory/src/pool_factories.rs similarity index 96% rename from substreams/ethereum-template/src/pool_factories.rs rename to substreams/ethereum-template-factory/src/pool_factories.rs index 3078f9c..647872d 100644 --- a/substreams/ethereum-template/src/pool_factories.rs +++ b/substreams/ethereum-template-factory/src/pool_factories.rs @@ -13,7 +13,7 @@ use tycho_substreams::models::{ChangeType, FinancialType, ImplementationType, Pr pub fn maybe_create_component( call: &Call, _log: &Log, - tx: &TransactionTrace, + _tx: &TransactionTrace, ) -> Option { match *call.address { // TODO: replace with your logic @@ -36,7 +36,6 @@ pub fn maybe_create_component( attribute_schema: vec![], implementation_type: ImplementationType::Vm.into(), }), - tx: Some(tx.into()), }) } _ => None, diff --git a/substreams/ethereum-template-factory/substreams.yaml b/substreams/ethereum-template-factory/substreams.yaml new file mode 100644 index 0000000..864e9bc --- /dev/null +++ b/substreams/ethereum-template-factory/substreams.yaml @@ -0,0 +1,67 @@ +specVersion: v0.1.0 +package: + name: "ethereum_template" + 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_template_factory.wasm + + +network: mainnet + +modules: + - name: map_protocol_components + kind: map + initialBlock: 1 + inputs: + - 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-template-singleton/Cargo.toml b/substreams/ethereum-template-singleton/Cargo.toml new file mode 100644 index 0000000..93537f1 --- /dev/null +++ b/substreams/ethereum-template-singleton/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "ethereum-template-singleton" +version = "0.1.0" +edition = "2021" + +[lib] +name = "ethereum_template_singleton" +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 = "3c08359" } +anyhow = "1.0.95" +ethabi = "18.0.0" +num-bigint = "0.4.6" +hex = { version = "0.4", features = ["serde"] } +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-template-singleton/abi/erc20.json b/substreams/ethereum-template-singleton/abi/erc20.json new file mode 100644 index 0000000..3b0ab2f --- /dev/null +++ b/substreams/ethereum-template-singleton/abi/erc20.json @@ -0,0 +1,222 @@ +[ + { + "constant": true, + "inputs": [], + "name": "name", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_spender", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_from", + "type": "address" + }, + { + "name": "_to", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "decimals", + "outputs": [ + { + "name": "", + "type": "uint8" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_owner", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "name": "balance", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "symbol", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_to", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_owner", + "type": "address" + }, + { + "name": "_spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "payable": true, + "stateMutability": "payable", + "type": "fallback" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "from", + "type": "address" + }, + { + "indexed": true, + "name": "to", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + } +] \ No newline at end of file diff --git a/substreams/ethereum-template-singleton/buf.gen.yaml b/substreams/ethereum-template-singleton/buf.gen.yaml new file mode 100644 index 0000000..d2e6544 --- /dev/null +++ b/substreams/ethereum-template-singleton/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-template-singleton/build.rs b/substreams/ethereum-template-singleton/build.rs new file mode 100644 index 0000000..5e7bd34 --- /dev/null +++ b/substreams/ethereum-template-singleton/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!("{}/{}.rs", output_folder, contract_name); + + mod_rs_content.push_str(&format!("pub mod {};\n", contract_name)); + + 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!("{}/mod.rs", output_folder); + 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-template-singleton/integration_test.tycho.yaml b/substreams/ethereum-template-singleton/integration_test.tycho.yaml new file mode 100644 index 0000000..bea6aa3 --- /dev/null +++ b/substreams/ethereum-template-singleton/integration_test.tycho.yaml @@ -0,0 +1,57 @@ +# 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 or not 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 +# A list of accounts that need to be indexed to run the tests properly. +# Usually used when there is a global component required by all pools and created before the tested range of blocks. For example a factory or a vault. +# Please note that this component needs to be indexed by your substreams module, this feature is only for testing purpose. +# Also please always add a reason why this account is needed for your tests. +# This will be applied to each test. +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. +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: + attr_1: "value" + attr_2: "value" + creation_tx: "0x20793bbf260912aae189d5d261ff003c9b9166da8191d8f9d63ff1c7722f3ac6" + # Whether or not 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 + - name: test_something_else + start_block: 123 + stop_block: 456 + expected_components: + - id: "0xdc24316b9ae028f1497c275eb9192a3ea0f67022" + tokens: + - "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" + - "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84" + static_attributes: null + creation_tx: "0xfac67ecbd423a5b915deff06045ec9343568edaec34ae95c43d35f2c018afdaa" + skip_simulation: true # If true, always add a reason diff --git a/substreams/ethereum-template-singleton/src/abi/erc20.rs b/substreams/ethereum-template-singleton/src/abi/erc20.rs new file mode 100644 index 0000000..b7b3a39 --- /dev/null +++ b/substreams/ethereum-template-singleton/src/abi/erc20.rs @@ -0,0 +1,1257 @@ + const INTERNAL_ERR: &'static str = "`ethabi_derive` internal error"; + /// Contract's functions. + #[allow(dead_code, unused_imports, unused_variables)] + pub mod functions { + use super::INTERNAL_ERR; + #[derive(Debug, Clone, PartialEq)] + pub struct Allowance { + pub owner: Vec, + pub spender: Vec, + } + impl Allowance { + const METHOD_ID: [u8; 4] = [221u8, 98u8, 237u8, 62u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Address, ethabi::ParamType::Address], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + owner: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + spender: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Address(ethabi::Address::from_slice(&self.owner)), + ethabi::Token::Address( + ethabi::Address::from_slice(&self.spender), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Allowance { + const NAME: &'static str = "allowance"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable + for Allowance { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Approve { + pub spender: Vec, + pub value: substreams::scalar::BigInt, + } + impl Approve { + const METHOD_ID: [u8; 4] = [9u8, 94u8, 167u8, 179u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Address, ethabi::ParamType::Uint(256usize)], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + spender: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + value: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Address( + ethabi::Address::from_slice(&self.spender), + ), + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.value.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Bool], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_bool() + .expect(INTERNAL_ERR), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Approve { + const NAME: &'static str = "approve"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for Approve { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct BalanceOf { + pub owner: Vec, + } + impl BalanceOf { + const METHOD_ID: [u8; 4] = [112u8, 160u8, 130u8, 49u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Address], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + owner: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ethabi::Token::Address(ethabi::Address::from_slice(&self.owner))], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for BalanceOf { + const NAME: &'static str = "balanceOf"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable + for BalanceOf { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Decimals {} + impl Decimals { + const METHOD_ID: [u8; 4] = [49u8, 60u8, 229u8, 103u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(8usize)], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Decimals { + const NAME: &'static str = "decimals"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable + for Decimals { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Name {} + impl Name { + const METHOD_ID: [u8; 4] = [6u8, 253u8, 222u8, 3u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::String], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_string() + .expect(INTERNAL_ERR), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Name { + const NAME: &'static str = "name"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for Name { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Symbol {} + impl Symbol { + const METHOD_ID: [u8; 4] = [149u8, 216u8, 155u8, 65u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::String], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_string() + .expect(INTERNAL_ERR), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Symbol { + const NAME: &'static str = "symbol"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for Symbol { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct TotalSupply {} + impl TotalSupply { + const METHOD_ID: [u8; 4] = [24u8, 22u8, 13u8, 221u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for TotalSupply { + const NAME: &'static str = "totalSupply"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable + for TotalSupply { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Transfer { + pub to: Vec, + pub value: substreams::scalar::BigInt, + } + impl Transfer { + const METHOD_ID: [u8; 4] = [169u8, 5u8, 156u8, 187u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Address, ethabi::ParamType::Uint(256usize)], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + to: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + value: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Address(ethabi::Address::from_slice(&self.to)), + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.value.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Bool], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_bool() + .expect(INTERNAL_ERR), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Transfer { + const NAME: &'static str = "transfer"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for Transfer { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct TransferFrom { + pub from: Vec, + pub to: Vec, + pub value: substreams::scalar::BigInt, + } + impl TransferFrom { + const METHOD_ID: [u8; 4] = [35u8, 184u8, 114u8, 221u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Address, + ethabi::ParamType::Address, + ethabi::ParamType::Uint(256usize), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + from: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + to: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + value: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Address(ethabi::Address::from_slice(&self.from)), + ethabi::Token::Address(ethabi::Address::from_slice(&self.to)), + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.value.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Bool], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_bool() + .expect(INTERNAL_ERR), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for TransferFrom { + const NAME: &'static str = "transferFrom"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for TransferFrom { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + } + /// Contract's events. + #[allow(dead_code, unused_imports, unused_variables)] + pub mod events { + use super::INTERNAL_ERR; + #[derive(Debug, Clone, PartialEq)] + pub struct Approval { + pub owner: Vec, + pub spender: Vec, + pub value: substreams::scalar::BigInt, + } + impl Approval { + const TOPIC_ID: [u8; 32] = [ + 140u8, + 91u8, + 225u8, + 229u8, + 235u8, + 236u8, + 125u8, + 91u8, + 209u8, + 79u8, + 113u8, + 66u8, + 125u8, + 30u8, + 132u8, + 243u8, + 221u8, + 3u8, + 20u8, + 192u8, + 247u8, + 178u8, + 41u8, + 30u8, + 91u8, + 32u8, + 10u8, + 200u8, + 199u8, + 195u8, + 185u8, + 37u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 3usize { + return false; + } + if log.data.len() != 32usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() + == Self::TOPIC_ID; + } + pub fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + log.data.as_ref(), + ) + .map_err(|e| format!("unable to decode log.data: {:?}", e))?; + values.reverse(); + Ok(Self { + owner: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[1usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'owner' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + spender: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[2usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'spender' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + value: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + } + impl substreams_ethereum::Event for Approval { + const NAME: &'static str = "Approval"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + Self::decode(log) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Transfer { + pub from: Vec, + pub to: Vec, + pub value: substreams::scalar::BigInt, + } + impl Transfer { + const TOPIC_ID: [u8; 32] = [ + 221u8, + 242u8, + 82u8, + 173u8, + 27u8, + 226u8, + 200u8, + 155u8, + 105u8, + 194u8, + 176u8, + 104u8, + 252u8, + 55u8, + 141u8, + 170u8, + 149u8, + 43u8, + 167u8, + 241u8, + 99u8, + 196u8, + 161u8, + 22u8, + 40u8, + 245u8, + 90u8, + 77u8, + 245u8, + 35u8, + 179u8, + 239u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 3usize { + return false; + } + if log.data.len() != 32usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() + == Self::TOPIC_ID; + } + pub fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + log.data.as_ref(), + ) + .map_err(|e| format!("unable to decode log.data: {:?}", e))?; + values.reverse(); + Ok(Self { + from: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[1usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'from' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + to: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[2usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'to' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + value: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + } + impl substreams_ethereum::Event for Transfer { + const NAME: &'static str = "Transfer"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + Self::decode(log) + } + } + } \ No newline at end of file diff --git a/substreams/ethereum-template-singleton/src/abi/mod.rs b/substreams/ethereum-template-singleton/src/abi/mod.rs new file mode 100644 index 0000000..4542b55 --- /dev/null +++ b/substreams/ethereum-template-singleton/src/abi/mod.rs @@ -0,0 +1,2 @@ +#![allow(clippy::all)] +pub mod erc20; diff --git a/substreams/ethereum-template-singleton/src/lib.rs b/substreams/ethereum-template-singleton/src/lib.rs new file mode 100644 index 0000000..83bc586 --- /dev/null +++ b/substreams/ethereum-template-singleton/src/lib.rs @@ -0,0 +1,4 @@ +mod abi; +mod pool_factories; +mod modules; + diff --git a/substreams/ethereum-template-singleton/src/modules.rs b/substreams/ethereum-template-singleton/src/modules.rs new file mode 100644 index 0000000..9bdfd1b --- /dev/null +++ b/substreams/ethereum-template-singleton/src/modules.rs @@ -0,0 +1,246 @@ +//! Template for Protocols with singleton contract +//! +//! +use std::collections::HashMap; +use anyhow::Result; +use substreams::pb::substreams::StoreDeltas; +use substreams::prelude::*; +use substreams_ethereum::Event; +use substreams_ethereum::pb::eth; +use tycho_substreams::balances::aggregate_balances_changes; +use tycho_substreams::contract::extract_contract_changes_builder; +use tycho_substreams::prelude::*; +use itertools::Itertools; +use prost::Message; +use substreams_ethereum::block_view::CallView; +use crate::pool_factories; +use crate::pool_factories::DeploymentConfig; + +/// 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( + params: String, + block: eth::v2::Block, +) -> Result { + let config = serde_qs::from_str(params.as_str())?; + Ok(BlockTransactionProtocolComponents { + tx_components: block + .transactions() + .filter_map(|tx| { + let components = tx + .logs_with_calls() + .filter_map(|(log, call)| { + // TODO: ensure this method is implemented correctly + pool_factories::maybe_create_component( + call.call, + log, + tx, + &config, + ) + }) + .collect::>(); + + if !components.is_empty() { + Some(TransactionProtocolComponents { tx: Some(tx.into()), components }) + } else { + None + } + }) + .collect::>(), + }) +} + +#[substreams::handlers::store] +fn store_protocol_tokens(map_protocol_components: BlockTransactionProtocolComponents, store: StoreSetInt64) { + map_protocol_components.tx_components + .into_iter() + .for_each(|tx_pc| { + tx_pc + .components + .into_iter() + .for_each(|pc| { + pc.tokens.iter().for_each(|token| { + let token_addr_hex = hex::encode(token); + store.set(0, &token_addr_hex, &1); + }) + }) + }); +} + +/// Extracts balance changes per component +/// +/// This template function inspects ERC20 transfer events to/from the singleton contract +/// to extract balance changes. If a transfer to the component is detected, it's +/// balanced is increased and if a balance from the component is detected its balance +/// is decreased. +/// +/// ## Note: +/// Changes are necessary if your protocol uses native ETH or your component burns or +/// mints 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(params: String, block: eth::v2::Block, store: StoreGetInt64) -> Result { + let config: DeploymentConfig = serde_qs::from_str(params.as_str())?; + let res = block + .transactions() + .flat_map(|tx| { + tx + .logs_with_calls() + .filter_map(|(log, call)| { + let token_addr_hex = hex::encode(&log.address); + if !store.has_last(&token_addr_hex) { + return None; + } + + crate::abi::erc20::events::Transfer::match_and_decode(log).map(|transfer| { + let to_addr = transfer.to.as_slice(); + let from_addr = transfer.from.as_slice(); + if let Some(component_id) = extract_component_id_from_call(call) { + if to_addr == config.vault_address { + return Some(BalanceDelta { + ord: log.ordinal, + tx: Some(tx.into()), + token: log.address.to_vec(), + delta: transfer.value.to_signed_bytes_be(), + component_id: component_id.encode_to_vec(), + }); + } else if from_addr == config.vault_address { + return Some(BalanceDelta { + ord: log.ordinal, + tx: Some(tx.into()), + token: log.address.to_vec(), + delta: (transfer.value.neg()).to_signed_bytes_be(), + component_id: component_id.encode_to_vec(), + }); + } + } + None + }) + }) + .flatten() + }) + .collect::>(); + + Ok(BlockBalanceDeltas { balance_deltas: res }) +} + +// TODO: given a relevant balance changing call associate it with the respective +// component +fn extract_component_id_from_call(_call: CallView) -> Option { + todo!() +} + +/// 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_component_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( + params: String, + block: eth::v2::Block, + new_components: BlockTransactionProtocolComponents, + balance_store: StoreDeltas, + deltas: BlockBalanceDeltas, +) -> Result { + let config: DeploymentConfig = serde_qs::from_str(params.as_str())?; + // 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)); + let mut contract_changes = InterimContractChange::new(&config.vault_address, false); + balances + .values() + .for_each(|token_bc_map| { + token_bc_map + .values() + .for_each(|bc| { + // track component balance + builder.add_balance_change(bc); + // track vault contract balance + contract_changes.upsert_token_balance(bc.token.as_slice(), bc.balance.as_slice()) + }) + }); + builder.add_contract_changes(&contract_changes); + }); + + + // 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. + // TODO: if you have any additional static contracts that need to be indexed, + // please add them here. + addr == config.vault_address + }, + &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::>(), + }) +} \ No newline at end of file diff --git a/substreams/ethereum-template-singleton/src/pool_factories.rs b/substreams/ethereum-template-singleton/src/pool_factories.rs new file mode 100644 index 0000000..6b07ef6 --- /dev/null +++ b/substreams/ethereum-template-singleton/src/pool_factories.rs @@ -0,0 +1,61 @@ +use serde::Deserialize; +use substreams_ethereum::pb::eth::v2::{Call, Log, TransactionTrace}; +use tycho_substreams::models::{ChangeType, FinancialType, ImplementationType, ProtocolComponent, ProtocolType}; + + +#[derive(Deserialize)] +pub struct DeploymentConfig { + #[serde(with = "hex::serde")] + pub vault_address: Vec, +} + +/// 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 contstruct and return it +/// here. Otherwise, simply return None. +pub fn maybe_create_component( + call: &Call, + _log: &Log, + _tx: &TransactionTrace, + config: &DeploymentConfig, +) -> Option { + if call.address == config.vault_address { + // TODO: replace with your logic + Some(ProtocolComponent { + id: "".to_string(), + tokens: vec![ + // TODO: add the components tokens + ], + contracts: vec![ + // TODO: any contracts required during swapping + ], + static_att: vec![ + // TODO: any additional metadata required, e.g. for swap encoding + ], + change: ChangeType::Creation.into(), + protocol_type: Some(ProtocolType { + name: "template".to_string(), + financial_type: FinancialType::Swap.into(), + attribute_schema: vec![], + implementation_type: ImplementationType::Vm.into(), + }), + }) + } else { + None + } +} + + +#[cfg(test)] +mod test { + use super::*; + #[test] + fn test_decode_config() { + let config: DeploymentConfig = serde_qs::from_str("vault_address=0001").unwrap(); + + assert_eq!(config.vault_address, [0u8, 1u8]); + } +} \ No newline at end of file diff --git a/substreams/ethereum-template-singleton/substreams.yaml b/substreams/ethereum-template-singleton/substreams.yaml new file mode 100644 index 0000000..a3eb035 --- /dev/null +++ b/substreams/ethereum-template-singleton/substreams.yaml @@ -0,0 +1,80 @@ +specVersion: v0.1.0 +package: + name: "ethereum_template" + 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_template_singleton.wasm + +network: mainnet +networks: + mainnet: + initialBlock: + map_protocol_components: 1 + store_protocol_components: 1 + map_relative_component_balance: 1 + store_balances: 1 + map_protocol_changes: 1 + params: + map_protocol_components: "vault_address=0000" + map_relative_component_balance: "vault_address=0000" + map_protocol_changes: "vault_address=0000" + +params: + map_protocol_components: "vault_address=0000" + map_relative_component_balance: "vault_address=0000" + map_protocol_changes: "vault_address=0000" + +modules: + - name: map_protocol_components + kind: map + inputs: + - params: string + - source: sf.ethereum.type.v2.Block + output: + type: proto:tycho.evm.v1.BlockTransactionProtocolComponents + + - name: store_protocol_tokens + kind: store + updatePolicy: set + valueType: string + inputs: + - map: map_protocol_components + + - name: map_relative_component_balance + kind: map + inputs: + - params: string + - source: sf.ethereum.type.v2.Block + - store: store_protocol_tokens + output: + type: proto:tycho.evm.v1.BlockBalanceDeltas + + - name: store_component_balances + kind: store + updatePolicy: add + valueType: bigint + inputs: + - map: map_relative_component_balance + + - name: map_protocol_changes + kind: map + inputs: + - params: string + - source: sf.ethereum.type.v2.Block + - map: map_protocol_components + - map: map_relative_component_balance + - store: store_component_balances + mode: deltas + output: + type: proto:tycho.evm.v1.BlockChanges diff --git a/substreams/ethereum-template/substreams.yaml b/substreams/ethereum-template/substreams.yaml deleted file mode 100644 index 903f44a..0000000 --- a/substreams/ethereum-template/substreams.yaml +++ /dev/null @@ -1,25 +0,0 @@ -specVersion: v0.1.0 -package: - name: "ethereum_template" - 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_template.wasm - -modules: - - name: map_protocol_changes - kind: map - inputs: - - source: sf.ethereum.type.v2.Block - output: - type: proto:tycho.evm.state.v1.BlockContractChanges