From bc2cd6bab255eea16fb673c599743c6b859fbe34 Mon Sep 17 00:00:00 2001 From: Florian Pellissier <111426680+flopell@users.noreply.github.com> Date: Fri, 16 Aug 2024 15:20:55 +0200 Subject: [PATCH 1/6] refactor(testing): miscellaneous improvements and bugfixes Includes bugfix on `tycho-indexer-client` and `protosim_py`, a script to simplify setting up testing python env and cli improvements. Also add support for building runtime for SwapAdapters with many args that was bugged before. --- evm/scripts/buildRuntime.sh | 11 +++++++++-- testing/.env.default | 2 -- testing/requirements.txt | 4 ++-- testing/setup_env.sh | 35 +++++++++++++++++++++++++++++++++++ testing/src/runner/cli.py | 7 +++++-- testing/src/runner/models.py | 2 -- testing/src/runner/runner.py | 4 ++-- testing/src/runner/tycho.py | 7 +++++-- testing/src/runner/utils.py | 4 ++-- 9 files changed, 60 insertions(+), 16 deletions(-) create mode 100755 testing/setup_env.sh diff --git a/evm/scripts/buildRuntime.sh b/evm/scripts/buildRuntime.sh index 27089a6..49c12ac 100755 --- a/evm/scripts/buildRuntime.sh +++ b/evm/scripts/buildRuntime.sh @@ -31,8 +31,15 @@ echo "CONSTRUCTOR_ARGUMENTS: $CONSTRUCTOR_ARGUMENTS" # Perform operations if CONSTRUCTOR_SIGNATURE and CONSTRUCTOR_ARGUMENTS are set if [[ ! -z "$CONSTRUCTOR_SIGNATURE" && ! -z "$CONSTRUCTOR_ARGUMENTS" ]]; then - # Do some operations here - export __PROPELLER_DEPLOY_ARGS=$(cast abi-encode $CONSTRUCTOR_SIGNATURE $CONSTRUCTOR_ARGUMENTS) + # Split the CONSTRUCTOR_ARGUMENTS by commas into an array + IFS=',' read -r -a ARG_ARRAY <<< "$CONSTRUCTOR_ARGUMENTS" + + # Create the cast abi-encode command with the arguments + ENCODED_ARGS=$(cast abi-encode "$CONSTRUCTOR_SIGNATURE" "${ARG_ARRAY[@]}") + + # Export the encoded arguments + export __PROPELLER_DEPLOY_ARGS=$ENCODED_ARGS + echo "$ENCODED_ARGS" fi export __PROPELLER_CONTRACT="$CONTRACT_NAME.sol:$CONTRACT_NAME" diff --git a/testing/.env.default b/testing/.env.default index a192fe5..55deb9d 100644 --- a/testing/.env.default +++ b/testing/.env.default @@ -1,5 +1,3 @@ -export SUBSTREAMS_PACKAGE=ethereum-curve export RPC_URL=https://mainnet.infura.io/v3/your-infura-key -export DATABASE_URL: "postgres://postgres:mypassword@db:5432/tycho_indexer_0" export SUBSTREAMS_API_TOKEN="changeme" export DOMAIN_OWNER="AWSAccountId" \ No newline at end of file diff --git a/testing/requirements.txt b/testing/requirements.txt index aef7986..74df75b 100644 --- a/testing/requirements.txt +++ b/testing/requirements.txt @@ -2,5 +2,5 @@ psycopg2==2.9.9 PyYAML==6.0.1 Requests==2.32.2 web3==5.31.3 -tycho-indexer-client>=0.7.0 -protosim_py>=0.5.0 +tycho-indexer-client>=0.7.2 +protosim_py>=0.6.3 diff --git a/testing/setup_env.sh b/testing/setup_env.sh new file mode 100755 index 0000000..bd80b67 --- /dev/null +++ b/testing/setup_env.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +# Variables +ENV_NAME="propeller-protocol-lib-testing" +PYTHON_VERSION="3.9" +REQUIREMENTS_FILE="requirements.txt" +PRE_BUILD_SCRIPT="pre_build.sh" + +# Allow to run either from root or from inside testing folder. +if [ -f "./$REQUIREMENTS_FILE" ]; then + # If the requirements file is found in the current directory, do nothing + SCRIPT_DIR="." +elif [ -f "testing/$REQUIREMENTS_FILE" ]; then + # If the requirements file is found in testing/, adjust the paths + SCRIPT_DIR="testing" +else + echo "Error: Script must be run from the propeller-protocol-lib or propeller-protocol-lib/testing directory." + exit 1 +fi + +# Create conda environment +echo "Creating conda environment ${ENV_NAME} with Python ${PYTHON_VERSION}..." +conda create --name $ENV_NAME python=$PYTHON_VERSION -y + +# Activate the environment +echo "Activating the environment..." +source activate $ENV_NAME + +# Install the requirements +echo "Installing the requirements from ${SCRIPT_DIR}/${REQUIREMENTS_FILE}..." +./${SCRIPT_DIR}/${PRE_BUILD_SCRIPT} +pip install -r ${SCRIPT_DIR}/${REQUIREMENTS_FILE} +conda activate $ENV_NAME + +echo "Setup complete." \ No newline at end of file diff --git a/testing/src/runner/cli.py b/testing/src/runner/cli.py index 1fe82b6..66f08d0 100644 --- a/testing/src/runner/cli.py +++ b/testing/src/runner/cli.py @@ -8,10 +8,13 @@ def main() -> None: ) parser.add_argument("--package", type=str, help="Name of the package to test.") parser.add_argument( - "--tycho-logs", action="store_true", help="Flag to activate logs from Tycho." + "--tycho-logs", action="store_true", help="Enable Tycho logs." ) parser.add_argument( - "--db-url", type=str, help="Postgres database URL for the Tycho indexer." + "--db-url", + default="postgres://postgres:mypassword@localhost:5431/tycho_indexer_0", + type=str, + help="Postgres database URL for the Tycho indexer. Default: postgres://postgres:mypassword@localhost:5431/tycho_indexer_0", ) parser.add_argument( "--vm-traces", action="store_true", help="Enable tracing during vm simulations." diff --git a/testing/src/runner/models.py b/testing/src/runner/models.py index 6aeaae1..faa8ada 100644 --- a/testing/src/runner/models.py +++ b/testing/src/runner/models.py @@ -4,8 +4,6 @@ from hexbytes import HexBytes from pydantic import BaseModel, Field, validator from typing import List, Dict, Optional -from tycho_client.dto import ProtocolComponent - class ProtocolComponentExpectation(BaseModel): """Represents a ProtocolComponent with its main attributes.""" diff --git a/testing/src/runner/runner.py b/testing/src/runner/runner.py index 84eb340..12a15cf 100644 --- a/testing/src/runner/runner.py +++ b/testing/src/runner/runner.py @@ -14,7 +14,7 @@ from protosim_py.evm.decoders import ThirdPartyPoolTychoDecoder from protosim_py.evm.storage import TychoDBSingleton from protosim_py.models import EVMBlock from pydantic import BaseModel -from tycho_client.dto import ( +from tycho_indexer_client.dto import ( Chain, ProtocolComponentsParams, ProtocolStateParams, @@ -26,7 +26,7 @@ from tycho_client.dto import ( Snapshot, ContractId, ) -from tycho_client.rpc_client import TychoRPCClient +from tycho_indexer_client.rpc_client import TychoRPCClient from models import ( IntegrationTestsConfig, diff --git a/testing/src/runner/tycho.py b/testing/src/runner/tycho.py index 52512c4..a6cd2a1 100644 --- a/testing/src/runner/tycho.py +++ b/testing/src/runner/tycho.py @@ -27,12 +27,15 @@ def find_binary_file(file_name): # Check each location for location in locations: - potential_path = location + "/" + file_name + potential_path = os.path.join(location, file_name) if os.path.exists(potential_path): return potential_path # If binary is not found in the usual locations, return None - raise RuntimeError("Unable to locate tycho-indexer binary") + searched_paths = "\n".join(locations) + raise RuntimeError( + f"Unable to locate {file_name} binary. Searched paths:\n{searched_paths}" + ) binary_path = find_binary_file("tycho-indexer") diff --git a/testing/src/runner/utils.py b/testing/src/runner/utils.py index dc98bd6..09bba3c 100644 --- a/testing/src/runner/utils.py +++ b/testing/src/runner/utils.py @@ -3,7 +3,7 @@ from typing import Union from eth_utils import to_checksum_address from protosim_py.models import EthereumToken -from tycho_client.dto import ( +from tycho_indexer_client.dto import ( ResponseProtocolState, ProtocolComponent, ResponseAccount, @@ -13,7 +13,7 @@ from tycho_client.dto import ( TokensParams, PaginationParams, ) -from tycho_client.rpc_client import TychoRPCClient +from tycho_indexer_client.rpc_client import TychoRPCClient log = getLogger(__name__) From 37f1fbfe0413150b8950b9d24244b061c028df99 Mon Sep 17 00:00:00 2001 From: Florian Pellissier <111426680+flopell@users.noreply.github.com> Date: Fri, 16 Aug 2024 15:22:27 +0200 Subject: [PATCH 2/6] docs(substreams): miscellaneous docs improvements and update --- docs/indexing/substreams-integration.md | 82 ++++---------- docs/indexing/vm-integration/README.md | 22 ++-- proto/tycho/evm/v1/common.proto | 4 +- substreams/Readme.md | 3 + .../integration_test.tycho.yaml | 23 +++- testing/README.md | 106 +++++++++++++----- 6 files changed, 137 insertions(+), 103 deletions(-) diff --git a/docs/indexing/substreams-integration.md b/docs/indexing/substreams-integration.md index 5572939..f4f71a4 100644 --- a/docs/indexing/substreams-integration.md +++ b/docs/indexing/substreams-integration.md @@ -18,29 +18,15 @@ So basically when processing a block we need to emit the block itself, all trans **The data model that encodes changes, transaction and blocks in messages, can be found** [**here**](https://github.com/propeller-heads/propeller-protocol-lib/tree/main/proto/tycho/evm/v1)**.** -#### Common Models +#### Models -The following models are shared for both vm and native integrations. +The models below are used for communication between Substreams and our indexer, as well as between Substreams modules. -{% @github-files/github-code-block url="https://github.com/propeller-heads/propeller-protocol-lib/blob/main/proto/tycho/evm/v1/common.proto" %} +Our indexer expects to receive a `BlockChanges` output from your Substreams package. -#### VM Specific Models +{% @github-files/github-code-block url="https://github.com/propeller-heads/propeller-protocol-lib/blob/main/proto/tycho/evm/v1/" %} -The models shown below are specific to vm integrations: - -{% @github-files/github-code-block url="https://github.com/propeller-heads/propeller-protocol-lib/blob/main/proto/tycho/evm/v1/vm.proto" %} - -Please be aware that changes need to be aggregated on the transaction level, it is considered an error to emit `BlockContractChanges` with duplicated transactions present in the `changes` attributes. - -All attributes are expected to be set in the final message unless the docs (in the comments) indicate otherwise. - -#### Native Integration Models - -The models below are very similar to the vm integration models but have a few modifications necessary to support native integrations. - -{% @github-files/github-code-block url="https://github.com/propeller-heads/propeller-protocol-lib/blob/main/proto/tycho/evm/v1/entity.proto" %} - -Once again changes must be aggregated on a transaction level, emitting these models with duplicated transaction as the final output would be considered an error. +Please be aware that changes need to be aggregated on the transaction level, it is considered an error to emit `BlockChanges` with duplicated transactions present in the `changes` attributes. #### Integer Byte encoding @@ -50,17 +36,15 @@ Many of the types above are variable length bytes. This allows for flexibility a **Strings**: If you need to store strings, please use utf-8 encoding to store them as bytes. -**Attributes:** the value encoding for attributes in the native implementation messages is variable. It depends on the use case. Since the attributes are highly dynamic they are only used by the corresponding logic components, so the encoding can be tailored to the logic implementation: E.g. since Rust uses little endian one may choose to use little endian encoding for integers if the native logic module is written in Rust. - - +**Attributes:** the value encoding for attributes is variable. It depends on the use case. Since the attributes are highly dynamic they are only used by the corresponding logic components, so the encoding can be tailored to the logic implementation: E.g. since Rust uses little endian one may choose to use little endian encoding for integers if the native logic module is written in Rust. ### Changes of interest PropellerHeads integration should at least communicate the following changes: -* Any changes to the protocol state, for VM integrations that usually means contract storage changes of all contracts whose state may be accessed during a swap operation. -* Any newly added protocol component such as a pool, pair, market, etc. Basically anything that signifies that a new operation can be executed now using the protocol. -* ERC20 Balances, whenever the balances of one contracts involved with the protocol change, this change should be communicated in terms of absolute balances. +- Any changes to the protocol state, for VM integrations that usually means contract storage changes of all contracts whose state may be accessed during a swap operation. +- Any newly added protocol component such as a pool, pair, market, etc. Basically anything that signifies that a new operation can be executed now using the protocol. +- ERC20 Balances, whenever the balances of one contracts involved with the protocol change, this change should be communicated in terms of absolute balances. In the next section we will show a few common techniques that can be leveraged to quickly implement an integration. @@ -70,9 +54,9 @@ Before starting, it is important to be aware of the protocol we are aiming to in It is especially important to know: -* Which contracts are involved in the protocol and what functions do they serve. How do they affect the behaviour of the component being integrated? -* What conditions (e.g. oracle update) or what kind of method calls can lead to a relevant state change on the protocol, which ultimately changes the protocols behaviour if observed externally. -* Are there components added or removed, and how are they added. Most protocols use either a factory contract, which can be used to deploy new components, or they use a method call that provisiona a new component within the overall system. +- Which contracts are involved in the protocol and what functions do they serve. How do they affect the behaviour of the component being integrated? +- What conditions (e.g. oracle update) or what kind of method calls can lead to a relevant state change on the protocol, which ultimately changes the protocols behaviour if observed externally. +- Are there components added or removed, and how are they added. Most protocols use either a factory contract, which can be used to deploy new components, or they use a method call that provisiona a new component within the overall system. Once the workings of the protocol are clear the implementation can start. @@ -94,44 +78,19 @@ You are ready to start coding. Please refer to the substreams documentation for Usually the first step consists in detecting the creation of new components and store their contract addresses in a store, so they can be properly tracked further downstream. -Later we'll have to emit balance and state changes based on the set of currently tracked components. +Later we'll have to emit balance and state changes based on the set of currently tracked components. {% hint style="info" %} -Note that emitting state changes of components that have not been previously announced is considered an error. +Note that emitting state changes of components that have not been previously announced is considered an error. {% endhint %} Newly created components are detected by mapping over the `sf.ethereum.type.v2.Block model`. The output message should usually contain as much information about the component available at that time as well as the transaction that created the protocol component. -We have found that using the final model prefilled with only component changes is usually good enough since it holds all the information that will be necessary at the end. +We have found that using the final model (`BlockChanges`) prefilled with only component changes is usually good enough since it holds all the information that will be necessary at the end. -For VM Integrations the final model is `BlockContractChanges`: - -```protobuf -// A set of changes aggregated by transaction. -message TransactionContractChanges { - // The transaction instance that results in the changes. - Transaction tx = 1; - // Contains the changes induced by the above transaction, aggregated on a per-contract basis. - // Must include changes to every contract that is tracked by all ProtocolComponents. - repeated ContractChange contract_changes = 2; - // An array of any component changes. - repeated ProtocolComponent component_changes = 3; - // An array of balance changes to components. - repeated BalanceChange balance_changes = 4; -} - -// A set of transaction changes within a single block. -message BlockContractChanges { - // The block for which these changes are collectively computed. - Block block = 1; - // The set of transaction changes observed in the specified block. - repeated TransactionContractChanges changes = 2; -} -``` - -Note that a single transaction may emit multiple newly created components. In this case it is expected that the `TransactionContractChanges.component_changes`, contains multiple `ProtocolComponents`. +Note that a single transaction may emit multiple newly created components. In this case it is expected that the `TransactionChanges.component_changes`, contains multiple `ProtocolComponents`. Once emitted, the protocol components should be stored in a Store, since we will later have to use this store to decide whether a contract is interesting to us or not. @@ -143,11 +102,12 @@ This means the relative values have to be aggregated by component, to arrive at Since this is challenging the following approach is recommended: -* Use a handler to process a block and emit the `BalanceDeltas` struct. Make sure to sort the balance deltas by `component_id, token_address` -* Aggregate the BalanceDelta messages using a `BigIntAddStore`. -* In a final handler, use as inputs: A `DeltaStore` input from step 2 and the `BalanceDeltas` from step 1. You can now zip the deltas from the store with the balance deltas from step 1. The store deltas contains the aggregated (absolute) balance at each version and the balance deltas contain the corresponding transaction. +- Use a handler to process a block and emit the `BlockBalanceDeltas` struct. Make sure to sort the balance deltas by `component_id, token_address` +- Aggregate the BalanceDelta messages using a `BigIntAddStore`. +- In a final handler, use as inputs: A `DeltaStore` input from step 2 and the `BlockBalanceDeltas` from step 1. You can now zip the deltas from the store with the balance deltas from step 1. The store deltas contains the aggregated (absolute) balance at each version and the balance deltas contain the corresponding transaction. + +Our Substreams SDK provide the `extract_balance_deltas_from_tx` function that extracts all relevant `BalanceDelta` from ERC20 `Transfer` events in a given transaction (see Curve implementation). #### Tracking State Changes To track contract changes, you can simply use the `extract_contract_changes` function (see balancer implementation). This function will extract all relevant contract storage changes given the full block model and a store that flags contract addresses as relevant. - diff --git a/docs/indexing/vm-integration/README.md b/docs/indexing/vm-integration/README.md index 95e0359..7f2f94a 100644 --- a/docs/indexing/vm-integration/README.md +++ b/docs/indexing/vm-integration/README.md @@ -4,7 +4,7 @@ Our indexing integrations use the Substreams library to transform raw blockchain ## Example -We have integrated the **Ambient** protocol as a reference, see `/substreams/ethereum-ambient` for more information. +We have integrated the **Balancer** protocol as a reference, see `/substreams/ethereum-balancer` for more information. ## Step by step @@ -51,7 +51,6 @@ If you are unfamiliar with ProtoBuf at all, you can start with the [official doc First get familiar with the raw ProtoBuf definitions provided by us: - [common.proto](../../../proto/tycho/evm/v1/common.proto) - Common types used by all integration types -- [vm.proto](../../../proto/tycho/evm/v1/vm.proto) - Types specific to the VM integration You can also create your own intermediate ProtoBufs. These files should reside in your own substreams package, e.g. `./substreams/ethereum-template/proto/custom-messages.proto`. You have to link these files in the `substreams.yaml` file, see the [manifest docs](https://substreams.streamingfast.io/developers-guide/creating-your-manifest) for more information or you can look at the official substreams example integration of [UniswapV2](https://github.com/messari/substreams/blob/master/uniswap-v2/substreams.yaml#L20-L22). @@ -63,7 +62,7 @@ The goal of the rust module is to implement the logic that will transform the ra *This is the actual integration code that you will be writing!* -The module is a Rust library that is compiled into a SPKG (`.spkg`) file using the Substreams CLI and then loaded by the Substreams server. It is defined by the `lib.rs` file (see the [Ambient reference example](../../../substreams/ethereum-ambient/src/lib.rs)). +The module is a Rust library that is compiled into a SPKG (`.spkg`) file using the Substreams CLI and then loaded by the Substreams server. It is defined by the `lib.rs` file (see the [Balancer reference example](../../../substreams/ethereum-balancer/src/lib.rs)). Read our [Substreams README.md](../../../substreams/README.md) for more information on how to write the Rust module. @@ -74,26 +73,27 @@ Read our [Substreams README.md](../../../substreams/README.md) for more informat ```bash cp -r ./substreams/ethereum-template ./substreams/[CHAIN]-[PROTOCOL_SYSTEM] ``` -1. Implement the logic in the Rust module `lib.rs`. The main function to implement is the `map_changes` function, which is called for every block. +1. Implement the logic in the Rust module `lib.rs`. The main function to implement is the `map_protocol_changes` function, which is called for every block. ```rust #[substreams::handlers::map] - fn map_changes( + fn map_protocol_changes( block: eth::v2::Block, - ) -> Result {} + ) -> Result {} ``` - The `map_changes` function takes a raw block as input and returns a `BlockContractChanges` struct, which is derived from the `BlockContractChanges` protobuf message in [vm.proto](../../../proto/tycho/evm/v1/vm.proto). + The `map_protocol_changes` function takes a raw block as input and returns a `BlockChanges` struct, which is derived from the `BlockChanges` protobuf message in [vm.proto](../../../proto/tycho/evm/v1/vm.proto). -1. The `BlockContractChanges` is a list of `TransactionContractChanges`, which includes these main fields: +1. The `BlockChanges` is a list of `TransactionChanges`, which includes these main fields: - list of `ContractChange` - All storage slots that have changed in the transaction for every contract tracked by any ProtocolComponent + - list of `EntityChanges` - All the attribute changes in the transaction - list of `ProtocolComponent` - All the protocol component changes in the transaction - - list of `BalanceChange` - All the contract component changes in the transaction + - list of `BalanceChange` - All the token balances changes in the transaction - See the [Ambient reference example](../../../substreams/ethereum-ambient/src/lib.rs) for more information. + See the [Balancer reference example](../../../substreams/ethereum-balancer/src/lib.rs) for more information. 1. If you are more advanced with Substreams, you can define more steps than a single "map" step, including defining your own protobuf files. Add these protobuf files in your `pb` folder and update the manifest accordingly. This allows for better parallelization of the indexing process. See the official documentation of [modules](https://substreams.streamingfast.io/concepts-and-fundamentals/modules#modules-basics-overview). ### Testing -Read the [Substreams testing docs](../../../substreams/README.md#testing-your-implementation) for more information on how to test your integration. +Read the [Substreams testing docs](../../../substreams/README.md#test-your-implementation) for more information on how to test your integration. diff --git a/proto/tycho/evm/v1/common.proto b/proto/tycho/evm/v1/common.proto index 8da97e7..cfead81 100644 --- a/proto/tycho/evm/v1/common.proto +++ b/proto/tycho/evm/v1/common.proto @@ -83,7 +83,8 @@ message ProtocolComponent { // Addresses of the contracts used by the component. // Usually it is a single contract, but some protocols use multiple contracts. repeated bytes contracts = 3; - // Attributes of the component. Used mainly be the native integration. + // Static attributes of the component. + // These attributes MUST be immutable. If it can ever change, it should be given as an EntityChanges for this component id. // The inner ChangeType of the attribute has to match the ChangeType of the ProtocolComponent. repeated Attribute static_att = 4; // Type of change the component underwent. @@ -160,6 +161,7 @@ message TransactionChanges { } // A set of transaction changes within a single block. +// This message must be the output of your substreams module. message BlockChanges { // The block for which these changes are collectively computed. Block block = 1; diff --git a/substreams/Readme.md b/substreams/Readme.md index 0ceaea5..9dc583e 100644 --- a/substreams/Readme.md +++ b/substreams/Readme.md @@ -22,3 +22,6 @@ the package you'd like to pre release. This will create a `[package]-[semver].pre-[commit-sha]` release in our spkg repository which you can use to run the substream´. +## Test your implementation + +To run a full end-to-end integration test you can refer to the [testing script documentation](../testing/README.md) diff --git a/substreams/ethereum-template/integration_test.tycho.yaml b/substreams/ethereum-template/integration_test.tycho.yaml index a5040d2..bea6aa3 100644 --- a/substreams/ethereum-template/integration_test.tycho.yaml +++ b/substreams/ethereum-template/integration_test.tycho.yaml @@ -1,19 +1,36 @@ +# Name of the substreams config file in your substreams module. Usually "./substreams.yaml" substreams_yaml_path: ./substreams.yaml -adapter_contract: "SwapAdapter.evm.runtime" +# 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: @@ -24,6 +41,8 @@ tests: 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 @@ -35,4 +54,4 @@ tests: - "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84" static_attributes: null creation_tx: "0xfac67ecbd423a5b915deff06045ec9343568edaec34ae95c43d35f2c018afdaa" - skip_simulation: true # If true, always add a reason \ No newline at end of file + skip_simulation: true # If true, always add a reason diff --git a/testing/README.md b/testing/README.md index 13dcad4..f760dde 100644 --- a/testing/README.md +++ b/testing/README.md @@ -1,66 +1,116 @@ # Substreams Testing -This package provides a comprehensive testing suite for Substreams modules. The testing suite is designed to facilitate end-to-end testing, ensuring that your Substreams modules function as expected. +This package provides a comprehensive testing suite for Substreams modules. The testing suite is designed to facilitate +end-to-end testing, ensuring that your Substreams modules function as expected. ## Overview -The testing suite builds the `.spkg` for your Substreams module, indexes a specified block range, and verifies that the expected state has been correctly indexed in PostgreSQL. +The testing suite builds the `.spkg` for your Substreams module, indexes a specified block range, and verifies that the +expected state has been correctly indexed in PostgreSQL. +Additionally, it will also try to simulate some transactions using the `SwapAdapter` interface. ## Prerequisites -- Latest version of our indexer, Tycho. Please contact us to obtain the latest version. Once acquired, place it in the `/testing/` directory. +- Latest version of our indexer, Tycho. Please contact us to obtain the latest version. Once acquired, place it in the + `/usr/local/bin/` directory. - Access to PropellerHeads' private PyPI repository. Please contact us to obtain access. - Docker installed on your machine. +- [Conda](https://conda.io/projects/conda/en/latest/user-guide/install/index.html) + and [AWS cli](https://aws.amazon.com/cli/) installed ## Test Configuration -Tests are defined in a `yaml` file. A template can be found at +Tests are defined in a `yaml` file. A documented template can be found at `substreams/ethereum-template/integration_test.tycho.yaml`. The configuration file should include: - The target Substreams config file. +- The corresponding SwapAdapter and args to build it. - The expected protocol types. - The tests to be run. -Each test will index all blocks between `start-block` and `stop-block` and verify that the indexed state matches the expected state. +Each test will index all blocks between `start-block` and `stop-block`, verify that the indexed state matches the +expected state and optionally simulate transactions using `SwapAdapter` interface. -You will also need the EVM Runtime file for the adapter contract. -The script to generate this file is available under `evm/scripts/buildRuntime.sh`. -Please place this Runtime file under the respective `substream` directory inside the `evm` folder. +You will also need the VM Runtime file for the adapter contract. +Our testing script should be able to build it using your test config. +The script to generate this file manually is available under `evm/scripts/buildRuntime.sh`. -## Running Tests +## Setup testing environment ### Step 1: Export Environment Variables -Export the required environment variables for the execution. You can find the available environment variables in the `.env.default` file. +**DOMAIN_OWNER** + +- **Description**: The domain owner identifier for Propellerhead's AWS account, used for authenticating on the private + PyPI repository. +- **Example**: `export DOMAIN_OWNER=123456789` + +### Step 2: Create python virtual environment for testing + +Run setup env script. It will create a conda virtual env and install all dependencies. + +Please note that some dependencies require access to our private PyPI repository. + +``` +setup_env.sh +``` + +## Running Tests + +### Prerequisites + +This section requires a testing environment setup. If you don’t have it yet, please refer to the [setup testing +environment section](#setup-testing-environment) + +### Step 1: Export Environment Variables + +Export the required environment variables for the execution. You can find the available environment variables in the +`.env.default` file. Please create a `.env` file in the `testing` directory and set the required environment variables. #### Environment Variables -**SUBSTREAMS_PACKAGE** -- **Description**: Specifies the Substreams module that you want to test -- **Example**: `export SUBSTREAMS_PACKAGE=ethereum-balancer` - -**DATABASE_URL** -- **Description**: The connection string for the PostgreSQL database. It includes the username, password, host, port, and database name. It's already set to the default for the Docker container. -- **Example**: `export DATABASE_URL="postgres://postgres:mypassword@localhost:5431/tycho_indexer_0` - **RPC_URL** -- **Description**: The URL for the Ethereum RPC endpoint. This is used to fetch the storage data. The node needs to be an archive node, and support [debug_storageRangeAt](https://www.quicknode.com/docs/ethereum/debug_storageRangeAt). + +- **Description**: The URL for the Ethereum RPC endpoint. This is used to fetch the storage data. The node needs to be + an archive node, and support [debug_storageRangeAt](https://www.quicknode.com/docs/ethereum/debug_storageRangeAt). - **Example**: `export RPC_URL="https://ethereum-mainnet.core.chainstack.com/123123123123"` **SUBSTREAMS_API_TOKEN** + - **Description**: The API token for accessing Substreams services. This token is required for authentication. - **Example**: `export SUBSTREAMS_API_TOKEN=eyJhbGci...` -**DOMAIN_OWNER** -- **Description**: The domain owner identifier for Propellerhead's AWS account, used for authenticating on the private PyPI repository. -- **Example**: `export DOMAIN_OWNER=123456789` +### Step 2: Run tests -### Step 2: Build and the Testing Script +Run local postgres database using docker compose -To build the testing script, run the following commands: ```bash -source pre_build.sh -docker compose build -docker compose run app -``` \ No newline at end of file +docker compose up -d db +``` + +Run tests for your package. + +```bash +python ./testing/src/runner/cli.py --package "your-package-name" +``` + +#### Example + +If you want to run tests for `ethereum-balancer`, use: + +```bash +conda activate propeller-protocol-lib-testing +export RPC_URL="https://ethereum-mainnet.core.chainstack.com/123123123123" +export SUBSTREAMS_API_TOKEN=eyJhbGci... +docker compose up -d db +python ./testing/src/runner/cli.py --package "ethereum-balancer" +``` + +#### Testing CLI args + +A list and description of all available CLI args can be found using: + +``` +python ./testing/src/runner/cli.py --help +``` From 6bd96374b513ebdcb01e6f711efbedb5394d2f45 Mon Sep 17 00:00:00 2001 From: Florian Pellissier <111426680+flopell@users.noreply.github.com> Date: Mon, 19 Aug 2024 10:34:36 +0200 Subject: [PATCH 3/6] docs(indexing): add docs for reserved attributes --- docs/indexing/reserved-attributes.md | 164 ++++++++++++++++++++++++ docs/indexing/substreams-integration.md | 34 ++++- docs/indexing/vm-integration/README.md | 2 +- 3 files changed, 197 insertions(+), 3 deletions(-) create mode 100644 docs/indexing/reserved-attributes.md diff --git a/docs/indexing/reserved-attributes.md b/docs/indexing/reserved-attributes.md new file mode 100644 index 0000000..f1c7ea0 --- /dev/null +++ b/docs/indexing/reserved-attributes.md @@ -0,0 +1,164 @@ +# Reserved attribute names + +Certain attribute names are reserved exclusively for specific purposes. Please use them only for their intended functions + +## Static Attributes + +The following attributes names are reserved and must be given using `ProtocolComponent.static_att`. + +- ### **pool_id** + +#### Description + +The `pool_id` static attribute is used to specify the identifier of the pool when it differs from the `ProtocolComponent.id`. For example, Balancer pools have a component ID that corresponds to their contract address, and a separate pool ID used for registration on the Balancer Vault contract. + +#### Type + +This attribute value must be provided as a UTF-8 encoded string in bytes. + +#### Example Usage + +```rust +Attribute { +name: "pool_id".to_string(), +value: format!("0x{}", hex::encode(pool_registered.pool_id)).as_bytes(), +change: ChangeType::Creation.into(), +} +``` + +- ### **manual_updates** + +#### Description + +The `manual_updates` static attribute determines whether the component update should be manually triggered using the `update_marker` state attribute. By default, updates occur automatically whenever there is a change in any of the required contracts. However, in scenarios where a contract undergoes frequent changes, automatic updates may not be desirable. For instance, a change in Balancer Vault storage should only trigger updates for the specific pools affected by the change, rather than for all pools indiscriminately. The `manual_updates` field helps to control and prevent unnecessary updates in such cases. + +If it's set to `[1u8]`, updates on this component are only triggered by emitting an `update_marker` state attribute (described below). + +#### Type + +This attribute value must be provided as bytes. + +#### Example Usage + +```rust +Attribute { +name: "manual_updates".to_string(), +value: [1u8], +change: ChangeType::Creation.into(), +} +``` + +## State Attributes + +The following attributes names are reserved and must be given using `EntityChanges`. + +- ### **stateless_contract_addr** + +#### Description + +The `stateless_contract_addr_{index}` field is used to specify the address of a stateless contract required by the component. This field is essential for components that interact with stateless contracts, particularly in scenarios involving `DELEGATECALL`. If the bytecode of this stateless contract can be retreived in Substreams, it must be passed using the `stateless_contract_code` attribute (see below). + +An index is used if multiple stateless contracts are needed. This index should start at 0 and increment by 1 for each additional `stateless_contract_addr`. + +The value for `stateless_contract_addr_{index}` can be provided in two ways: + +1. **Direct Contract Address**: A static contract address can be specified directly. +2. **Dynamic Address Resolution**: Alternatively, you can define a function or method that dynamically resolves and retrieves the stateless contract address at runtime. This can be particularly useful in complex contract architectures, such as those using a dynamic proxy pattern. It is important to note that the called contract must be indexed by the Substreams module. + +#### Type + +This attribute value must be provided as a UTF-8 encoded string in bytes. + +#### Example Usage + +##### 1. Direct Contract Address + +To specify a direct contract address: + +```rust +Attribute { + name: "stateless_contract_addr_0".into(), + value: format!("0x{}", hex::encode(address)).into_bytes(), + change: ChangeType::Creation.into(), +} +Attribute { + name: "stateless_contract_addr_1".into(), + value: format!("0x{}", hex::encode(other_address)).into_bytes(), + change: ChangeType::Creation.into(), +} +``` + +##### 2. Dynamic Address Resolution + +To specify a function that dynamically resolves the address: + +```rust +Attribute { +name: "stateless_contract_addr_0".into(), +// Call views_implementation() on TRICRYPTO_FACTORY +value: format!("call:0x{}:views_implementation()", hex::encode(TRICRYPTO_FACTORY)).into_bytes(), +change: ChangeType::Creation.into(), +} +``` + +- ### **stateless_contract_code** + +#### Description + +The `stateless_contract_code_{index}` field is used to specify the code for a given `stateless_contract_addr`. + +An index is used if multiple stateless contracts are needed. This index must match with the related `stateless_contract_addr`. + +#### Type + +This attribute value must be provided as bytes. + +#### Example Usage + +```rust +Attribute { +name: "stateless_contract_code_0".to_string(), +value: code.to_vec(), +change: ChangeType::Creation.into(), +} +``` + +- ### **balance_owner** + +#### Description + +The `balance_owner` field is used to specify the address of the account that owns the protocol component tokens, in cases where the tokens are not owned by the protocol component itself. This is particularly useful for protocols that use a vault, for example Balancer. + +#### Type + +This attribute value must be provided as bytes. + +#### Example Usage + +```rust +Attribute { +name: "balance_owner".to_string(), +value: VAULT_ADDRESS.to_vec(), +change: ChangeType::Creation.into(), +} +``` + +- ### **update_marker** + +#### Description + +The `update_marker` field is used to indicate that a pool has changed, thereby triggering an update on the protocol component when `manual_update` is enabled. + +#### Type + +This attribute value must be provided as bytes. + +#### Example Usage + +```rust +Attribute { + name: "update_marker".to_string(), + value: vec![1u8], + change: ChangeType::Update.into(), +}; +``` diff --git a/docs/indexing/substreams-integration.md b/docs/indexing/substreams-integration.md index f4f71a4..7f747d7 100644 --- a/docs/indexing/substreams-integration.md +++ b/docs/indexing/substreams-integration.md @@ -38,6 +38,10 @@ Many of the types above are variable length bytes. This allows for flexibility a **Attributes:** the value encoding for attributes is variable. It depends on the use case. Since the attributes are highly dynamic they are only used by the corresponding logic components, so the encoding can be tailored to the logic implementation: E.g. since Rust uses little endian one may choose to use little endian encoding for integers if the native logic module is written in Rust. +#### Special attribute names + +Certain attribute names are reserved exclusively for specific purposes in our simulation process. Please use them only for their intended functions. See the [list of reserved attributes](./reserved-attributes.md) + ### Changes of interest PropellerHeads integration should at least communicate the following changes: @@ -88,7 +92,33 @@ Newly created components are detected by mapping over the `sf.ethereum.type.v2.B The output message should usually contain as much information about the component available at that time as well as the transaction that created the protocol component. -We have found that using the final model (`BlockChanges`) prefilled with only component changes is usually good enough since it holds all the information that will be necessary at the end. +We have found that using the final model (see `BlockChanges` below) prefilled with only component changes is usually good enough since it holds all the information that will be necessary at the end. + +```protobuf +// A set of changes aggregated by transaction. +message TransactionChanges { + // The transaction instance that results in the changes. + Transaction tx = 1; + // Contains the changes induced by the above transaction, aggregated on a per-contract basis. + // Contains the contract changes induced by the above transaction, usually for tracking VM components. + repeated ContractChange contract_changes = 2; + // Contains the entity changes induced by the above transaction. + // Usually for tracking native components or used for VM extensions (plugins). + repeated EntityChanges entity_changes = 3; + // An array of newly added components. + repeated ProtocolComponent component_changes = 4; + // An array of balance changes to components. + repeated BalanceChange balance_changes = 5; +} +// A set of transaction changes within a single block. +// This message must be the output of your substreams module. +message BlockChanges { + // The block for which these changes are collectively computed. + Block block = 1; + // The set of transaction changes observed in the specified block. + repeated TransactionChanges changes = 2; +} +``` Note that a single transaction may emit multiple newly created components. In this case it is expected that the `TransactionChanges.component_changes`, contains multiple `ProtocolComponents`. @@ -106,7 +136,7 @@ Since this is challenging the following approach is recommended: - Aggregate the BalanceDelta messages using a `BigIntAddStore`. - In a final handler, use as inputs: A `DeltaStore` input from step 2 and the `BlockBalanceDeltas` from step 1. You can now zip the deltas from the store with the balance deltas from step 1. The store deltas contains the aggregated (absolute) balance at each version and the balance deltas contain the corresponding transaction. -Our Substreams SDK provide the `extract_balance_deltas_from_tx` function that extracts all relevant `BalanceDelta` from ERC20 `Transfer` events in a given transaction (see Curve implementation). +Our Substreams SDK provides the `extract_balance_deltas_from_tx` function that extracts all relevant `BalanceDelta` from ERC20 `Transfer` events for a given transaction (see Curve implementation). #### Tracking State Changes diff --git a/docs/indexing/vm-integration/README.md b/docs/indexing/vm-integration/README.md index 7f2f94a..e7de893 100644 --- a/docs/indexing/vm-integration/README.md +++ b/docs/indexing/vm-integration/README.md @@ -81,7 +81,7 @@ Read our [Substreams README.md](../../../substreams/README.md) for more informat block: eth::v2::Block, ) -> Result {} ``` - The `map_protocol_changes` function takes a raw block as input and returns a `BlockChanges` struct, which is derived from the `BlockChanges` protobuf message in [vm.proto](../../../proto/tycho/evm/v1/vm.proto). + The `map_protocol_changes` function takes a raw block as input and returns a `BlockChanges` struct, which is derived from the `BlockChanges` protobuf message in [common.proto](../../../proto/tycho/evm/v1/common.proto). 1. The `BlockChanges` is a list of `TransactionChanges`, which includes these main fields: From 2ad12cb659ef8d4db7b19ebc17bf04f9b5171416 Mon Sep 17 00:00:00 2001 From: Florian Pellissier <111426680+flopell@users.noreply.github.com> Date: Tue, 20 Aug 2024 09:06:52 +0200 Subject: [PATCH 4/6] docs(indexing): miscellaneous small docs improvements --- docs/indexing/reserved-attributes.md | 12 ++++++------ docs/indexing/substreams-integration.md | 2 +- testing/README.md | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/indexing/reserved-attributes.md b/docs/indexing/reserved-attributes.md index f1c7ea0..a65a7ea 100644 --- a/docs/indexing/reserved-attributes.md +++ b/docs/indexing/reserved-attributes.md @@ -1,10 +1,10 @@ # Reserved attribute names -Certain attribute names are reserved exclusively for specific purposes. Please use them only for their intended functions +Certain attribute names are reserved exclusively for specific purposes. Please use them only for their intended applications. ## Static Attributes -The following attributes names are reserved and must be given using `ProtocolComponent.static_att`. +The following attributes names are reserved and must be given using `ProtocolComponent.static_att`. These attributes MUST be immutable. If it can ever change, it should be given as a state attribute (see below) for this component id. - ### **pool_id** @@ -32,11 +32,11 @@ change: ChangeType::Creation.into(), The `manual_updates` static attribute determines whether the component update should be manually triggered using the `update_marker` state attribute. By default, updates occur automatically whenever there is a change in any of the required contracts. However, in scenarios where a contract undergoes frequent changes, automatic updates may not be desirable. For instance, a change in Balancer Vault storage should only trigger updates for the specific pools affected by the change, rather than for all pools indiscriminately. The `manual_updates` field helps to control and prevent unnecessary updates in such cases. -If it's set to `[1u8]`, updates on this component are only triggered by emitting an `update_marker` state attribute (described below). +If it's enable, updates on this component are only triggered by emitting an `update_marker` state attribute (described below). #### Type -This attribute value must be provided as bytes. +This attribute must be set to [1u8] to enable manual updates. #### Example Usage @@ -50,7 +50,7 @@ change: ChangeType::Creation.into(), ## State Attributes -The following attributes names are reserved and must be given using `EntityChanges`. +The following attributes names are reserved and must be given using `EntityChanges`. Unlike [Static Attributes](#static-attributes), state attributes are used for dynamic attributes and are allowed to change at anytime. - ### **stateless_contract_addr** @@ -127,7 +127,7 @@ change: ChangeType::Creation.into(), #### Description -The `balance_owner` field is used to specify the address of the account that owns the protocol component tokens, in cases where the tokens are not owned by the protocol component itself. This is particularly useful for protocols that use a vault, for example Balancer. +The `balance_owner` field is used to specify the address of the account that owns the protocol component tokens, in cases where the tokens are not owned by the protocol component itself or the component specifies multiple contract addresses. This is particularly useful for protocols that use a vault, for example Balancer. #### Type diff --git a/docs/indexing/substreams-integration.md b/docs/indexing/substreams-integration.md index 7f747d7..93efd85 100644 --- a/docs/indexing/substreams-integration.md +++ b/docs/indexing/substreams-integration.md @@ -20,7 +20,7 @@ So basically when processing a block we need to emit the block itself, all trans #### Models -The models below are used for communication between Substreams and our indexer, as well as between Substreams modules. +The models below are used for communication between Substreams and Tycho indexer, as well as between Substreams modules. Our indexer expects to receive a `BlockChanges` output from your Substreams package. diff --git a/testing/README.md b/testing/README.md index f760dde..49dd028 100644 --- a/testing/README.md +++ b/testing/README.md @@ -11,8 +11,7 @@ Additionally, it will also try to simulate some transactions using the `SwapAdap ## Prerequisites -- Latest version of our indexer, Tycho. Please contact us to obtain the latest version. Once acquired, place it in the - `/usr/local/bin/` directory. +- Latest version of our indexer, Tycho. Please contact us to obtain the latest version. Once acquired, place it in a directory that is included in your system’s PATH. - Access to PropellerHeads' private PyPI repository. Please contact us to obtain access. - Docker installed on your machine. - [Conda](https://conda.io/projects/conda/en/latest/user-guide/install/index.html) @@ -48,6 +47,7 @@ The script to generate this file manually is available under `evm/scripts/buildR ### Step 2: Create python virtual environment for testing Run setup env script. It will create a conda virtual env and install all dependencies. +This script must be run from within the `propeller-protocol-lib/testing` directory. Please note that some dependencies require access to our private PyPI repository. From c7b1796f7d58bf1704ca0df1c311a1ee097afd16 Mon Sep 17 00:00:00 2001 From: Florian Pellissier <111426680+flopell@users.noreply.github.com> Date: Tue, 20 Aug 2024 09:07:34 +0200 Subject: [PATCH 5/6] refactor(testing): make setup_env.sh runnable only from within `/testing` --- testing/setup_env.sh | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/testing/setup_env.sh b/testing/setup_env.sh index bd80b67..2bdf3d8 100755 --- a/testing/setup_env.sh +++ b/testing/setup_env.sh @@ -4,19 +4,6 @@ ENV_NAME="propeller-protocol-lib-testing" PYTHON_VERSION="3.9" REQUIREMENTS_FILE="requirements.txt" -PRE_BUILD_SCRIPT="pre_build.sh" - -# Allow to run either from root or from inside testing folder. -if [ -f "./$REQUIREMENTS_FILE" ]; then - # If the requirements file is found in the current directory, do nothing - SCRIPT_DIR="." -elif [ -f "testing/$REQUIREMENTS_FILE" ]; then - # If the requirements file is found in testing/, adjust the paths - SCRIPT_DIR="testing" -else - echo "Error: Script must be run from the propeller-protocol-lib or propeller-protocol-lib/testing directory." - exit 1 -fi # Create conda environment echo "Creating conda environment ${ENV_NAME} with Python ${PYTHON_VERSION}..." @@ -27,9 +14,9 @@ echo "Activating the environment..." source activate $ENV_NAME # Install the requirements -echo "Installing the requirements from ${SCRIPT_DIR}/${REQUIREMENTS_FILE}..." -./${SCRIPT_DIR}/${PRE_BUILD_SCRIPT} -pip install -r ${SCRIPT_DIR}/${REQUIREMENTS_FILE} +echo "Installing the requirements from ${REQUIREMENTS_FILE}..." +./pre_build.sh +pip install -r $REQUIREMENTS_FILE conda activate $ENV_NAME echo "Setup complete." \ No newline at end of file From 28dee5e7b787c741578c56d20d775827ede83b2b Mon Sep 17 00:00:00 2001 From: Florian Pellissier <111426680+flopell@users.noreply.github.com> Date: Tue, 20 Aug 2024 11:32:01 +0200 Subject: [PATCH 6/6] docs(indexing): add special notice for `pool_id` --- docs/indexing/reserved-attributes.md | 42 +++++++++++++++------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/docs/indexing/reserved-attributes.md b/docs/indexing/reserved-attributes.md index a65a7ea..8fea91e 100644 --- a/docs/indexing/reserved-attributes.md +++ b/docs/indexing/reserved-attributes.md @@ -6,26 +6,6 @@ Certain attribute names are reserved exclusively for specific purposes. Please u The following attributes names are reserved and must be given using `ProtocolComponent.static_att`. These attributes MUST be immutable. If it can ever change, it should be given as a state attribute (see below) for this component id. -- ### **pool_id** - -#### Description - -The `pool_id` static attribute is used to specify the identifier of the pool when it differs from the `ProtocolComponent.id`. For example, Balancer pools have a component ID that corresponds to their contract address, and a separate pool ID used for registration on the Balancer Vault contract. - -#### Type - -This attribute value must be provided as a UTF-8 encoded string in bytes. - -#### Example Usage - -```rust -Attribute { -name: "pool_id".to_string(), -value: format!("0x{}", hex::encode(pool_registered.pool_id)).as_bytes(), -change: ChangeType::Creation.into(), -} -``` - - ### **manual_updates** #### Description @@ -48,6 +28,28 @@ change: ChangeType::Creation.into(), } ``` +- ### **pool_id** + +#### Description + +The `pool_id` static attribute is used to specify the identifier of the pool when it differs from the `ProtocolComponent.id`. For example, Balancer pools have a component ID that corresponds to their contract address, and a separate pool ID used for registration on the Balancer Vault contract. + +**Notice**: In most of the cases, using `ProtocolComponent.id` directly is preferred over `pool_id`. + +#### Type + +This attribute value must be provided as a UTF-8 encoded string in bytes. + +#### Example Usage + +```rust +Attribute { +name: "pool_id".to_string(), +value: format!("0x{}", hex::encode(pool_registered.pool_id)).as_bytes(), +change: ChangeType::Creation.into(), +} +``` + ## State Attributes The following attributes names are reserved and must be given using `EntityChanges`. Unlike [Static Attributes](#static-attributes), state attributes are used for dynamic attributes and are allowed to change at anytime.