diff --git a/substreams/crates/tycho-substreams/src/models.rs b/substreams/crates/tycho-substreams/src/models.rs index 445baf5..333f172 100644 --- a/substreams/crates/tycho-substreams/src/models.rs +++ b/substreams/crates/tycho-substreams/src/models.rs @@ -300,6 +300,88 @@ impl ProtocolComponent { }); self } + + /// Checks if the instance contains all specified attributes. + /// + /// This function verifies whether the `ProtocolComponent` has all the given static attributes. + /// Each attribute is represented by a tuple containing a name and a value. The function + /// iterates over the provided attributes and checks if they exist in the instance's + /// `static_att`. + /// + /// # Arguments + /// + /// * `attributes` - A slice of tuples where each tuple consists of a `String` representing the + /// attribute name and a `Vec` representing the attribute value. + /// + /// # Returns + /// + /// A boolean indicating whether all specified attributes are present in the instance. + /// + /// # Example + /// + /// ``` + /// let attributes_to_check = vec![ + /// ("attribute1".to_string(), vec![1, 2, 3]), + /// ("attribute2".to_string(), vec![4, 5, 6]), + /// ]; + /// + /// let has_all_attributes = instance.has_attributes(&attributes_to_check); + /// assert!(has_all_attributes); + /// ``` + /// + /// # Notes + /// + /// - The function assumes that the `static_att` collection contains attributes with a + /// `ChangeType` of `Creation` when they are initially added. This is fine because + /// `static_att` can't be updated + pub fn has_attributes(&self, attributes: &[(&str, Vec)]) -> bool { + attributes.iter().all(|(name, value)| { + self.static_att.contains(&Attribute { + name: name.to_string(), + value: value.clone(), + change: ChangeType::Creation.into(), + }) + }) + } + + /// Retrieves the value of a specified attribute by name. + /// + /// This function searches the instance's `static_att` collection for an attribute with the + /// given name. If found, it returns a copy of the attribute's value. If the attribute is + /// not found, it returns `None`. + /// + /// # Arguments + /// + /// * `name` - A string slice that holds the name of the attribute to be searched. + /// + /// # Returns + /// + /// An `Option>` containing the attribute value if found, or `None` if the attribute + /// does not exist. + /// + /// # Example + /// + /// ``` + /// let attribute_name = "attribute1"; + /// if let Some(value) = instance.get_attribute_value(attribute_name) { + /// // Use the attribute value + /// println!("Attribute value: {:?}", value); + /// } else { + /// println!("Attribute not found"); + /// } + /// ``` + /// + /// # Notes + /// + /// - The function performs a search based on the attribute name and returns the first match + /// found. If there are multiple attributes with the same name, only the first one is + /// returned. + pub fn get_attribute_value(&self, name: &str) -> Option> { + self.static_att + .iter() + .find(|attr| attr.name == name) + .map(|attr| attr.value.clone()) + } } /// Same as `EntityChanges` but ensures attributes are unique by name. diff --git a/substreams/ethereum-curve/README.md b/substreams/ethereum-curve/README.md index fec075b..2bcc243 100644 --- a/substreams/ethereum-curve/README.md +++ b/substreams/ethereum-curve/README.md @@ -1,8 +1,11 @@ # Instructions -The run command for our substream is a little different here due to the inclusion of the dynamic parameters for manually admitted pools. +The run command for our substream is a little different here due to the inclusion of the dynamic parameters for manually +admitted pools. -This command will add extra parameters to the `map_components` module via the `python params.py` script. This embeds directly in the bash/zsh compatible command here. If `python` is not ideal, the script can be easily converted into `bash` but it would require the `jq` executable (I've used AI to convert it just fine in testing). +This command will add extra parameters to the `map_components` module via the `python params.py` script. This embeds +directly in the bash/zsh compatible command here. If `python` is not ideal, the script can be easily converted into +`bash` but it would require the `jq` executable (I've used AI to convert it just fine in testing). ```bash $ substreams run -e mainnet.eth.streamingfast.io:443 substreams.yaml map_protocol_changes --start-block 11507454 --stop-block +100 -p map_components=`python params.py` @@ -10,12 +13,14 @@ $ substreams run -e mainnet.eth.streamingfast.io:443 substreams.yaml map_protoco ## `params.json` -This json file is a top-level array containing objects that describe a specific `ProtocolComponent`. Each object contains the following fields: +This json file is a top-level array containing objects that describe a specific `ProtocolComponent`. Each object +contains the following fields: - `name`: Just for documentation purposes - `address`: The **lowercase** address of the component - `tx_hash`: The hash of the transaction where the component was emitted - `tokens`: A list of token addresses ordered in the exact same way as the Pool -- `attributes`: A nested object of key to value that represents the static attributes of the component. +- `static_attributes`: A nested object of key to value that represents the static attributes of the component. +- `attributes`: A nested object of key to value that represents attributes. Please see the included 3 examples for `3pool`, `steth`, and `tricrypto2`. diff --git a/substreams/ethereum-curve/integration_test.tycho.yaml b/substreams/ethereum-curve/integration_test.tycho.yaml index 6552c25..b579c9a 100644 --- a/substreams/ethereum-curve/integration_test.tycho.yaml +++ b/substreams/ethereum-curve/integration_test.tycho.yaml @@ -1,143 +1,316 @@ substreams_yaml_path: ./substreams.yaml protocol_type_names: - "curve_pool" -adapter_contract: "CurveSwapAdapter.evm.runtime" -skip_balance_check: false +adapter_contract: "CurveAdapter" +skip_balance_check: true +initialized_accounts: tests: - - name: test_3pool_creation + # Unique pool (no factory) 3pool - 0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7 + - name: test_3pool start_block: 10809470 stop_block: 10810226 - expected_state: - protocol_components: - - id: "0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7" - tokens: - - "0xdac17f958d2ee523a2206206994597c13d831ec7" - - "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" - - "0x6b175474e89094c44da98b954eedeac495271d0f" - static_attributes: - creation_tx: "0x20793bbf260912aae189d5d261ff003c9b9166da8191d8f9d63ff1c7722f3ac6" - - name: test_steth_creation + expected_components: + - id: "0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7" + tokens: + - "0xdac17f958d2ee523a2206206994597c13d831ec7" + - "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" + - "0x6b175474e89094c44da98b954eedeac495271d0f" + static_attributes: + factory_name: "0x6e61" # na + name: "0x33706f6f6c" # 3pool + factory: "0x307830303030303030303030303030303030303030303030303030303030303030303030303030303030" # 0x0000000000000000000000000000000000000000 + creation_tx: "0x20793bbf260912aae189d5d261ff003c9b9166da8191d8f9d63ff1c7722f3ac6" + skip_simulation: false + + # Unique pool (no factory) steth - 0xdc24316b9ae028f1497c275eb9192a3ea0f67022 + - name: test_steth start_block: 11592550 stop_block: 11595553 - expected_state: - protocol_components: - - id: "0xdc24316b9ae028f1497c275eb9192a3ea0f67022" - tokens: - - "0x0000000000000000000000000000000000000000" - - "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84" - static_attributes: - creation_tx: "0xfac67ecbd423a5b915deff06045ec9343568edaec34ae95c43d35f2c018afdaa" - - name: test_crypto_swap_ng_factory_plain_pool_creation - start_block: 19355220 - stop_block: 19356225 - expected_state: - protocol_components: - - id: "0xeeda34a377dd0ca676b9511ee1324974fa8d980d" - tokens: - - "0xd9a442856c234a39a81a089c06451ebaa4306a72" - - "0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0" - static_attributes: - creation_tx: "0x0e2bad5695d4ff8ebbaf668674a24bdcc4843da44933d947f2a454fd731da3c1" - - name: test_crypto_swap_ng_factory_meta_pool_creation + expected_components: + - id: "0xdc24316b9ae028f1497c275eb9192a3ea0f67022" + tokens: + - "0x0000000000000000000000000000000000000000" + - "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84" + static_attributes: + factory: "0x307830303030303030303030303030303030303030303030303030303030303030303030303030303030" # 0x0000000000000000000000000000000000000000 + name: "0x7374657468" # steth + factory_name: "0x6e61" # na + creation_tx: "0xfac67ecbd423a5b915deff06045ec9343568edaec34ae95c43d35f2c018afdaa" + skip_simulation: false + + # Unique pool (no factory) tricrypto2 - 0xd51a44d3fae010294c616388b506acda1bfaae46 + - name: test_tricrypto2 + start_block: 12821118 #This pool was created at 12821148, but it requires some contracts that were created shortly before. + stop_block: 12831387 + expected_components: + - id: "0xd51a44d3fae010294c616388b506acda1bfaae46" + tokens: + - "0xdac17f958d2ee523a2206206994597c13d831ec7" + - "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599" + - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + static_attributes: + factory: "0x307830303030303030303030303030303030303030303030303030303030303030303030303030303030" # 0x0000000000000000000000000000000000000000 + factory_name: "0x6e61" # na + name: "0x74726963727970746f32" # tricrypto2 + creation_tx: "0xdafb6385ed988ce8aacecfe1d97b38ea5e60b1ebce74d2423f71ddd621680138" + skip_simulation: false + + # Unique pool (no factory) susd - 0xa5407eae9ba41422680e2e00537571bcc53efbfd + - name: test_susd + start_block: 9906598 + stop_block: 9907338 + expected_components: + - id: "0xa5407eae9ba41422680e2e00537571bcc53efbfd" + tokens: + - "0x6b175474e89094c44da98b954eedeac495271d0f" + - "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" + - "0xdac17f958d2ee523a2206206994597c13d831ec7" + - "0x57ab1ec28d129707052df4df418d58a2d46d5f51" + static_attributes: + factory: "0x307830303030303030303030303030303030303030303030303030303030303030303030303030303030" # 0x0000000000000000000000000000000000000000 + factory_name: "0x6e61" # na + name: "0x73757364" # susd + creation_tx: "0x51aca4a03a395de8855fa2ca59b7febe520c2a223e69c502066162f7c1a95ec2" + skip_simulation: false + + # Unique pool (no factory) fraxusdc - 0xdcef968d416a41cdac0ed8702fac8128a64241a2 + - name: test_fraxusdc + start_block: 14939588 + stop_block: 14939712 + expected_components: + - id: "0xdcef968d416a41cdac0ed8702fac8128a64241a2" + tokens: + - "0x853d955acef822db058eb8505911ed77f175b99e" + - "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" + static_attributes: + name: "0x6672617875736463" # fraxusdc + factory_name: "0x6e61" # na + factory: "0x307830303030303030303030303030303030303030303030303030303030303030303030303030303030" # 0x0000000000000000000000000000000000000000 + creation_tx: "0x1f4254004ce9e19d4eb742ee5a69d30f29085902d976f73e97c44150225ef775" + skip_simulation: false + + # CryptoSwapNG factory 0x6A8cbed756804B16E05E741eDaBd5cB544AE21bf - PlainPool + - name: test_crypto_swap_ng_factory_plain_pool + start_block: 18580701 + stop_block: 18614742 + initialized_accounts: + - "0x6a8cbed756804b16e05e741edabd5cb544ae21bf" + - "0x0c0e5f2fF0ff18a3be9b835635039256dC4B4963" # Needed by another component that is created within this block range + - "0xc4ad29ba4b3c580e6d59105fff484999997675ff" # Needed by another component that is created within this block range + - "0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7" + expected_components: + - id: "0x02950460E2b9529D0E00284A5fA2d7bDF3fA4d72" + tokens: + - "0x4c9EDD5852cd905f086C759E8383e09bff1E68B3" + - "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" + static_attributes: + factory: "0x307836613863626564373536383034623136653035653734316564616264356362353434616532316266" # 0x6a8cbed756804b16e05e741edabd5cb544ae21bf + factory_name: "0x63727970746f5f737761705f6e675f666163746f7279" # crypto_swap_ng_factory + name: "0x757364652d75736463" # usde-usdc + pool_type: "0x706c61696e5f706f6f6c" # plain_pool + creation_tx: "0x6f4438aa1785589e2170599053a0cdc740d8987746a4b5ad9614b6ab7bb4e550" + skip_simulation: false + + # CryptoSwapNG factory 0x6A8cbed756804B16E05E741eDaBd5cB544AE21bf - MetaPool + - name: test_crypto_swap_ng_factory_metapool start_block: 19216042 stop_block: 19217045 - expected_state: - protocol_components: - - id: "0xef484de8C07B6e2d732A92B5F78e81B38f99f95E" - tokens: - - "0x865377367054516e17014CcdED1e7d814EDC9ce4" - - "0xA5588F7cdf560811710A2D82D3C9c99769DB1Dcb" - static_attributes: - creation_tx: "0x3cfeecae1b43086ee5705f89b803e21eb0492d7d5db06c229586db8fc72f5665" - - name: test_metapool_factory_metapool_creation - start_block: 18028600 + initialized_accounts: + - "0x6a8cbed756804b16e05e741edabd5cb544ae21bf" # Factory, needed for implementations contrats queries + - "0xa5588f7cdf560811710a2d82d3c9c99769db1dcb" # Base pool of this meta pool + expected_components: + - id: "0xef484de8C07B6e2d732A92B5F78e81B38f99f95E" + tokens: + - "0x865377367054516e17014CcdED1e7d814EDC9ce4" + - "0xa5588f7cdf560811710a2d82d3c9c99769db1dcb" + static_attributes: + factory_name: "0x63727970746f5f737761705f6e675f666163746f7279" # crypto_swap_ng_factory + name: "0x646f6c612f667261787079757364" # dola/fraxpyusd + pool_type: "0x6d657461706f6f6c" # metapool + base_pool: "0x307861353538386637636466353630383131373130613264383264336339633939373639646231646362" # 0xa5588f7cdf560811710a2d82d3c9c99769db1dcb + factory: "0x307836613863626564373536383034623136653035653734316564616264356362353434616532316266" # 0x6a8cbed756804b16e05e741edabd5cb544ae21bf + creation_tx: "0x3cfeecae1b43086ee5705f89b803e21eb0492d7d5db06c229586db8fc72f5665" + skip_simulation: true # Reason: this pool use a base pool which is also one of its tokens, therefore our token override doesn't work here and it fails on transfers + + # Metapool factory 0xB9fC157394Af804a3578134A6585C0dc9cc990d4 - MetaPool + - name: test_metapool_factory_metapool + start_block: 18028604 stop_block: 18029610 - expected_state: - protocol_components: - - id: "0x61fA2c947e523F9ABfb8d7e2903A5D5218C119a7" - tokens: - - "0x6c3ea9036406852006290770BEdFcAbA0e23A0e8" - - "0x3175Df0976dFA876431C2E9eE6Bc45b65d3473CC" - static_attributes: - creation_tx: "0xc9c6b879cbb19f7f26405335c3879c350592d530956878ff172e9efad786c63f" - - name: test_metapool_factory_plainpool_creation + initialized_accounts: + - "0xdcef968d416a41cdac0ed8702fac8128a64241a2" + - "0xc4ad29ba4b3c580e6d59105fff484999997675ff" # Needed by another component that is created within this block range + expected_components: + - id: "0x61fA2c947e523F9ABfb8d7e2903A5D5218C119a7" + tokens: + - "0x6c3ea9036406852006290770BEdFcAbA0e23A0e8" + - "0x3175Df0976dFA876431C2E9eE6Bc45b65d3473CC" + static_attributes: + name: "0x70617970616c667261786270" # paypalfraxbp + factory_name: "0x6d6574615f706f6f6c5f666163746f7279" # meta_pool_factory + base_pool: "0x307864636566393638643431366134316364616330656438373032666163383132386136343234316132" # 0xdcfe968d416ac0ed8702fac8128a64241a2 + factory: "0x307862396663313537333934616638303461333537383133346136353835633064633963633939306434" # 0xb9fc157394af804a3578134a6585c0dcc993099d + pool_type: "0x6d657461706f6f6c" # metapool + creation_tx: "0xc9c6b879cbb19f7f26405335c3879c350592d530956878ff172e9efad786c63f" + skip_simulation: true # Reason: this pool calls `totalSupply()` on the LP token during simulation. But this token is overridden and doesn't have anything for totalSupply + + # Metapool factory 0xB9fC157394Af804a3578134A6585C0dc9cc990d4 - PlainPool + - name: test_metapool_factory_plainpool start_block: 18808555 stop_block: 18818577 - expected_state: - protocol_components: - - id: "0xf2DCf6336D8250754B4527f57b275b19c8D5CF88" - tokens: - - "0xe9633C52f4c8B7BDeb08c4A7fE8a5c1B84AFCf67" - - "0x77E06c9eCCf2E797fd462A92B6D7642EF85b0A44" - static_attributes: - creation_tx: "0xeb34c90d352f18ffcfe78b7e393e155f0314acf06c54d1ac9996e4ee5a9b4742" - - id: "0x3f67dc2AdBA4B1beB6A48c30AB3AFb1c1440d35B" - tokens: - - "0xe9633C52f4c8B7BDeb08c4A7fE8a5c1B84AFCf67" - - "0x77E06c9eCCf2E797fd462A92B6D7642EF85b0A44" - static_attributes: - creation_tx: "0x455559b43afaf429c15c1d807fd7f5dd47be30f6411a854499f719b944f4c024" - - name: test_cryptopool_factory_creation + initialized_accounts: + - "0xc4ad29ba4b3c580e6d59105fff484999997675ff" # Needed by another component that is created within this block range + expected_components: + - id: "0xf2DCf6336D8250754B4527f57b275b19c8D5CF88" + tokens: + - "0xe9633C52f4c8B7BDeb08c4A7fE8a5c1B84AFCf67" + - "0x77E06c9eCCf2E797fd462A92B6D7642EF85b0A44" + static_attributes: + name: "0x77737474616f2f7774616f" # wsttao/wtao + factory: "0x307862396663313537333934616638303461333537383133346136353835633064633963633939306434" # 0xb9fc157394af804a3578134a6585c0dcc993099d + factory_name: "0x6d6574615f706f6f6c5f666163746f7279" # meta_pool_factory + pool_type: "0x706c61696e5f706f6f6c" # plain_pool + creation_tx: "0xeb34c90d352f18ffcfe78b7e393e155f0314acf06c54d1ac9996e4ee5a9b4742" + skip_simulation: false + - id: "0x3f67dc2AdBA4B1beB6A48c30AB3AFb1c1440d35B" + tokens: + - "0xe9633C52f4c8B7BDeb08c4A7fE8a5c1B84AFCf67" + - "0x77E06c9eCCf2E797fd462A92B6D7642EF85b0A44" + static_attributes: + name: "0x77737474616f2f7774616f" # wsttao/wtao + factory: "0x307862396663313537333934616638303461333537383133346136353835633064633963633939306434" # 0xb9fc157394af804a3578134a6585c0dcc993099d + factory_name: "0x6d6574615f706f6f6c5f666163746f7279" # meta_pool_factory + pool_type: "0x706c61696e5f706f6f6c" # plain_pool + creation_tx: "0x455559b43afaf429c15c1d807fd7f5dd47be30f6411a854499f719b944f4c024" + skip_simulation: true # Reason: this pool has no liquidity at stop_block + + # CryptoPool factory 0xF18056Bbd320E96A48e3Fbf8bC061322531aac99 + - name: test_cryptopool_factory start_block: 19162590 stop_block: 19163633 - expected_state: - protocol_components: - - id: "0x71db3764d6841d8b01dc27c0fd4a66a8a34b2be0" - tokens: - - "0x04c154b66cb340f3ae24111cc767e0184ed00cc6" - - "0x4591dbff62656e7859afe5e45f6f47d3669fbb28" - static_attributes: - creation_tx: "0xa89c09a7e0dfd84f3a294b8df4f33cc4a623e6d52deee357457afe2591ea596f" - - id: "0x6c9Fe53cC13b125d6476E5Ce2b76983bd5b7A112" - tokens: - - "0x35fA164735182de50811E8e2E824cFb9B6118ac2" - - "0xf951E335afb289353dc249e82926178EaC7DEd78" - static_attributes: - creation_tx: "0xa5b13d50c56242f7994b8e1339032bb4c6f9ac3af3054d4eae3ce9e32e3c1a50" - - name: test_tricrypto_factory_creation + expected_components: + - id: "0x71db3764d6841d8b01dc27c0fd4a66a8a34b2be0" #TODO: ADD TEST THAT USE WETH + tokens: + - "0x04c154b66cb340f3ae24111cc767e0184ed00cc6" + - "0x4591dbff62656e7859afe5e45f6f47d3669fbb28" + static_attributes: + name: "0x343030303030" # 400000 + pool_type: "0x63727970746f5f706f6f6c" # crypto_pool + factory: "0x307866313830353662626433323065393661343865336662663862633036313332323533316161633939" # 0xf18056bbd320e96a48e3fb8bc061322531aacc99 + factory_name: "0x63727970746f5f706f6f6c5f666163746f7279" # crypto_pool_factory + lp_token: "0x6ade6971ca3d90990c30d39c78b0736c7166e07b" # 0x6ade6971ca3d90990c30d39c78b0736c7166e07b + creation_tx: "0xa89c09a7e0dfd84f3a294b8df4f33cc4a623e6d52deee357457afe2591ea596f" + skip_simulation: false + - id: "0x6c9Fe53cC13b125d6476E5Ce2b76983bd5b7A112" + tokens: + - "0x35fA164735182de50811E8e2E824cFb9B6118ac2" + - "0xf951E335afb289353dc249e82926178EaC7DEd78" + static_attributes: + name: "0x343030303030" # 400000 + pool_type: "0x63727970746f5f706f6f6c" # crypto_pool + factory: "0x307866313830353662626433323065393661343865336662663862633036313332323533316161633939" # 0xf18056bbd320e96a48e3fb8bc061322531aacc99 + factory_name: "0x63727970746f5f706f6f6c5f666163746f7279" # crypto_pool_factory + lp_token: "0x94c4eba4f4b97be8d778f8c27027d676270e87a6" # 0x94c4eba4f4b97be8d778f8c27027d676270e87a6 + creation_tx: "0xa5b13d50c56242f7994b8e1339032bb4c6f9ac3af3054d4eae3ce9e32e3c1a50" + skip_simulation: true # Reason: this pool has no liquidity at stop_block + + # CryptoPool factory 0xF18056Bbd320E96A48e3Fbf8bC061322531aac99 - with ETH + - name: test_cryptopool_factory + start_block: 19278886 + stop_block: 19278926 + expected_components: + - id: "0x99e09ee2d6Bb16c0F5ADDfEA649dbB2C1d524624" + tokens: + - "0x0000000000000000000000000000000000000000" + - "0x55296f69f40Ea6d20E478533C15A6B08B654E758" + static_attributes: + name: "0x343030303030" # 400000 + pool_type: "0x63727970746f5f706f6f6c" # crypto_pool + factory: "0x307866313830353662626433323065393661343865336662663862633036313332323533316161633939" # 0xf18056bbd320e96a48e3fb8bc061322531aacc99 + factory_name: "0x63727970746f5f706f6f6c5f666163746f7279" # crypto_pool_factory + lp_token: "0x393dad6c76d962abba489a77dbf37ae948a4a6ee" # 0x393dad6c76d962abba489a77dbf37ae948a4a6ee + creation_tx: "0x52f0f76d97e77579eebd32876de99f656930a99131dc4c4f1dec005786c8782b" + skip_simulation: false + + # Tricrypto factory 0x0c0e5f2fF0ff18a3be9b835635039256dC4B4963 + - name: test_tricrypto_factory start_block: 17371455 stop_block: 17374457 - expected_state: - protocol_components: - - id: "0x7F86Bf177Dd4F3494b841a37e810A34dD56c829B" - tokens: - - "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" - - "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599" - - "0x0000000000000000000000000000000000000000" - static_attributes: - creation_tx: "0x2bd59c19f993b83729fb23498f897a58567c6f0b3ee2f00613ba515a7b19fe23" - - name: test_twocrypto_factory_creation - start_block: 19760009 - stop_block: 19763634 - expected_state: - protocol_components: - - id: "0x011e998d2d794424de95935d55a6ca81822ecb2b" - tokens: - - "0x6c4a8973e6633da2da7187669479c27830c7b1c4" - - "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" - static_attributes: - creation_tx: "0x412b745a9467aed14f22d2a4cc30651939872d19cee65053f14dd3eb9d488003" - - id: "0x19d2b5ce188ca60790755204691e38102749297b" - tokens: - - "0x6c4a8973e6633da2da7187669479c27830c7b1c4" - - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - static_attributes: - creation_tx: "0x61118d9903f8344e5971d1e7c781f76e855996408dac979d3a4971cefafa6587" - - id: "0xb3341ca63b6cecf1e1a0d1a99bf0587f4c305652" - tokens: - - "0x6c4a8973e6633da2da7187669479c27830c7b1c4" - - "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" - static_attributes: - creation_tx: "0xc69332294313b3a8f260e0d5b6a50f0d83707f715fbd8016c32ca61a39ce7ad5" - - id: "0x99ca0fbaa278cd62e26f0e9b6d167b07d1f0d51b" - tokens: - - "0x6c4a8973e6633da2da7187669479c27830c7b1c4" - - "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" - static_attributes: - creation_tx: "0xf2b0c697161f08384c642297e01b3a35f7ec00dd3871d4237a16ae4bb8a1ca99" - - id: "0xde73e407efc75edbafc5bcd62ebb1e7a9b38ebcd" - tokens: - - "0x0d86883faf4ffd7aeb116390af37746f45b6f378" - - "0x78da5799cf427fee11e9996982f4150ece7a99a7" - static_attributes: - creation_tx: "0xd4ad7efdcc16d797dd3494ba02b377da4127fd5b1bd25089858b66e5a7e456ab" + initialized_accounts: + - "0x0c0e5f2ff0ff18a3be9b835635039256dc4b4963" + - "0xc4ad29ba4b3c580e6d59105fff484999997675ff" # Needed by another component that is created within this block range + expected_components: + - id: "0x7F86Bf177Dd4F3494b841a37e810A34dD56c829B" + tokens: + - "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" + - "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599" + - "0x0000000000000000000000000000000000000000" + static_attributes: + factory: "0x307830633065356632666630666631386133626539623833353633353033393235366463346234393633" # 0x0c0e5f2ff0ff18a3be9b8356335039256dc4b4963 + factory_name: "0x74726963727970746f5f666163746f7279" # tricrypto_factory + name: "0x74726963727970746f75736463" # tricrypto_usdc + pool_type: "0x74726963727970746f" # tricrypto + creation_tx: "0x2bd59c19f993b83729fb23498f897a58567c6f0b3ee2f00613ba515a7b19fe23" + skip_simulation: false + + # Twocrypto factory 0x98ee851a00abee0d95d08cf4ca2bdce32aeaaf7f + - name: test_twocrypto_factory + start_block: 19692166 + stop_block: 19692232 + initialized_accounts: + - "0x98ee851a00abee0d95d08cf4ca2bdce32aeaaf7f" # Factory, needed for implementations contracts queries + expected_components: + - id: "0x77146B0a1d08B6844376dF6d9da99bA7F1b19e71" + tokens: + - "0x55C08ca52497e2f1534B59E2917BF524D4765257" + - "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" + static_attributes: + factory: "0x307839386565383531613030616265653064393564303863663463613262646365333261656161663766" # 0x98ee851a00abee0d95d08cf4ca2bdce32aea7f7f + pool_type: "0x74776f63727970746f" # twocrypto + factory_name: "0x74776f63727970746f5f666163746f7279" # twocrypto_factory + name: "0x7577752f77657468" # uwu/weth + creation_tx: "0x61d563e2627437da172fdd60ab54e5cc955fcb75829fd819486e857bac31cad2" + skip_simulation: false + + # StableSwap factory 0x4F8846Ae9380B90d2E71D5e3D042dff3E7ebb40d - PlainPool + - name: test_stableswap_factory_plain_pool + start_block: 17258004 + stop_block: 17260023 + initialized_accounts: + - "0xc4ad29ba4b3c580e6d59105fff484999997675ff" # Needed by another component that is created within this block range + expected_components: + - id: "0x390f3595bCa2Df7d23783dFd126427CCeb997BF4" + tokens: + - "0xf939E0A03FB07F59A73314E73794Be0E57ac1b4E" + - "0xdAC17F958D2ee523a2206206994597C13D831ec7" + static_attributes: + name: "0x6372767573642f75736474" # crvusd/usdt + pool_type: "0x706c61696e5f706f6f6c" # plain_pool + factory: "0x307834663838343661653933383062393064326537316435653364303432646666336537656262343064" # 0x4f8846ae9380b90d2e71d5e3d042dff3e7ebb40d + factory_name: "0x737461626c655f737761705f666163746f7279" # stable_swap_factory + creation_tx: "0x40b25773bf8ea673434277d279af40a85b09072072e7004e9048a2ec0f0dd5a0" + skip_simulation: false + + # StableSwap factory 0x4F8846Ae9380B90d2E71D5e3D042dff3E7ebb40d - Metapool + # - name: test_stableswap_factory_meta_pool + # There was no metapool created from this factory yet. + # https://etherscan.io/address/0x4F8846Ae9380B90d2E71D5e3D042dff3E7ebb40d#events search events with topic 0x01f31cd2abdeb4e5e10ba500f2db0f937d9e8c735ab04681925441b4ea37eda5. + # related event is MetaPoolDeployed(address,address,uint256,uint256,address) + + - name: test_metapool_factory_old + start_block: 11968730 + stop_block: 12028110 + initialized_accounts: + - "0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7" # Linked pool of this metapool + expected_components: + - id: "0xd632f22692FaC7611d2AA1C0D552930D43CAEd3B" + tokens: + - "0x853d955aCEf822Db058eb8505911ED77F175b99e" + - "0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490" + static_attributes: + factory_name: "0x6d6574615f706f6f6c5f666163746f7279" # meta_pool_factory + base_pool: "0x307862656263343437383263376462306131613630636236666539376430623438333033326666316337" # 0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7 + factory: "0x307830393539313538623630343064333264303463333031613732636266643662333965323163396165" # 0x0959158b6040d32d04c301a72cbfd6b39e21c9ae + pool_type: "0x6d657461706f6f6c" # metapool + name: "0x66726178" # frax + creation_tx: "0x1f2a0d4e1c1eca594bd7f27f9952480ccda422c3453e0c5074a63aa46a2ed628" + skip_simulation: true # Reason: this pool calls `totalSupply()` on the LP token during simulation. But this token is overridden and doesn't have anything for totalSupply diff --git a/substreams/ethereum-curve/params.json b/substreams/ethereum-curve/params.json index 23c01aa..f8f0db7 100644 --- a/substreams/ethereum-curve/params.json +++ b/substreams/ethereum-curve/params.json @@ -1,51 +1,58 @@ [ - { - "name": "3pool", - "address": "bebc44782c7db0a1a60cb6fe97d0b483032ff1c7", - "tx_hash": "20793bbf260912aae189d5d261ff003c9b9166da8191d8f9d63ff1c7722f3ac6", - "tokens": [ - "6b175474e89094c44da98b954eedeac495271d0f", - "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", - "dac17f958d2ee523a2206206994597c13d831ec7" - ] - }, - { - "name": "steth", - "address": "dc24316b9ae028f1497c275eb9192a3ea0f67022", - "tx_hash": "fac67ecbd423a5b915deff06045ec9343568edaec34ae95c43d35f2c018afdaa", - "tokens": [ - "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", - "ae7ab96520de3a18e5e111b5eaab095312d7fe84" - ] - }, - { - "name": "tricrypto2", - "address": "d51a44d3fae010294c616388b506acda1bfaae46", - "tx_hash": "dafb6385ed988ce8aacecfe1d97b38ea5e60b1ebce74d2423f71ddd621680138", - "tokens": [ - "dac17f958d2ee523a2206206994597c13d831ec7", - "2260fac5e5542a773aa44fbcfedf7c193bc2c599", - "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - ] - }, - { - "name": "susd", - "address": "a5407eae9ba41422680e2e00537571bcc53efbfd", - "tx_hash": "51aca4a03a395de8855fa2ca59b7febe520c2a223e69c502066162f7c1a95ec2", - "tokens": [ - "6b175474e89094c44da98b954eedeac495271d0f", - "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", - "dac17f958d2ee523a2206206994597c13d831ec7", - "57ab1ec28d129707052df4df418d58a2d46d5f51" - ] - }, - { - "name": "fraxusdc", - "address": "dcef968d416a41cdac0ed8702fac8128a64241a2", - "tx_hash": "1f4254004ce9e19d4eb742ee5a69d30f29085902d976f73e97c44150225ef775", - "tokens": [ - "853d955acef822db058eb8505911ed77f175b99e", - "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" - ] + { + "name": "3pool", + "address": "bebc44782c7db0a1a60cb6fe97d0b483032ff1c7", + "tx_hash": "20793bbf260912aae189d5d261ff003c9b9166da8191d8f9d63ff1c7722f3ac6", + "tokens": [ + "6b175474e89094c44da98b954eedeac495271d0f", + "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "dac17f958d2ee523a2206206994597c13d831ec7" + ] + }, + { + "name": "steth", + "address": "dc24316b9ae028f1497c275eb9192a3ea0f67022", + "tx_hash": "fac67ecbd423a5b915deff06045ec9343568edaec34ae95c43d35f2c018afdaa", + "tokens": [ + "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", + "ae7ab96520de3a18e5e111b5eaab095312d7fe84" + ] + }, + { + "name": "tricrypto2", + "address": "d51a44d3fae010294c616388b506acda1bfaae46", + "contracts": [ + "c4ad29ba4b3c580e6d59105fff484999997675ff", + "40745803c2faa8e8402e2ae935933d07ca8f355c" + ], + "tx_hash": "dafb6385ed988ce8aacecfe1d97b38ea5e60b1ebce74d2423f71ddd621680138", + "tokens": [ + "dac17f958d2ee523a2206206994597c13d831ec7", + "2260fac5e5542a773aa44fbcfedf7c193bc2c599", + "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + ], + "attributes": { + "stateless_contract_addr_0": "0x8F68f4810CcE3194B6cB6F3d50fa58c2c9bDD1d5" } + }, + { + "name": "susd", + "address": "a5407eae9ba41422680e2e00537571bcc53efbfd", + "tx_hash": "51aca4a03a395de8855fa2ca59b7febe520c2a223e69c502066162f7c1a95ec2", + "tokens": [ + "6b175474e89094c44da98b954eedeac495271d0f", + "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "dac17f958d2ee523a2206206994597c13d831ec7", + "57ab1ec28d129707052df4df418d58a2d46d5f51" + ] + }, + { + "name": "fraxusdc", + "address": "dcef968d416a41cdac0ed8702fac8128a64241a2", + "tx_hash": "1f4254004ce9e19d4eb742ee5a69d30f29085902d976f73e97c44150225ef775", + "tokens": [ + "853d955acef822db058eb8505911ed77f175b99e", + "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" + ] + } ] \ No newline at end of file diff --git a/substreams/ethereum-curve/params.py b/substreams/ethereum-curve/params.py index 8023c4c..3085ce3 100644 --- a/substreams/ethereum-curve/params.py +++ b/substreams/ethereum-curve/params.py @@ -11,14 +11,21 @@ def encode_json_to_query_params(params: list[dict[str, Any]]): try: for i, param in enumerate(params): address: str = param["address"] + contracts: str = param.get("contracts", []) tx_hash: str = param["tx_hash"] tokens: list[str] = param["tokens"] + static_attributes: dict[str, str] = param.get("static_attributes", {}) + static_attributes["name"] = param["name"] + static_attributes["factory_name"] = "NA" + static_attributes["factory"] = EMPTY attributes: dict[str, str] = param.get("attributes", {}) - attributes["name"] = param["name"] - attributes["factory_name"] = "NA" - attributes["factory"] = EMPTY encoded_address = f"address={address}" + encoded_contracts = ( + "&" + "&".join([f"contracts[]={contract}" for contract in contracts]) + if contracts + else "" + ) encoded_tx_hash = f"tx_hash={tx_hash}" encoded_tokens = "&".join([f"tokens[]={token}" for token in tokens]) encoded_attributes = "&".join( @@ -27,8 +34,14 @@ def encode_json_to_query_params(params: list[dict[str, Any]]): for key, value in attributes.items() ] ) + encoded_static_attributes = "&".join( + [ + f"static_attribute_keys[]={key}&static_attribute_vals[]={value}" + for key, value in static_attributes.items() + ] + ) - encoded_param = f"{encoded_address}&{encoded_tx_hash}&{encoded_tokens}&{encoded_attributes}" + encoded_param = f"{encoded_address}{encoded_contracts}&{encoded_tx_hash}&{encoded_tokens}&{encoded_attributes}&{encoded_static_attributes}" encoded_param = encoded_param.rstrip("&") encoded_params.append(encoded_param) diff --git a/substreams/ethereum-curve/src/consts.rs b/substreams/ethereum-curve/src/consts.rs index 50181a8..71f860b 100644 --- a/substreams/ethereum-curve/src/consts.rs +++ b/substreams/ethereum-curve/src/consts.rs @@ -15,3 +15,10 @@ pub const STABLESWAP_FACTORY: [u8; 20] = hex!("4F8846Ae9380B90d2E71D5e3D042dff3E // Important addresses pub const WETH_ADDRESS: [u8; 20] = hex!("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"); pub const ETH_ADDRESS: [u8; 20] = hex!("EeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"); +pub const OLD_SUSD: [u8; 20] = hex!("57Ab1E02fEE23774580C119740129eAC7081e9D3"); +pub const NEW_SUSD: [u8; 20] = hex!("57ab1ec28d129707052df4df418d58a2d46d5f51"); +pub const TRICRYPTO_2_LP: [u8; 20] = hex!("c4ad29ba4b3c580e6d59105fff484999997675ff"); +pub const TRICRYPTO_2_MATH_CONTRACT: [u8; 20] = hex!("40745803c2faa8e8402e2ae935933d07ca8f355c"); + +pub const CONTRACTS_TO_INDEX: [[u8; 20]; 4] = + [CRYPTO_SWAP_NG_FACTORY, TRICRYPTO_FACTORY, TRICRYPTO_2_LP, TRICRYPTO_2_MATH_CONTRACT]; diff --git a/substreams/ethereum-curve/src/modules.rs b/substreams/ethereum-curve/src/modules.rs index 59f75c9..3aa8551 100644 --- a/substreams/ethereum-curve/src/modules.rs +++ b/substreams/ethereum-curve/src/modules.rs @@ -5,12 +5,19 @@ use itertools::Itertools; use substreams::{ pb::substreams::StoreDeltas, scalar::BigInt, - store::{StoreAddBigInt, StoreGet, StoreGetString, StoreNew, StoreSet, StoreSetString}, + store::{ + StoreAddBigInt, StoreGet, StoreGetInt64, StoreGetString, StoreNew, StoreSet, StoreSetInt64, + StoreSetString, + }, }; - use substreams_ethereum::pb::eth; -use crate::{pool_changes::emit_eth_deltas, pool_factories, pools::emit_specific_pools}; +use crate::{ + consts::{CONTRACTS_TO_INDEX, NEW_SUSD, OLD_SUSD}, + pool_changes::emit_eth_deltas, + pool_factories, + pools::emit_specific_pools, +}; use tycho_substreams::{ balances::{extract_balance_deltas_from_tx, store_balance_changes}, contract::extract_contract_changes, @@ -29,63 +36,69 @@ impl PartialEq for TransactionWrapper { } #[substreams::handlers::map] -pub fn map_components( - params: String, - block: eth::v2::Block, -) -> Result { - // Gather contract changes by indexing `PoolCreated` events and analysing the `Create` call - // We store these as a hashmap by tx hash since we need to agg by tx hash later - Ok(BlockTransactionProtocolComponents { - tx_components: block - .transactions() - .filter_map(|tx| { - let mut components = tx - .logs_with_calls() - .filter(|(_, call)| !call.call.state_reverted) - .filter_map(|(log, call)| { - pool_factories::address_map( - call.call - .address - .as_slice() - .try_into() - .ok()?, // this shouldn't fail - log, - call.call, - tx, - ) - }) - .collect::>(); +// Map all created components and their related entity changes. +pub fn map_components(params: String, block: eth::v2::Block) -> Result { + let changes = block + .transactions() + .filter_map(|tx| { + let mut entity_changes = vec![]; + let mut components = vec![]; - if let Some(component) = emit_specific_pools(¶ms, tx).expect( - "An unexpected error occured when parsing params for emitting specific pools", + for (log, call) in tx + .logs_with_calls() + .filter(|(_, call)| !call.call.state_reverted) + { + if let Some((component, mut state)) = pool_factories::address_map( + call.call + .address + .as_slice() + .try_into() + .ok()?, // this shouldn't fail + log, + call.call, + tx, ) { - components.push(component) + entity_changes.append(&mut state); + components.push(component); } + } - if !components.is_empty() { - Some(TransactionProtocolComponents { - tx: Some(Transaction { - hash: tx.hash.clone(), - from: tx.from.clone(), - to: tx.to.clone(), - index: Into::::into(tx.index), - }), - components, - }) - } else { - None - } - }) - .collect::>(), - }) + if let Some((component, mut state)) = emit_specific_pools(¶ms, tx).expect( + "An unexpected error occured when parsing params for emitting specific pools", + ) { + entity_changes.append(&mut state); + components.push(component); + } + + if components.is_empty() { + None + } else { + Some(TransactionChanges { + tx: Some(Transaction { + hash: tx.hash.clone(), + from: tx.from.clone(), + to: tx.to.clone(), + index: tx.index.into(), + }), + contract_changes: vec![], + entity_changes, + component_changes: components, + balance_changes: vec![], + }) + } + }) + .collect::>(); + + Ok(BlockChanges { block: None, changes }) } -/// Simply stores the `ProtocolComponent`s with the pool id as the key and tokens as the value +/// Get result `map_components` and stores the created `ProtocolComponent`s with the pool id as the +/// key and tokens as the value #[substreams::handlers::store] -pub fn store_component_tokens(map: BlockTransactionProtocolComponents, store: StoreSetString) { - map.tx_components +pub fn store_component_tokens(map: BlockChanges, store: StoreSetString) { + map.changes .iter() - .flat_map(|tx_components| &tx_components.components) + .flat_map(|tx_changes| &tx_changes.component_changes) .for_each(|component| { store.set( 0, @@ -99,6 +112,29 @@ pub fn store_component_tokens(map: BlockTransactionProtocolComponents, store: St }); } +/// Stores contracts required by components, for example LP tokens if they are different from the +/// pool. +/// This is later used to index them with `extract_contract_changes` +#[substreams::handlers::store] +pub fn store_non_component_accounts(map: BlockChanges, store: StoreSetInt64) { + map.changes + .iter() + .flat_map(|tx_changes| &tx_changes.component_changes) + .for_each(|component| { + // Crypto pool factory creates LP token separated from the pool, we need to index it so + // we add it to the store if the new protocol component comes from this factory + if component.has_attributes(&[ + ("pool_type", "crypto_pool".into()), + ("factory_name", "crypto_pool_factory".into()), + ]) { + let lp_token = component + .get_attribute_value("lp_token") + .expect("didn't find lp_token attribute"); + store.set(0, hex::encode(lp_token), &1); + } + }); +} + /// Since the `PoolBalanceChanged` events administer only deltas, we need to leverage a map and a /// store to be able to tally up final balances for tokens in a pool. #[substreams::handlers::map] @@ -113,15 +149,29 @@ pub fn map_relative_balances( .flat_map(|tx| { emit_eth_deltas(tx, &tokens_store) .into_iter() - .chain(extract_balance_deltas_from_tx(tx, |token, transactor| { - let pool_key = format!("pool:{}", hex::encode(transactor)); - if let Some(tokens) = tokens_store.get_last(pool_key) { - let token_id = hex::encode(token); - tokens.split(':').any(|t| t == token_id) - } else { - false - } - })) + .chain( + extract_balance_deltas_from_tx(tx, |token, transactor| { + let pool_key = format!("pool:{}", hex::encode(transactor)); + if let Some(tokens) = tokens_store.get_last(pool_key) { + let token_id = if token == OLD_SUSD { + hex::encode(NEW_SUSD) + } else { + hex::encode(token) + }; + tokens.split(':').any(|t| t == token_id) + } else { + false + } + }) + .into_iter() + .map(|mut balance| { + if balance.token == OLD_SUSD { + balance.token = NEW_SUSD.into(); + } + balance + }) + .collect::>(), + ) }) .collect(); @@ -150,23 +200,24 @@ pub fn store_balances(deltas: BlockBalanceDeltas, store: StoreAddBigInt) { #[substreams::handlers::map] pub fn map_protocol_changes( block: eth::v2::Block, - grouped_components: BlockTransactionProtocolComponents, + grouped_components: BlockChanges, deltas: BlockBalanceDeltas, components_store: StoreGetString, + non_component_accounts_store: StoreGetInt64, balance_store: StoreDeltas, // Note, this map module is using the `deltas` mode for the store. ) -> 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<_, TransactionChanges> = HashMap::new(); - // `ProtocolComponents` are gathered from `map_pools_created` which just need a bit of work to - // convert into `TransactionChanges` + // `ProtocolComponents` are gathered with some entity changes from `map_pools_created` which + // just need a bit of work to convert into `TransactionChanges` grouped_components - .tx_components + .changes .into_iter() - .for_each(|tx_component| { - let tx = tx_component.tx.as_ref().unwrap(); - transaction_changes + .for_each(|tx_changes| { + let tx = tx_changes.tx.as_ref().unwrap(); + let transaction_entry = transaction_changes .entry(tx.index) .or_insert_with(|| TransactionChanges { tx: Some(tx.clone()), @@ -174,18 +225,23 @@ pub fn map_protocol_changes( component_changes: vec![], balance_changes: vec![], entity_changes: vec![], - }) + }); + + let formated_components: Vec<_> = tx_changes //TODO: format directly at creation .component_changes - .extend_from_slice( - &(tx_component - .components - .into_iter() - .map(|mut component| { - component.id = format!("0x{}", component.id); - component - }) - .collect::>()), - ); + .into_iter() + .map(|mut component| { + component.id = format!("0x{}", component.id); + component + }) + .collect(); + + transaction_entry + .component_changes + .extend(formated_components); + transaction_entry + .entity_changes + .extend(tx_changes.entity_changes); }); // Balance changes are gathered by the `StoreDelta` based on `TokenExchange`, etc. creating @@ -242,7 +298,14 @@ pub fn map_protocol_changes( |addr| { components_store .get_last(format!("pool:{0}", hex::encode(addr))) - .is_some() + .is_some() || + non_component_accounts_store + .get_last(hex::encode(addr)) + .is_some() || + CONTRACTS_TO_INDEX.contains( + addr.try_into() + .expect("address should be 20 bytes long"), + ) }, &mut transaction_changes, ); diff --git a/substreams/ethereum-curve/src/pool_factories.rs b/substreams/ethereum-curve/src/pool_factories.rs index cf3c658..a52e249 100644 --- a/substreams/ethereum-curve/src/pool_factories.rs +++ b/substreams/ethereum-curve/src/pool_factories.rs @@ -6,9 +6,8 @@ use substreams_ethereum::{ use crate::abi; use tycho_substreams::prelude::*; -use substreams::scalar::BigInt; - use crate::consts::*; +use substreams::scalar::BigInt; /// This trait defines some helpers for serializing and deserializing `Vec` which is needed /// to be able to encode some of the `Attribute`s. This should also be handled by any downstream @@ -33,9 +32,14 @@ impl SerializableVecBigInt for Vec { } } -/// Converts address bytes into a string containing a leading `0x`. +/// Converts address bytes into a Vec containing a leading `0x`. fn address_to_bytes_with_0x(address: &[u8; 20]) -> Vec { - format!("0x{}", hex::encode(address)).into_bytes() + address_to_string_with_0x(address).into_bytes() +} + +/// Converts address bytes into a string containing a leading `0x`. +fn address_to_string_with_0x(address: &[u8]) -> String { + format!("0x{}", hex::encode(address)) } /// Function that swaps `WETH` addresses for `ETH` address for specific factory types that decide @@ -53,9 +57,9 @@ fn swap_weth_for_eth(tokens: Vec>) -> Vec> { } /// This massive function matches factory address to specific logic to construct -/// `ProtocolComponent`s. While, most of the logic is readily replicable, several factories differ -/// in information density resulting in needing other information sources such as decoding calls -/// or even making RPC calls to provide extra details. +/// `ProtocolComponent`s and their related `EntityChanges` at creation. While, most of the logic is +/// readily replicable, several factories differ in information density resulting in needing other +/// information sources such as decoding calls or even making RPC calls to provide extra details. /// /// Each `ProtocolComponent` contains the following static attributes: /// - `pool_type`: The type of pool, such as `crypto_pool`, `plain_pool`, `metapool`, etc. @@ -66,15 +70,15 @@ fn swap_weth_for_eth(tokens: Vec>) -> Vec> { /// The basic flow of this function is as follows: /// - Match the factory address /// - Decode the relevant event from the log -/// - Attempt to decode the cooresponding function call (based on the permutation of the ABI) +/// - Attempt to decode the corresponding function call (based on the permutation of the ABI) /// - Optionally make an RPC call to produce further information (see metapools) -/// - Construct the cooresponding `ProtocolComponent` +/// - Construct the corresponding `ProtocolComponent` pub fn address_map( call_address: &[u8; 20], log: &Log, call: &Call, tx: &TransactionTrace, -) -> Option { +) -> Option<(ProtocolComponent, Vec)> { match *call_address { CRYPTO_POOL_FACTORY => { let pool_added = @@ -84,46 +88,71 @@ pub fn address_map( let component_id = &call.return_data[12..]; - Some(ProtocolComponent { - id: hex::encode(component_id), - tx: Some(Transaction { - to: tx.to.clone(), - from: tx.from.clone(), - hash: tx.hash.clone(), - index: tx.index.into(), - }), - tokens, - contracts: vec![component_id.into()], - static_att: vec![ - Attribute { - name: "pool_type".into(), - value: "crypto_pool".into(), - change: ChangeType::Creation.into(), - }, - Attribute { - name: "name".into(), - value: pool_added.a.to_string().into(), - change: ChangeType::Creation.into(), - }, - Attribute { - name: "factory_name".into(), - value: "crypto_pool_factory".into(), - change: ChangeType::Creation.into(), - }, - Attribute { - name: "factory".into(), - value: address_to_bytes_with_0x(&CRYPTO_POOL_FACTORY), - change: ChangeType::Creation.into(), - }, - ], - change: ChangeType::Creation.into(), - protocol_type: Some(ProtocolType { - name: "curve_pool".into(), - financial_type: FinancialType::Swap.into(), - attribute_schema: Vec::new(), - implementation_type: ImplementationType::Vm.into(), - }), - }) + let token_implementation = extract_proxy_impl(call, tx, 0).unwrap_or([1u8; 20]); + let pool_implementation = extract_proxy_impl(call, tx, 1).unwrap_or([1u8; 20]); + + Some(( + ProtocolComponent { + id: hex::encode(component_id), + tx: Some(Transaction { + to: tx.to.clone(), + from: tx.from.clone(), + hash: tx.hash.clone(), + index: tx.index.into(), + }), + tokens, + contracts: vec![component_id.into(), pool_added.token.clone()], + static_att: vec![ + Attribute { + name: "pool_type".into(), + value: "crypto_pool".into(), + change: ChangeType::Creation.into(), + }, + Attribute { + name: "name".into(), + value: pool_added.a.to_string().into(), + change: ChangeType::Creation.into(), + }, + Attribute { + name: "factory_name".into(), + value: "crypto_pool_factory".into(), + change: ChangeType::Creation.into(), + }, + Attribute { + name: "factory".into(), + value: address_to_bytes_with_0x(&CRYPTO_POOL_FACTORY), + change: ChangeType::Creation.into(), + }, + Attribute { + name: "lp_token".into(), + value: pool_added.token, + change: ChangeType::Creation.into(), + }, + ], + change: ChangeType::Creation.into(), + protocol_type: Some(ProtocolType { + name: "curve_pool".into(), + financial_type: FinancialType::Swap.into(), + attribute_schema: Vec::new(), + implementation_type: ImplementationType::Vm.into(), + }), + }, + vec![EntityChanges { + component_id: address_to_string_with_0x(component_id), + attributes: vec![ + Attribute { + name: "stateless_contract_addr_0".into(), + value: address_to_bytes_with_0x(&pool_implementation), + change: ChangeType::Creation.into(), + }, + Attribute { + name: "stateless_contract_addr_1".into(), + value: address_to_bytes_with_0x(&token_implementation), + change: ChangeType::Creation.into(), + }, + ], + }], + )) } META_POOL_FACTORY => { if let Some(pool_added) = @@ -164,51 +193,63 @@ pub fn address_map( // The return data of several of these calls contain the actual component id let component_id = &call.return_data[12..]; + let tokens: Vec<_> = pool_added + .coins + .into_iter() + .filter(|token| *token != [0; 20]) + .collect(); - Some(ProtocolComponent { - id: hex::encode(component_id), - tx: Some(Transaction { - to: tx.to.clone(), - from: tx.from.clone(), - hash: tx.hash.clone(), - index: tx.index.into(), - }), - tokens: pool_added - .coins - .into_iter() - .filter(|token| *token != [0; 20]) - .collect(), - contracts: vec![component_id.into()], - static_att: vec![ - Attribute { - name: "pool_type".into(), - value: "plain_pool".into(), + let pool_implementation = extract_proxy_impl(call, tx, 0).unwrap_or([1u8; 20]); + Some(( + ProtocolComponent { + id: hex::encode(component_id), + tx: Some(Transaction { + to: tx.to.clone(), + from: tx.from.clone(), + hash: tx.hash.clone(), + index: tx.index.into(), + }), + tokens, + contracts: vec![component_id.into()], + static_att: vec![ + Attribute { + name: "pool_type".into(), + value: "plain_pool".into(), + change: ChangeType::Creation.into(), + }, + Attribute { + name: "name".into(), + value: add_pool.name.into(), + change: ChangeType::Creation.into(), + }, + Attribute { + name: "factory_name".into(), + value: "meta_pool_factory".into(), + change: ChangeType::Creation.into(), + }, + Attribute { + name: "factory".into(), + value: address_to_bytes_with_0x(&META_POOL_FACTORY), + change: ChangeType::Creation.into(), + }, + ], + change: ChangeType::Creation.into(), + protocol_type: Some(ProtocolType { + name: "curve_pool".into(), + financial_type: FinancialType::Swap.into(), + attribute_schema: Vec::new(), + implementation_type: ImplementationType::Vm.into(), + }), + }, + vec![EntityChanges { + component_id: address_to_string_with_0x(component_id), + attributes: vec![Attribute { + name: "stateless_contract_addr_0".into(), + value: address_to_bytes_with_0x(&pool_implementation), change: ChangeType::Creation.into(), - }, - Attribute { - name: "name".into(), - value: add_pool.name.into(), - change: ChangeType::Creation.into(), - }, - Attribute { - name: "factory_name".into(), - value: "meta_pool_factory".into(), - change: ChangeType::Creation.into(), - }, - Attribute { - name: "factory".into(), - value: address_to_bytes_with_0x(&META_POOL_FACTORY), - change: ChangeType::Creation.into(), - }, - ], - change: ChangeType::Creation.into(), - protocol_type: Some(ProtocolType { - name: "curve_pool".into(), - financial_type: FinancialType::Swap.into(), - attribute_schema: Vec::new(), - implementation_type: ImplementationType::Vm.into(), - }), - }) + }], + }], + )) } else if let Some(pool_added) = abi::meta_pool_factory::events::MetaPoolDeployed::match_and_decode(log) { @@ -238,53 +279,65 @@ pub fn address_map( abi::meta_registry::functions::GetLpToken1 { pool: add_pool.base_pool.clone() }; let lp_token = get_lp_token.call(META_REGISTRY.to_vec())?; - Some(ProtocolComponent { - id: hex::encode(component_id), - tx: Some(Transaction { - to: tx.to.clone(), - from: tx.from.clone(), - hash: tx.hash.clone(), - index: tx.index.into(), - }), - tokens: vec![pool_added.coin, lp_token], - contracts: vec![component_id.into()], - static_att: vec![ - Attribute { - name: "pool_type".into(), - value: "metapool".into(), + let pool_implementation = extract_proxy_impl(call, tx, 0).unwrap_or([1u8; 20]); + + Some(( + ProtocolComponent { + id: hex::encode(component_id), + tx: Some(Transaction { + to: tx.to.clone(), + from: tx.from.clone(), + hash: tx.hash.clone(), + index: tx.index.into(), + }), + tokens: vec![pool_added.coin, lp_token], + contracts: vec![component_id.into(), add_pool.base_pool.clone()], + static_att: vec![ + Attribute { + name: "pool_type".into(), + value: "metapool".into(), + change: ChangeType::Creation.into(), + }, + Attribute { + name: "name".into(), + value: add_pool.name.into(), + change: ChangeType::Creation.into(), + }, + Attribute { + name: "factory_name".into(), + value: "meta_pool_factory".into(), + change: ChangeType::Creation.into(), + }, + Attribute { + name: "factory".into(), + value: address_to_bytes_with_0x(&META_POOL_FACTORY), + change: ChangeType::Creation.into(), + }, + Attribute { + name: "base_pool".into(), + value: address_to_bytes_with_0x( + &add_pool.base_pool.try_into().unwrap(), + ), + change: ChangeType::Creation.into(), + }, + ], + change: ChangeType::Creation.into(), + protocol_type: Some(ProtocolType { + name: "curve_pool".into(), + financial_type: FinancialType::Swap.into(), + attribute_schema: Vec::new(), + implementation_type: ImplementationType::Vm.into(), + }), + }, + vec![EntityChanges { + component_id: address_to_string_with_0x(component_id), + attributes: vec![Attribute { + name: "stateless_contract_addr_0".into(), + value: address_to_bytes_with_0x(&pool_implementation), change: ChangeType::Creation.into(), - }, - Attribute { - name: "name".into(), - value: add_pool.name.into(), - change: ChangeType::Creation.into(), - }, - Attribute { - name: "factory_name".into(), - value: "meta_pool_factory".into(), - change: ChangeType::Creation.into(), - }, - Attribute { - name: "factory".into(), - value: address_to_bytes_with_0x(&META_POOL_FACTORY), - change: ChangeType::Creation.into(), - }, - Attribute { - name: "base_pool".into(), - value: address_to_bytes_with_0x( - &add_pool.base_pool.try_into().unwrap(), - ), - change: ChangeType::Creation.into(), - }, - ], - change: ChangeType::Creation.into(), - protocol_type: Some(ProtocolType { - name: "curve_pool".into(), - financial_type: FinancialType::Swap.into(), - attribute_schema: Vec::new(), - implementation_type: ImplementationType::Vm.into(), - }), - }) + }], + }], + )) } else { None } @@ -310,56 +363,68 @@ pub fn address_map( ) })?; + let pool_implementation = extract_proxy_impl(call, tx, 0).unwrap_or([1u8; 20]); + let component_id = &call.return_data[12..]; let lp_token = get_token_from_pool(&pool_added.base_pool); - Some(ProtocolComponent { - id: hex::encode(component_id), - tx: Some(Transaction { - to: tx.to.clone(), - from: tx.from.clone(), - hash: tx.hash.clone(), - index: tx.index.into(), - }), - tokens: vec![pool_added.coin, lp_token], - contracts: vec![component_id.into()], - static_att: vec![ - Attribute { - name: "pool_type".into(), - value: "metapool".into(), + Some(( + ProtocolComponent { + id: hex::encode(component_id), + tx: Some(Transaction { + to: tx.to.clone(), + from: tx.from.clone(), + hash: tx.hash.clone(), + index: tx.index.into(), + }), + tokens: vec![pool_added.coin, lp_token], + contracts: vec![component_id.into()], + static_att: vec![ + Attribute { + name: "pool_type".into(), + value: "metapool".into(), + change: ChangeType::Creation.into(), + }, + Attribute { + name: "name".into(), + value: add_pool.name.into(), + change: ChangeType::Creation.into(), + }, + Attribute { + name: "factory_name".into(), + value: "meta_pool_factory".into(), + change: ChangeType::Creation.into(), + }, + Attribute { + name: "factory".into(), + value: address_to_bytes_with_0x(&META_POOL_FACTORY_OLD), + change: ChangeType::Creation.into(), + }, + Attribute { + name: "base_pool".into(), + value: address_to_bytes_with_0x( + &add_pool.base_pool.try_into().unwrap(), + ), + change: ChangeType::Creation.into(), + }, + ], + change: ChangeType::Creation.into(), + protocol_type: Some(ProtocolType { + name: "curve_pool".into(), + financial_type: FinancialType::Swap.into(), + attribute_schema: Vec::new(), + implementation_type: ImplementationType::Vm.into(), + }), + }, + vec![EntityChanges { + component_id: address_to_string_with_0x(component_id), + attributes: vec![Attribute { + name: "stateless_contract_addr_0".into(), + value: address_to_bytes_with_0x(&pool_implementation), change: ChangeType::Creation.into(), - }, - Attribute { - name: "name".into(), - value: add_pool.name.into(), - change: ChangeType::Creation.into(), - }, - Attribute { - name: "factory_name".into(), - value: "meta_pool_factory".into(), - change: ChangeType::Creation.into(), - }, - Attribute { - name: "factory".into(), - value: address_to_bytes_with_0x(&META_POOL_FACTORY_OLD), - change: ChangeType::Creation.into(), - }, - Attribute { - name: "base_pool".into(), - value: address_to_bytes_with_0x( - &add_pool.base_pool.try_into().unwrap(), - ), - change: ChangeType::Creation.into(), - }, - ], - change: ChangeType::Creation.into(), - protocol_type: Some(ProtocolType { - name: "curve_pool".into(), - financial_type: FinancialType::Swap.into(), - attribute_schema: Vec::new(), - implementation_type: ImplementationType::Vm.into(), - }), - }) + }], + }], + )) } else { None } @@ -373,46 +438,61 @@ pub fn address_map( call, )?; let component_id = &call.return_data[12..]; - Some(ProtocolComponent { - id: hex::encode(component_id), - tx: Some(Transaction { - to: tx.to.clone(), - from: tx.from.clone(), - hash: tx.hash.clone(), - index: tx.index.into(), - }), - tokens: pool_added.coins, - contracts: vec![component_id.into()], - static_att: vec![ - Attribute { - name: "pool_type".into(), - value: "plain_pool".into(), + Some(( + ProtocolComponent { + id: hex::encode(component_id), + tx: Some(Transaction { + to: tx.to.clone(), + from: tx.from.clone(), + hash: tx.hash.clone(), + index: tx.index.into(), + }), + tokens: pool_added.coins, + contracts: vec![component_id.into(), CRYPTO_SWAP_NG_FACTORY.into()], + static_att: vec![ + Attribute { + name: "pool_type".into(), + value: "plain_pool".into(), + change: ChangeType::Creation.into(), + }, + Attribute { + name: "name".into(), + value: add_pool.name.into(), + change: ChangeType::Creation.into(), + }, + Attribute { + name: "factory_name".into(), + value: "crypto_swap_ng_factory".into(), + change: ChangeType::Creation.into(), + }, + Attribute { + name: "factory".into(), + value: address_to_bytes_with_0x(&CRYPTO_SWAP_NG_FACTORY), + change: ChangeType::Creation.into(), + }, + ], + change: ChangeType::Creation.into(), + protocol_type: Some(ProtocolType { + name: "curve_pool".into(), + financial_type: FinancialType::Swap.into(), + attribute_schema: Vec::new(), + implementation_type: ImplementationType::Vm.into(), + }), + }, + vec![EntityChanges { + component_id: address_to_string_with_0x(component_id), + attributes: vec![Attribute { + name: "stateless_contract_addr_0".into(), + // Call views_implementation() on CRYPTO_SWAP_NG_FACTORY + value: format!( + "call:0x{}:views_implementation()", + hex::encode(CRYPTO_SWAP_NG_FACTORY) + ) + .into(), change: ChangeType::Creation.into(), - }, - Attribute { - name: "name".into(), - value: add_pool.name.into(), - change: ChangeType::Creation.into(), - }, - Attribute { - name: "factory_name".into(), - value: "crypto_swap_ng_factory".into(), - change: ChangeType::Creation.into(), - }, - Attribute { - name: "factory".into(), - value: address_to_bytes_with_0x(&CRYPTO_SWAP_NG_FACTORY), - change: ChangeType::Creation.into(), - }, - ], - change: ChangeType::Creation.into(), - protocol_type: Some(ProtocolType { - name: "curve_pool".into(), - financial_type: FinancialType::Swap.into(), - attribute_schema: Vec::new(), - implementation_type: ImplementationType::Vm.into(), - }), - }) + }], + }], + )) } else if let Some(pool_added) = abi::crypto_swap_ng_factory::events::MetaPoolDeployed::match_and_decode(log) { @@ -421,53 +501,84 @@ pub fn address_map( let component_id = &call.return_data[12..]; let lp_token = get_token_from_pool(&pool_added.base_pool); - Some(ProtocolComponent { - id: hex::encode(component_id), - tx: Some(Transaction { - to: tx.to.clone(), - from: tx.from.clone(), - hash: tx.hash.clone(), - index: tx.index.into(), - }), - tokens: vec![pool_added.coin, lp_token], - contracts: vec![component_id.into()], - static_att: vec![ - Attribute { - name: "pool_type".into(), - value: "metapool".into(), - change: ChangeType::Creation.into(), - }, - Attribute { - name: "name".into(), - value: add_pool.name.into(), - change: ChangeType::Creation.into(), - }, - Attribute { - name: "factory_name".into(), - value: "crypto_swap_ng_factory".into(), - change: ChangeType::Creation.into(), - }, - Attribute { - name: "factory".into(), - value: address_to_bytes_with_0x(&CRYPTO_SWAP_NG_FACTORY), - change: ChangeType::Creation.into(), - }, - Attribute { - name: "base_pool".into(), - value: address_to_bytes_with_0x( - &pool_added.base_pool.try_into().unwrap(), - ), - change: ChangeType::Creation.into(), - }, - ], - change: ChangeType::Creation.into(), - protocol_type: Some(ProtocolType { - name: "curve_pool".into(), - financial_type: FinancialType::Swap.into(), - attribute_schema: Vec::new(), - implementation_type: ImplementationType::Vm.into(), - }), - }) + Some(( + ProtocolComponent { + id: hex::encode(component_id), + tx: Some(Transaction { + to: tx.to.clone(), + from: tx.from.clone(), + hash: tx.hash.clone(), + index: tx.index.into(), + }), + tokens: vec![pool_added.coin, lp_token], + contracts: vec![ + component_id.into(), + CRYPTO_SWAP_NG_FACTORY.into(), + pool_added.base_pool.clone(), + ], + static_att: vec![ + Attribute { + name: "pool_type".into(), + value: "metapool".into(), + change: ChangeType::Creation.into(), + }, + Attribute { + name: "name".into(), + value: add_pool.name.into(), + change: ChangeType::Creation.into(), + }, + Attribute { + name: "factory_name".into(), + value: "crypto_swap_ng_factory".into(), + change: ChangeType::Creation.into(), + }, + Attribute { + name: "factory".into(), + value: address_to_bytes_with_0x(&CRYPTO_SWAP_NG_FACTORY), + change: ChangeType::Creation.into(), + }, + Attribute { + name: "base_pool".into(), + value: address_to_bytes_with_0x( + &pool_added.base_pool.try_into().unwrap(), + ), + change: ChangeType::Creation.into(), + }, + ], + change: ChangeType::Creation.into(), + protocol_type: Some(ProtocolType { + name: "curve_pool".into(), + financial_type: FinancialType::Swap.into(), + attribute_schema: Vec::new(), + implementation_type: ImplementationType::Vm.into(), + }), + }, + vec![EntityChanges { + component_id: address_to_string_with_0x(component_id), + attributes: vec![ + Attribute { + name: "stateless_contract_addr_0".into(), + // Call views_implementation() on CRYPTO_SWAP_NG_FACTORY + value: format!( + "call:0x{}:views_implementation()", + hex::encode(CRYPTO_SWAP_NG_FACTORY) + ) + .into(), + change: ChangeType::Creation.into(), + }, + Attribute { + name: "stateless_contract_addr_1".into(), + // Call math_implementation() on CRYPTO_SWAP_NG_FACTORY + value: format!( + "call:0x{}:math_implementation()", + hex::encode(CRYPTO_SWAP_NG_FACTORY) + ) + .into(), + change: ChangeType::Creation.into(), + }, + ], + }], + )) } else { None } @@ -477,47 +588,75 @@ pub fn address_map( abi::tricrypto_factory::events::TricryptoPoolDeployed::match_and_decode(log) { let tokens = swap_weth_for_eth(pool_added.coins.into()); + let id = hex::encode(&pool_added.pool); - Some(ProtocolComponent { - id: hex::encode(&pool_added.pool), - tx: Some(Transaction { - to: tx.to.clone(), - from: tx.from.clone(), - hash: tx.hash.clone(), - index: tx.index.into(), - }), - tokens, - contracts: vec![pool_added.pool], - static_att: vec![ - Attribute { - name: "pool_type".into(), - value: "tricrypto".into(), - change: ChangeType::Creation.into(), - }, - Attribute { - name: "name".into(), - value: pool_added.name.into(), - change: ChangeType::Creation.into(), - }, - Attribute { - name: "factory_name".into(), - value: "tricrypto_factory".into(), - change: ChangeType::Creation.into(), - }, - Attribute { - name: "factory".into(), - value: address_to_bytes_with_0x(&TRICRYPTO_FACTORY), - change: ChangeType::Creation.into(), - }, - ], - change: ChangeType::Creation.into(), - protocol_type: Some(ProtocolType { - name: "curve_pool".into(), - financial_type: FinancialType::Swap.into(), - attribute_schema: Vec::new(), - implementation_type: ImplementationType::Vm.into(), - }), - }) + Some(( + ProtocolComponent { + id: id.clone(), + tx: Some(Transaction { + to: tx.to.clone(), + from: tx.from.clone(), + hash: tx.hash.clone(), + index: tx.index.into(), + }), + tokens, + contracts: vec![pool_added.pool, TRICRYPTO_FACTORY.into()], + static_att: vec![ + Attribute { + name: "pool_type".into(), + value: "tricrypto".into(), + change: ChangeType::Creation.into(), + }, + Attribute { + name: "name".into(), + value: pool_added.name.into(), + change: ChangeType::Creation.into(), + }, + Attribute { + name: "factory_name".into(), + value: "tricrypto_factory".into(), + change: ChangeType::Creation.into(), + }, + Attribute { + name: "factory".into(), + value: address_to_bytes_with_0x(&TRICRYPTO_FACTORY), + change: ChangeType::Creation.into(), + }, + ], + change: ChangeType::Creation.into(), + protocol_type: Some(ProtocolType { + name: "curve_pool".into(), + financial_type: FinancialType::Swap.into(), + attribute_schema: Vec::new(), + implementation_type: ImplementationType::Vm.into(), + }), + }, + vec![EntityChanges { + component_id: format!("0x{}", id), + attributes: vec![ + Attribute { + name: "stateless_contract_addr_0".into(), + // Call views_implementation() on TRICRYPTO_FACTORY + value: format!( + "call:0x{}:views_implementation()", + hex::encode(TRICRYPTO_FACTORY) + ) + .into(), + change: ChangeType::Creation.into(), + }, + Attribute { + name: "stateless_contract_addr_1".into(), + // Call math_implementation() on TRICRYPTO_FACTORY + value: format!( + "call:0x{}:math_implementation()", + hex::encode(TRICRYPTO_FACTORY) + ) + .into(), + change: ChangeType::Creation.into(), + }, + ], + }], + )) } else { None } @@ -558,50 +697,65 @@ pub fn address_map( return None; }; let component_id = &call.return_data[12..]; - Some(ProtocolComponent { - id: hex::encode(component_id), - tx: Some(Transaction { - to: tx.to.clone(), - from: tx.from.clone(), - hash: tx.hash.clone(), - index: tx.index.into(), - }), - tokens: pool_added - .coins - .into_iter() - .filter(|token| *token != [0; 20]) - .collect(), - contracts: vec![component_id.into()], - static_att: vec![ - Attribute { - name: "pool_type".into(), - value: "plain_pool".into(), + + let tokens: Vec<_> = pool_added + .coins + .into_iter() + .filter(|token| *token != [0; 20]) + .collect(); + + let pool_implementation = extract_proxy_impl(call, tx, 0).unwrap_or([1u8; 20]); + + Some(( + ProtocolComponent { + id: hex::encode(component_id), + tx: Some(Transaction { + to: tx.to.clone(), + from: tx.from.clone(), + hash: tx.hash.clone(), + index: tx.index.into(), + }), + tokens, + contracts: vec![component_id.into()], + static_att: vec![ + Attribute { + name: "pool_type".into(), + value: "plain_pool".into(), + change: ChangeType::Creation.into(), + }, + Attribute { + name: "name".into(), + value: add_pool.name.into(), + change: ChangeType::Creation.into(), + }, + Attribute { + name: "factory_name".into(), + value: "stable_swap_factory".into(), + change: ChangeType::Creation.into(), + }, + Attribute { + name: "factory".into(), + value: address_to_bytes_with_0x(&STABLESWAP_FACTORY), + change: ChangeType::Creation.into(), + }, + ], + change: ChangeType::Creation.into(), + protocol_type: Some(ProtocolType { + name: "curve_pool".into(), + financial_type: FinancialType::Swap.into(), + attribute_schema: Vec::new(), + implementation_type: ImplementationType::Vm.into(), + }), + }, + vec![EntityChanges { + component_id: address_to_string_with_0x(component_id), + attributes: vec![Attribute { + name: "stateless_contract_addr_0".into(), + value: address_to_bytes_with_0x(&pool_implementation), change: ChangeType::Creation.into(), - }, - Attribute { - name: "name".into(), - value: add_pool.name.into(), - change: ChangeType::Creation.into(), - }, - Attribute { - name: "factory_name".into(), - value: "stable_swap_factory".into(), - change: ChangeType::Creation.into(), - }, - Attribute { - name: "factory".into(), - value: address_to_bytes_with_0x(&STABLESWAP_FACTORY), - change: ChangeType::Creation.into(), - }, - ], - change: ChangeType::Creation.into(), - protocol_type: Some(ProtocolType { - name: "curve_pool".into(), - financial_type: FinancialType::Swap.into(), - attribute_schema: Vec::new(), - implementation_type: ImplementationType::Vm.into(), - }), - }) + }], + }], + )) } else if let Some(pool_added) = abi::stableswap_factory::events::MetaPoolDeployed::match_and_decode(log) { @@ -630,53 +784,56 @@ pub fn address_map( abi::meta_registry::functions::GetLpToken1 { pool: add_pool.base_pool.clone() }; let lp_token = get_lp_token.call(META_REGISTRY.to_vec())?; - Some(ProtocolComponent { - id: hex::encode(component_id), - tx: Some(Transaction { - to: tx.to.clone(), - from: tx.from.clone(), - hash: tx.hash.clone(), - index: tx.index.into(), - }), - tokens: vec![pool_added.coin, lp_token], - contracts: vec![component_id.into()], - static_att: vec![ - Attribute { - name: "pool_type".into(), - value: "metapool".into(), - change: ChangeType::Creation.into(), - }, - Attribute { - name: "name".into(), - value: add_pool.name.into(), - change: ChangeType::Creation.into(), - }, - Attribute { - name: "factory_name".into(), - value: "stable_swap_factory".into(), - change: ChangeType::Creation.into(), - }, - Attribute { - name: "factory".into(), - value: address_to_bytes_with_0x(&STABLESWAP_FACTORY), - change: ChangeType::Creation.into(), - }, - Attribute { - name: "base_pool".into(), - value: address_to_bytes_with_0x( - &pool_added.base_pool.try_into().unwrap(), - ), - change: ChangeType::Creation.into(), - }, - ], - change: ChangeType::Creation.into(), - protocol_type: Some(ProtocolType { - name: "curve_pool".into(), - financial_type: FinancialType::Swap.into(), - attribute_schema: Vec::new(), - implementation_type: ImplementationType::Vm.into(), - }), - }) + Some(( + ProtocolComponent { + id: hex::encode(component_id), + tx: Some(Transaction { + to: tx.to.clone(), + from: tx.from.clone(), + hash: tx.hash.clone(), + index: tx.index.into(), + }), + tokens: vec![pool_added.coin, lp_token], + contracts: vec![component_id.into()], + static_att: vec![ + Attribute { + name: "pool_type".into(), + value: "metapool".into(), + change: ChangeType::Creation.into(), + }, + Attribute { + name: "name".into(), + value: add_pool.name.into(), + change: ChangeType::Creation.into(), + }, + Attribute { + name: "factory_name".into(), + value: "stable_swap_factory".into(), + change: ChangeType::Creation.into(), + }, + Attribute { + name: "factory".into(), + value: address_to_bytes_with_0x(&STABLESWAP_FACTORY), + change: ChangeType::Creation.into(), + }, + Attribute { + name: "base_pool".into(), + value: address_to_bytes_with_0x( + &pool_added.base_pool.try_into().unwrap(), + ), + change: ChangeType::Creation.into(), + }, + ], + change: ChangeType::Creation.into(), + protocol_type: Some(ProtocolType { + name: "curve_pool".into(), + financial_type: FinancialType::Swap.into(), + attribute_schema: Vec::new(), + implementation_type: ImplementationType::Vm.into(), + }), + }, + vec![], + )) } else { None } @@ -685,46 +842,74 @@ pub fn address_map( if let Some(pool_added) = abi::twocrypto_factory::events::TwocryptoPoolDeployed::match_and_decode(log) { - Some(ProtocolComponent { - id: hex::encode(&pool_added.pool), - tx: Some(Transaction { - to: tx.to.clone(), - from: tx.from.clone(), - hash: tx.hash.clone(), - index: tx.index.into(), - }), - tokens: pool_added.coins.into(), - contracts: vec![pool_added.pool], - static_att: vec![ - Attribute { - name: "pool_type".into(), - value: "twocrypto".into(), - change: ChangeType::Creation.into(), - }, - Attribute { - name: "name".into(), - value: pool_added.name.into(), - change: ChangeType::Creation.into(), - }, - Attribute { - name: "factory_name".into(), - value: "twocrypto_factory".into(), - change: ChangeType::Creation.into(), - }, - Attribute { - name: "factory".into(), - value: address_to_bytes_with_0x(&TWOCRYPTO_FACTORY), - change: ChangeType::Creation.into(), - }, - ], - change: ChangeType::Creation.into(), - protocol_type: Some(ProtocolType { - name: "curve_pool".into(), - financial_type: FinancialType::Swap.into(), - attribute_schema: Vec::new(), - implementation_type: ImplementationType::Vm.into(), - }), - }) + let id = hex::encode(&pool_added.pool); + Some(( + ProtocolComponent { + id: id.clone(), + tx: Some(Transaction { + to: tx.to.clone(), + from: tx.from.clone(), + hash: tx.hash.clone(), + index: tx.index.into(), + }), + tokens: pool_added.coins.into(), + contracts: vec![pool_added.pool, TWOCRYPTO_FACTORY.into()], + static_att: vec![ + Attribute { + name: "pool_type".into(), + value: "twocrypto".into(), + change: ChangeType::Creation.into(), + }, + Attribute { + name: "name".into(), + value: pool_added.name.into(), + change: ChangeType::Creation.into(), + }, + Attribute { + name: "factory_name".into(), + value: "twocrypto_factory".into(), + change: ChangeType::Creation.into(), + }, + Attribute { + name: "factory".into(), + value: address_to_bytes_with_0x(&TWOCRYPTO_FACTORY), + change: ChangeType::Creation.into(), + }, + ], + change: ChangeType::Creation.into(), + protocol_type: Some(ProtocolType { + name: "curve_pool".into(), + financial_type: FinancialType::Swap.into(), + attribute_schema: Vec::new(), + implementation_type: ImplementationType::Vm.into(), + }), + }, + vec![EntityChanges { + component_id: format!("0x{}", id), + attributes: vec![ + Attribute { + name: "stateless_contract_addr_0".into(), + // Call views_implementation() on TWOCRYPTO_FACTORY + value: format!( + "call:0x{}:views_implementation()", + hex::encode(TWOCRYPTO_FACTORY) + ) + .into(), + change: ChangeType::Creation.into(), + }, + Attribute { + name: "stateless_contract_addr_1".into(), + // Call math_implementation() on TWOCRYPTO_FACTORY + value: format!( + "call:0x{}:math_implementation()", + hex::encode(TWOCRYPTO_FACTORY) + ) + .into(), + change: ChangeType::Creation.into(), + }, + ], + }], + )) } else { None } @@ -769,3 +954,28 @@ fn get_token_from_pool(pool: &Vec) -> Vec { }) .unwrap() } + +fn extract_eip1167_target_from_code(code: &[u8]) -> [u8; 20] { + let mut target = [0u8; 20]; + + // Depending on the Vyper version, they use different implementations of EIP1167. + // We use the first 10 bytes of the code to make a clear distinction. + match code.get(0..10) { + Some([54, 61, 61, 55, 61, 61, 61, 54, 61, 115]) => target.copy_from_slice(&code[10..30]), + Some([54, 96, 0, 96, 0, 55, 97, 16, 0, 96]) => target.copy_from_slice(&code[15..35]), + _ => target = [1u8; 20], // Placeholder for unexpected values + } + + target +} + +fn extract_proxy_impl(call: &Call, tx: &TransactionTrace, index: usize) -> Option<[u8; 20]> { + let code_change = tx + .calls + .iter() + .filter(|c| !c.code_changes.is_empty() && c.parent_index == call.index) + .nth(index)? + .code_changes + .first()?; + Some(extract_eip1167_target_from_code(&code_change.new_code)) +} diff --git a/substreams/ethereum-curve/src/pools.rs b/substreams/ethereum-curve/src/pools.rs index 81d585a..a8ca576 100644 --- a/substreams/ethereum-curve/src/pools.rs +++ b/substreams/ethereum-curve/src/pools.rs @@ -9,8 +9,11 @@ const PARAMS_SEPERATOR: &str = ","; #[derive(Debug, Deserialize, PartialEq)] struct PoolQueryParams { address: String, + contracts: Option>, tx_hash: String, tokens: Vec, + static_attribute_keys: Option>, + static_attribute_vals: Option>, attribute_keys: Option>, attribute_vals: Option>, } @@ -31,7 +34,7 @@ struct PoolQueryParams { pub fn emit_specific_pools( params: &str, tx: &TransactionTrace, -) -> Result> { +) -> Result)>> { let pools = parse_params(params)?; create_component(tx, pools) } @@ -39,49 +42,81 @@ pub fn emit_specific_pools( fn create_component( tx: &TransactionTrace, pools: HashMap, -) -> Result> { +) -> Result)>> { let encoded_hash = hex::encode(tx.hash.clone()); if let Some(pool) = pools.get(&encoded_hash) { - Ok(Some(ProtocolComponent { - id: pool.address.clone(), - tx: Some(Transaction { - to: tx.to.clone(), - from: tx.from.clone(), - hash: tx.hash.clone(), - index: tx.index.into(), - }), - tokens: pool - .tokens + Ok(Some(( + ProtocolComponent { + id: pool.address.clone(), + tx: Some(Transaction { + to: tx.to.clone(), + from: tx.from.clone(), + hash: tx.hash.clone(), + index: tx.index.into(), + }), + tokens: pool + .tokens + .clone() + .into_iter() + .map(|token| Result::Ok(hex::decode(token)?)) + .collect::>>() + .with_context(|| "Token addresses were not formatted properly")?, + static_att: zip( + pool.static_attribute_keys + .clone() + .unwrap_or(vec![]), + pool.static_attribute_vals + .clone() + .unwrap_or(vec![]), + ) .clone() - .into_iter() - .map(|token| Result::Ok(hex::decode(token)?)) - .collect::>>() - .with_context(|| "Token addresses were not formatted properly")?, - static_att: zip( - pool.attribute_keys + .map(|(key, value)| Attribute { + name: key, + value: value.into(), + change: ChangeType::Creation.into(), + }) + .collect::>(), + contracts: pool + .contracts .clone() - .unwrap_or(vec![]), - pool.attribute_vals - .clone() - .unwrap_or(vec![]), - ) - .clone() - .map(|(key, value)| Attribute { - name: key, - value: value.into(), + .unwrap_or_default() + .into_iter() + .map(|contract| { + hex::decode(contract) + .with_context(|| "Pool contracts was not formatted properly") + }) + .chain(std::iter::once( + hex::decode(&pool.address) + .with_context(|| "Pool address was not formatted properly"), + )) + .collect::>>>()?, change: ChangeType::Creation.into(), - }) - .collect::>(), - contracts: vec![hex::decode(pool.address.clone()) - .with_context(|| "Pool address was not formatted properly")?], - change: ChangeType::Creation.into(), - protocol_type: Some(ProtocolType { - name: "curve_pool".into(), - financial_type: FinancialType::Swap.into(), - attribute_schema: Vec::new(), - implementation_type: ImplementationType::Vm.into(), - }), - })) + protocol_type: Some(ProtocolType { + name: "curve_pool".into(), + financial_type: FinancialType::Swap.into(), + attribute_schema: Vec::new(), + implementation_type: ImplementationType::Vm.into(), + }), + }, + vec![EntityChanges { + component_id: format!("0x{}", pool.address.clone()), + attributes: zip( + pool.attribute_keys + .clone() + .unwrap_or(vec![]), + pool.attribute_vals + .clone() + .unwrap_or(vec![]), + ) + .clone() + .map(|(key, value)| Attribute { + name: key, + value: value.into(), + change: ChangeType::Creation.into(), + }) + .collect::>(), + }], + ))) } else { Ok(None) } @@ -113,6 +148,7 @@ mod tests { "0xb71a66c1d93c525a2dd19a8db0da19e65be04f36e733af7f03e3c9dff41aa16a".to_string(), PoolQueryParams { address: "0x5F890841f657d90E081bAbdB532A05996Af79Fe6".to_string(), + contracts: None, tx_hash: "0xb71a66c1d93c525a2dd19a8db0da19e65be04f36e733af7f03e3c9dff41aa16a" .to_string(), tokens: vec![ @@ -120,6 +156,8 @@ mod tests { "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48".to_string(), "0xdac17f958d2ee523a2206206994597c13d831ec7".to_string(), ], + static_attribute_keys: None, + static_attribute_vals: None, attribute_keys: Some(vec!["key1".to_string()]), attribute_vals: Some(vec!["val1".to_string()]), }, diff --git a/substreams/ethereum-curve/substreams.yaml b/substreams/ethereum-curve/substreams.yaml index dc1ae65..34ff1b8 100644 --- a/substreams/ethereum-curve/substreams.yaml +++ b/substreams/ethereum-curve/substreams.yaml @@ -20,12 +20,12 @@ binaries: modules: - name: map_components kind: map - initialBlock: 9906598 + initialBlock: 9906598 # Creation of first Curve pool 0xa5407eae9ba41422680e2e00537571bcc53efbfd inputs: - params: string - source: sf.ethereum.type.v2.Block output: - type: proto:tycho.evm.v1.BlockTransactionProtocolComponents + type: proto:tycho.evm.v1.BlockChanges - name: store_component_tokens kind: store @@ -35,9 +35,17 @@ modules: inputs: - map: map_components + - name: store_non_component_accounts + kind: store + initialBlock: 9906598 + updatePolicy: set + valueType: int64 + inputs: + - map: map_components + - name: map_relative_balances kind: map - initialBlock: 9906598 # An arbitrary block that should change based on your requirements + initialBlock: 9906598 inputs: - source: sf.ethereum.type.v2.Block - store: store_component_tokens @@ -60,10 +68,11 @@ modules: - map: map_components - map: map_relative_balances - store: store_component_tokens + - store: store_non_component_accounts - store: store_balances mode: deltas # This is the key property that simplifies `BalanceChange` handling output: type: proto:tycho.evm.v1.BlockChanges params: - map_components: "address=bebc44782c7db0a1a60cb6fe97d0b483032ff1c7&tx_hash=20793bbf260912aae189d5d261ff003c9b9166da8191d8f9d63ff1c7722f3ac6&tokens[]=6b175474e89094c44da98b954eedeac495271d0f&tokens[]=a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48&tokens[]=dac17f958d2ee523a2206206994597c13d831ec7&attribute_keys[]=name&attribute_vals[]=3pool&attribute_keys[]=factory_name&attribute_vals[]=NA&attribute_keys[]=factory&attribute_vals[]=0x0000000000000000000000000000000000000000,address=dc24316b9ae028f1497c275eb9192a3ea0f67022&tx_hash=fac67ecbd423a5b915deff06045ec9343568edaec34ae95c43d35f2c018afdaa&tokens[]=eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee&tokens[]=ae7ab96520de3a18e5e111b5eaab095312d7fe84&attribute_keys[]=name&attribute_vals[]=steth&attribute_keys[]=factory_name&attribute_vals[]=NA&attribute_keys[]=factory&attribute_vals[]=0x0000000000000000000000000000000000000000,address=d51a44d3fae010294c616388b506acda1bfaae46&tx_hash=dafb6385ed988ce8aacecfe1d97b38ea5e60b1ebce74d2423f71ddd621680138&tokens[]=dac17f958d2ee523a2206206994597c13d831ec7&tokens[]=2260fac5e5542a773aa44fbcfedf7c193bc2c599&tokens[]=c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2&attribute_keys[]=name&attribute_vals[]=tricrypto2&attribute_keys[]=factory_name&attribute_vals[]=NA&attribute_keys[]=factory&attribute_vals[]=0x0000000000000000000000000000000000000000,address=a5407eae9ba41422680e2e00537571bcc53efbfd&tx_hash=51aca4a03a395de8855fa2ca59b7febe520c2a223e69c502066162f7c1a95ec2&tokens[]=6b175474e89094c44da98b954eedeac495271d0f&tokens[]=a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48&tokens[]=dac17f958d2ee523a2206206994597c13d831ec7&tokens[]=57ab1ec28d129707052df4df418d58a2d46d5f51&attribute_keys[]=name&attribute_vals[]=susd&attribute_keys[]=factory_name&attribute_vals[]=NA&attribute_keys[]=factory&attribute_vals[]=0x0000000000000000000000000000000000000000,address=dcef968d416a41cdac0ed8702fac8128a64241a2&tx_hash=1f4254004ce9e19d4eb742ee5a69d30f29085902d976f73e97c44150225ef775&tokens[]=853d955acef822db058eb8505911ed77f175b99e&tokens[]=a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48&attribute_keys[]=name&attribute_vals[]=fraxusdc&attribute_keys[]=factory_name&attribute_vals[]=NA&attribute_keys[]=factory&attribute_vals[]=0x0000000000000000000000000000000000000000" + map_components: "address=bebc44782c7db0a1a60cb6fe97d0b483032ff1c7&tx_hash=20793bbf260912aae189d5d261ff003c9b9166da8191d8f9d63ff1c7722f3ac6&tokens[]=6b175474e89094c44da98b954eedeac495271d0f&tokens[]=a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48&tokens[]=dac17f958d2ee523a2206206994597c13d831ec7&&static_attribute_keys[]=name&static_attribute_vals[]=3pool&static_attribute_keys[]=factory_name&static_attribute_vals[]=NA&static_attribute_keys[]=factory&static_attribute_vals[]=0x0000000000000000000000000000000000000000,address=dc24316b9ae028f1497c275eb9192a3ea0f67022&tx_hash=fac67ecbd423a5b915deff06045ec9343568edaec34ae95c43d35f2c018afdaa&tokens[]=eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee&tokens[]=ae7ab96520de3a18e5e111b5eaab095312d7fe84&&static_attribute_keys[]=name&static_attribute_vals[]=steth&static_attribute_keys[]=factory_name&static_attribute_vals[]=NA&static_attribute_keys[]=factory&static_attribute_vals[]=0x0000000000000000000000000000000000000000,address=d51a44d3fae010294c616388b506acda1bfaae46&contracts[]=c4ad29ba4b3c580e6d59105fff484999997675ff&contracts[]=40745803c2faa8e8402e2ae935933d07ca8f355c&tx_hash=dafb6385ed988ce8aacecfe1d97b38ea5e60b1ebce74d2423f71ddd621680138&tokens[]=dac17f958d2ee523a2206206994597c13d831ec7&tokens[]=2260fac5e5542a773aa44fbcfedf7c193bc2c599&tokens[]=c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2&attribute_keys[]=stateless_contract_addr_0&attribute_vals[]=0x8F68f4810CcE3194B6cB6F3d50fa58c2c9bDD1d5&static_attribute_keys[]=name&static_attribute_vals[]=tricrypto2&static_attribute_keys[]=factory_name&static_attribute_vals[]=NA&static_attribute_keys[]=factory&static_attribute_vals[]=0x0000000000000000000000000000000000000000,address=a5407eae9ba41422680e2e00537571bcc53efbfd&tx_hash=51aca4a03a395de8855fa2ca59b7febe520c2a223e69c502066162f7c1a95ec2&tokens[]=6b175474e89094c44da98b954eedeac495271d0f&tokens[]=a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48&tokens[]=dac17f958d2ee523a2206206994597c13d831ec7&tokens[]=57ab1ec28d129707052df4df418d58a2d46d5f51&&static_attribute_keys[]=name&static_attribute_vals[]=susd&static_attribute_keys[]=factory_name&static_attribute_vals[]=NA&static_attribute_keys[]=factory&static_attribute_vals[]=0x0000000000000000000000000000000000000000,address=dcef968d416a41cdac0ed8702fac8128a64241a2&tx_hash=1f4254004ce9e19d4eb742ee5a69d30f29085902d976f73e97c44150225ef775&tokens[]=853d955acef822db058eb8505911ed77f175b99e&tokens[]=a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48&&static_attribute_keys[]=name&static_attribute_vals[]=fraxusdc&static_attribute_keys[]=factory_name&static_attribute_vals[]=NA&static_attribute_keys[]=factory&static_attribute_vals[]=0x0000000000000000000000000000000000000000" diff --git a/testing/src/runner/models.py b/testing/src/runner/models.py index 6aeaae1..9c51260 100644 --- a/testing/src/runner/models.py +++ b/testing/src/runner/models.py @@ -66,7 +66,7 @@ class ProtocolComponentExpectation(BaseModel): colorize_diff(diff) if colorize_output else "\n".join(diff) ) differences.append( - f"Field '{field_name}' mismatch:\n{highlighted_diff}" + f"Field '{field_name}' mismatch for {self.id}:\n{highlighted_diff}" ) if not differences: return None