feat(Testing SDK): Pass adapter path to decoder (#249)

* feat: Pass adapter contract to decoder

This was the most elegant solution we could come up with at the moment for having the proper adapter in the builder.

Notes:
- The default adapter bytecode in tycho-simulation is still loaded at compile time.
- If the adapter bytecode is passed to the decoder, it will be loaded dynamically at runtime and used instead the bytecodes in tycho-simulation.
- The adapter bytecode is used in the builder to get capabilities and thus spot prices before returning the state, so just overwriting the adapter in the state is way too cumbersome. We went with this solution since it was the lesser evil, even though we know it leaks VM-specific info to non-vm protocols (which was already being done anyway with the balances).

* feat: Take a struct of vm_attributes instead of just adapter path

So that we don't need to change the interface in the future

* chore: remove comment - we are no longer blocked

This is fixed with the latest change to pass adapter contract to decoder

* feat: Use DecoderContext instead of VMAttributes

- More easily extendable
- Doesn't break the existing decoder interface

---------

Co-authored-by: TAMARA LIPOWSKI <data.lipowski@extaccount.com>
This commit is contained in:
Tamara
2025-09-08 14:34:27 -04:00
committed by GitHub
parent 5a9a757a06
commit 93b969e41f
7 changed files with 68 additions and 18 deletions

View File

@@ -21,6 +21,7 @@ use tycho_simulation::{
engine_db::tycho_db::PreCachedDB,
protocol::{u256_num::bytes_to_u256, vm::state::EVMPoolState},
},
protocol::models::DecoderContext,
tycho_client::feed::{
synchronizer::{ComponentWithState, Snapshot, StateSyncMessage},
FeedMessage,
@@ -28,6 +29,7 @@ use tycho_simulation::{
};
use crate::{
adapter_builder::AdapterContractBuilder,
config::{IntegrationTest, IntegrationTestsConfig, ProtocolComponentWithTestConfig},
rpc::RPCProvider,
tycho_rpc::TychoClient,
@@ -40,12 +42,17 @@ pub struct TestRunner {
db_url: String,
vm_traces: bool,
substreams_path: PathBuf,
adapter_contract_builder: AdapterContractBuilder,
}
impl TestRunner {
pub fn new(package: String, tycho_logs: bool, db_url: String, vm_traces: bool) -> Self {
let substreams_path = PathBuf::from("../substreams").join(&package);
Self { tycho_logs, db_url, vm_traces, substreams_path }
let repo_root = env::current_dir().expect("Failed to get current directory");
let evm_path = repo_root.join("../evm");
let adapter_contract_builder =
AdapterContractBuilder::new(evm_path.to_string_lossy().to_string());
Self { tycho_logs, db_url, vm_traces, substreams_path, adapter_contract_builder }
}
pub fn run_tests(&self) -> miette::Result<()> {
@@ -96,7 +103,7 @@ impl TestRunner {
fn parse_config(config_yaml_path: &PathBuf) -> miette::Result<IntegrationTestsConfig> {
info!("Config YAML: {}", config_yaml_path.display());
let yaml = Yaml::file(&config_yaml_path);
let yaml = Yaml::file(config_yaml_path);
let figment = Figment::new().merge(yaml);
let config = figment
.extract::<IntegrationTestsConfig>()
@@ -145,7 +152,16 @@ impl TestRunner {
.wrap_err("Failed to run Tycho")?;
let _ = tycho_runner.run_with_rpc_server(
validate_state,
|expected_components, start_block, stop_block, skip_balance_check| {
validate_state(
expected_components,
start_block,
stop_block,
skip_balance_check,
config,
&self.adapter_contract_builder,
)
},
&test.expected_components,
test.start_block,
test.stop_block,
@@ -176,6 +192,8 @@ fn validate_state(
start_block: u64,
stop_block: u64,
skip_balance_check: bool,
config: &IntegrationTestsConfig,
adapter_contract_builder: &AdapterContractBuilder,
) -> miette::Result<()> {
let rt = Runtime::new().unwrap();
@@ -263,8 +281,42 @@ fn validate_state(
}
// Step 3: Run Tycho Simulation
// Build/find the adapter contract
let adapter_contract_path =
match adapter_contract_builder.find_contract(&config.adapter_contract) {
Ok(path) => {
info!("Found adapter contract at: {}", path.display());
path
}
Err(_) => {
info!("Adapter contract not found, building it...");
adapter_contract_builder
.build_target(
&config.adapter_contract,
config
.adapter_build_signature
.as_deref(),
config.adapter_build_args.as_deref(),
)
.wrap_err("Failed to build adapter contract")?
}
};
info!("Using adapter contract: {}", adapter_contract_path.display());
let adapter_contract_path_str: &str = adapter_contract_path.to_str().unwrap();
let mut decoder = TychoStreamDecoder::new();
decoder.register_decoder::<EVMPoolState<PreCachedDB>>("test_protocol");
let decoder_context = DecoderContext::new().vm_adapter_path(adapter_contract_path_str);
decoder.register_decoder_with_context::<EVMPoolState<PreCachedDB>>(
"test_protocol",
decoder_context,
);
// NOTE: Once tycho-simulation is updated, you can use the new API like this:
// use tycho_simulation::protocol::models::DecoderContext;
// let context = DecoderContext::new().vm_adapter_path(adapter_contract_path_str);
// decoder.register_decoder_with_context::<EVMPoolState<PreCachedDB>>("test_protocol", context);
// Mock a stream message, with only a Snapshot and no deltas
let mut states: HashMap<String, ComponentWithState> = HashMap::new();
@@ -325,9 +377,6 @@ fn validate_state(
.or_insert_with(|| comp.tokens.clone());
}
// This is where we get blocked. Currently, Tycho Simulation expects the runtime to be
// prebuild and accessible from TychoSim - we should allow passing it when parsing the block
// TODO: Since we don't have balances on the VM State, we could try to use Limits, otherwise ask
// the user to specify a set of values on the YAML file.
for (id, state) in block_msg.states.iter() {