diff --git a/substreams/ethereum-balancer/integration_test.tycho.yaml b/substreams/ethereum-balancer/integration_test.tycho.yaml new file mode 100644 index 0000000..e1e1810 --- /dev/null +++ b/substreams/ethereum-balancer/integration_test.tycho.yaml @@ -0,0 +1,169 @@ +substreams_yaml_path: ./substreams.yaml +protocol_type_names: + - "balancer_pool" +adapter_contract: "BalancerV2SwapAdapter" +adapter_build_signature: "constructor(address)" +adapter_build_args: "0xBA12222222228d8Ba445958a75a0704d566BF2C8" +skip_balance_check: true +initialized_accounts: + - "0xba12222222228d8ba445958a75a0704d566bf2c8" +# Uncomment entries below to include composable stable pool dependencies +# wstETH dependencies +# - "0x72D07D7DcA67b8A406aD1Ec34ce969c90bFEE768" +# - "0xb8ffc3cd6e7cf5a098a1c92f48009765b24088dc" +# - "0xae7ab96520de3a18e5e111b5eaab095312d7fe84" +# - "0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0" +# - "0x2b33cf282f867a7ff693a66e11b0fcc5552e4425" +# - "0x17144556fd3424edc8fc8a4c940b2d04936d17eb" +# sfrxETH dependencies +# - "0x302013E7936a39c358d07A3Df55dc94EC417E3a1" +# - "0xac3e018457b222d93114458476f3e3416abbe38f" +# rETH dependencies +# - "0x1a8F81c256aee9C640e14bB0453ce247ea0DFE6F" +# - "0x07fcabcbe4ff0d80c2b1eb42855c0131b6cba2f4" +# - "0x1d8f8f00cfa6758d7be78336684788fb0ee0fa46" +# - "0xae78736cd615f374d3085123a210448e74fc6393" +tests: + # WeightedPoolFactory - 0x897888115Ada5773E02aA29F775430BFB5F34c51 + - name: test_weighted_pool_creation + start_block: 20128706 + stop_block: 20128806 + expected_components: + - id: "0xe96a45f66bdDA121B24F0a861372A72E8889523d" + tokens: + - "0x38C2a4a7330b22788374B8Ff70BBa513C8D848cA" + - "0x514910771AF9Ca656af840dff83E8264EcF986CA" + static_attributes: + rate_providers: "0x5b22307830303030303030303030303030303030303030303030303030303030303030303030303030303030222c22307830303030303030303030303030303030303030303030303030303030303030303030303030303030225d" + pool_id: "0x307865393661343566363662646461313231623234663061383631333732613732653838383935323364303030323030303030303030303030303030303030363962" + normalized_weights: "0x5b22307830623161326263326563353030303030222c22307830326336386166306262313430303030225d" + fee: "0x11c37937e08000" + manual_updates: "0x01" + pool_type: "0x5765696768746564506f6f6c466163746f7279" + creation_tx: "0xa63c671046ad2075ec8ea83ac21199cf3e3a5f433e72ec4c117cbabfb9b18de2" + + # WeightedPool2TokensFactory - 0xA5bf2ddF098bb0Ef6d120C98217dD6B141c74EE0 + - name: weighted_legacy_creation + start_block: 13148365 + stop_block: 13148465 + expected_components: + - id: "0xBF96189Eee9357a95C7719f4F5047F76bdE804E5" + tokens: + - "0x5A98FcBEA516Cf06857215779Fd812CA3beF1B32" + - "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" + static_attributes: + pool_id: "0x307862663936313839656565393335376139356337373139663466353034376637366264653830346535303030323030303030303030303030303030303030303837" + weights: "0x5b22307830623161326263326563353030303030222c22307830326336386166306262313430303030225d" + fee: "0x08e1bc9bf04000" + manual_updates: "0x01" + pool_type: "0x5765696768746564506f6f6c32546f6b656e73466163746f7279" + creation_tx: "0xdced662e41b1608c386551bbc89894a10321fd8bd58782e22077d1044cf99cb5" + + # ComposableStablePoolFactory - 0xDB8d758BCb971e482B2C45f7F8a7740283A1bd3A + - name: test_composable_stable_pool_creation + start_block: 17677300 + stop_block: 17678400 + expected_components: + - id: "0x42ED016F826165C2e5976fe5bC3df540C5aD0Af7" + tokens: + - "0x42ed016f826165c2e5976fe5bc3df540c5ad0af7" + - "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0" + - "0xac3E018457B222d93114458476f3E3416Abbe38F" + - "0xae78736Cd615f374D3085123A210448E74Fc6393" + static_attributes: + rate_providers: "0x5b22307837326430376437646361363762386134303661643165633334636539363963393062666565373638222c22307833303230313365373933366133396333353864303761336466353564633934656334313765336131222c22307831613866383163323536616565396336343065313462623034353363653234376561306466653666225d" + pool_id: "0x307834326564303136663832363136356332653539373666653562633364663534306335616430616637303030303030303030303030303030303030303030353862" + bpt: "0x42ed016f826165c2e5976fe5bc3df540c5ad0af7" + fee: "0x5af3107a4000" + manual_updates: "0x01" + pool_type: "0x436f6d706f7361626c65537461626c65506f6f6c466163746f7279" + skip_simulation: true + creation_tx: "0x53ff6bab0d8a76a998e29e59da8068ad906ae85507a1c2fbf2505e2cb52fd754" + + # ERC4626LinearPoolFactory - 0x813EE7a840CE909E7Fea2117A44a90b8063bd4fd + - name: test_erc4626_linear_pool_creation + start_block: 17480142 + stop_block: 17480242 + expected_components: + - id: "0x3fCb7085B8F2F473F80bF6D879cAe99eA4DE9344" + tokens: + - "0x39Dd7790e75C6F663731f7E1FdC0f35007D3879b" + - "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" + - "0x3fcb7085b8f2f473f80bf6d879cae99ea4de9344" + static_attributes: + pool_id: "0x307833666362373038356238663266343733663830626636643837396361653939656134646539333434303030303030303030303030303030303030303030353664" + wrapped_token: "0x39dd7790e75c6f663731f7e1fdc0f35007d3879b" + fee: "0x00b5e620f48000" + manual_updates: "0x01" + pool_type: "0x455243343632364c696e656172506f6f6c466163746f7279" + upper_target: "0x108b2a2c28029094000000" + bpt: "0x3fcb7085b8f2f473f80bf6d879cae99ea4de9344" + main_token: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + skip_simulation: true + creation_tx: "0x5ff97870685370bab3876a4335d28c42e24659064fe78b486d6fb1b37b992877" + + # EulerLinearPoolFactory - 0x5F43FBa61f63Fa6bFF101a0A0458cEA917f6B347 + - name: test_euler_linear_pool_creation + start_block: 16588117 + stop_block: 16588217 + expected_components: + - id: "0xD4e7C1F3DA1144c9E2CfD1b015eDA7652b4a4399" + tokens: + - "0xD4e7C1F3DA1144c9E2CfD1b015eDA7652b4a4399" + - "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" + - "0xEb91861f8A4e1C12333F42DCE8fB0Ecdc28dA716" + static_attributes: + pool_id: "0x307864346537633166336461313134346339653263666431623031356564613736353262346134333939303030303030303030303030303030303030303030343661" + wrapped_token: "0xeb91861f8a4e1c12333f42dce8fb0ecdc28da716" + fee: "0x00b5e620f48000" + manual_updates: "0x01" + pool_type: "0x45756c65724c696e656172506f6f6c466163746f7279" + upper_target: "0x108b2a2c28029094000000" + bpt: "0xd4e7c1f3da1144c9e2cfd1b015eda7652b4a4399" + main_token: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" + skip_simulation: true + creation_tx: "0x4a9ea683052afefdae3d189862868c3a7dc8f431d1d9828b6bfd9451a8816426" + + # SiloLinearPoolFactory - 0x4E11AEec21baF1660b1a46472963cB3DA7811C89 + - name: test_silo_linear_pool_creation + start_block: 17173185 + stop_block: 17173187 + expected_components: + - id: "0x74CBfAF94A3577c539a9dCEE9870A6349a33b34f" + tokens: + - "0x192E67544694a7bAA2DeA94f9B1Df58BB3395A12" + - "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" + - "0x74cbfaf94a3577c539a9dcee9870a6349a33b34f" + static_attributes: + pool_id: "0x307837346362666166393461333537376335333961396463656539383730613633343961333362333466303030303030303030303030303030303030303030353334" + wrapped_token: "0x192e67544694a7baa2dea94f9b1df58bb3395a12" + fee: "0x00e8d4a51000" + manual_updates: "0x01" + pool_type: "0x53696c6f4c696e656172506f6f6c466163746f7279" + upper_target: "0x00" + bpt: "0x74cbfaf94a3577c539a9dcee9870a6349a33b34f" + main_token: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + skip_simulation: true + creation_tx: "0x215c9f4256ab450368132f4063611ae8cdd98e80bea7e44ecf0600ed1d757018" + + # YearnLinearPoolFactory - 0x5F5222Ffa40F2AEd6380D022184D6ea67C776eE0a + - name: test_yearn_linear_pool_creation + start_block: 17052601 + stop_block: 17052605 + expected_components: + - id: "0xac5b4ef7ede2f2843a704e96dcaa637f4ba3dc3f" + tokens: + - "0x806E02Dea8d4a0882caD9fA3Fa75B212328692dE" + - "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" + - "0xac5b4ef7ede2f2843a704e96dcaa637f4ba3dc3f" + static_attributes: + pool_id: "0x307861633562346566376564653266323834336137303465393664636161363337663462613364633366303030303030303030303030303030303030303030353164" + wrapped_token: "0x806e02dea8d4a0882cad9fa3fa75b212328692de" + fee: "0x00e8d4a51000" + manual_updates: "0x01" + pool_type: "0x596561726e4c696e656172506f6f6c466163746f7279" + upper_target: "0x00" + bpt: "0xac5b4ef7ede2f2843a704e96dcaa637f4ba3dc3f" + main_token: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + skip_simulation: true + creation_tx: "0x497aa03ce84d236c183204ddfc6762c8e4158da1ebc5e7e18e7f6cceaa497a2a" \ No newline at end of file diff --git a/substreams/ethereum-balancer/test_assets.yaml b/substreams/ethereum-balancer/test_assets.yaml deleted file mode 100644 index 76711b7..0000000 --- a/substreams/ethereum-balancer/test_assets.yaml +++ /dev/null @@ -1,127 +0,0 @@ -substreams_yaml_path: ./substreams.yaml -protocol_type_names: - - "balancer_pool" -adapter_contract: "BalancerV2SwapAdapter" -adapter_build_signature: "constructor(address)" -adapter_build_args: "0xBA12222222228d8Ba445958a75a0704d566BF2C8" -skip_balance_check: true -initialized_accounts: - - "0xba12222222228d8ba445958a75a0704d566bf2c8" -# Uncomment entries below to include composable stable pool dependencies -# wstETH dependencies -# - "0x72D07D7DcA67b8A406aD1Ec34ce969c90bFEE768" -# - "0xb8ffc3cd6e7cf5a098a1c92f48009765b24088dc" -# - "0xae7ab96520de3a18e5e111b5eaab095312d7fe84" -# - "0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0" -# - "0x2b33cf282f867a7ff693a66e11b0fcc5552e4425" -# - "0x17144556fd3424edc8fc8a4c940b2d04936d17eb" -# sfrxETH dependencies -# - "0x302013E7936a39c358d07A3Df55dc94EC417E3a1" -# - "0xac3e018457b222d93114458476f3e3416abbe38f" -# rETH dependencies -# - "0x1a8F81c256aee9C640e14bB0453ce247ea0DFE6F" -# - "0x07fcabcbe4ff0d80c2b1eb42855c0131b6cba2f4" -# - "0x1d8f8f00cfa6758d7be78336684788fb0ee0fa46" -# - "0xae78736cd615f374d3085123a210448e74fc6393" -tests: - # WeightedPoolFactory - 0x897888115Ada5773E02aA29F775430BFB5F34c51 - - name: test_weighted_pool_creation - start_block: 20128706 - stop_block: 20128806 - expected_state: - protocol_components: - - id: "0xe96a45f66bdDA121B24F0a861372A72E8889523d" - tokens: - - "0x38C2a4a7330b22788374B8Ff70BBa513C8D848cA" - - "0x514910771AF9Ca656af840dff83E8264EcF986CA" - static_attributes: null - creation_tx: "0xa63c671046ad2075ec8ea83ac21199cf3e3a5f433e72ec4c117cbabfb9b18de2" - - # WeightedPool2TokensFactory - 0xA5bf2ddF098bb0Ef6d120C98217dD6B141c74EE0 - - name: weighted_legacy_creation - start_block: 13148365 - stop_block: 13148465 - expected_state: - protocol_components: - - id: "0xBF96189Eee9357a95C7719f4F5047F76bdE804E5" - tokens: - - "0x5A98FcBEA516Cf06857215779Fd812CA3beF1B32" - - "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" - static_attributes: null - creation_tx: "0xdced662e41b1608c386551bbc89894a10321fd8bd58782e22077d1044cf99cb5" - - # ComposableStablePoolFactory - 0xDB8d758BCb971e482B2C45f7F8a7740283A1bd3A - - name: test_composable_stable_pool_creation - start_block: 17677300 - stop_block: 17678400 - expected_state: - protocol_components: - - id: "0x42ED016F826165C2e5976fe5bC3df540C5aD0Af7" - tokens: - - "0x42ed016f826165c2e5976fe5bc3df540c5ad0af7" - - "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0" - - "0xac3E018457B222d93114458476f3E3416Abbe38F" - - "0xae78736Cd615f374D3085123A210448E74Fc6393" - static_attributes: null - skip_simulation: true - creation_tx: "0x53ff6bab0d8a76a998e29e59da8068ad906ae85507a1c2fbf2505e2cb52fd754" - - # ERC4626LinearPoolFactory - 0x813EE7a840CE909E7Fea2117A44a90b8063bd4fd - - name: test_erc4626_linear_pool_creation - start_block: 17480142 - stop_block: 17480242 - expected_state: - protocol_components: - - id: "0x3fCb7085B8F2F473F80bF6D879cAe99eA4DE9344" - tokens: - - "0x39Dd7790e75C6F663731f7E1FdC0f35007D3879b" - - "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" - - "0x3fcb7085b8f2f473f80bf6d879cae99ea4de9344" - static_attributes: null - skip_simulation: true - creation_tx: "0x5ff97870685370bab3876a4335d28c42e24659064fe78b486d6fb1b37b992877" - - # EulerLinearPoolFactory - 0x5F43FBa61f63Fa6bFF101a0A0458cEA917f6B347 - - name: test_euler_linear_pool_creation - start_block: 16588117 - stop_block: 16588217 - expected_state: - protocol_components: - - id: "0xD4e7C1F3DA1144c9E2CfD1b015eDA7652b4a4399" - tokens: - - "0xD4e7C1F3DA1144c9E2CfD1b015eDA7652b4a4399" - - "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" - - "0xEb91861f8A4e1C12333F42DCE8fB0Ecdc28dA716" - static_attributes: null - skip_simulation: true - creation_tx: "0x4a9ea683052afefdae3d189862868c3a7dc8f431d1d9828b6bfd9451a8816426" - - # SiloLinearPoolFactory - 0x4E11AEec21baF1660b1a46472963cB3DA7811C89 - - name: test_silo_linear_pool_creation - start_block: 17173185 - stop_block: 17173187 - expected_state: - protocol_components: - - id: "0x74CBfAF94A3577c539a9dCEE9870A6349a33b34f" - tokens: - - "0x192E67544694a7bAA2DeA94f9B1Df58BB3395A12" - - "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" - - "0x74cbfaf94a3577c539a9dcee9870a6349a33b34f" - static_attributes: null - skip_simulation: true - creation_tx: "0x215c9f4256ab450368132f4063611ae8cdd98e80bea7e44ecf0600ed1d757018" - - # YearnLinearPoolFactory - 0x5F5222Ffa40F2AEd6380D022184D6ea67C776eE0a - - name: test_yearn_linear_pool_creation - start_block: 17052601 - stop_block: 17052605 - expected_state: - protocol_components: - - id: "0xac5b4ef7ede2f2843a704e96dcaa637f4ba3dc3f" - tokens: - - "0x806E02Dea8d4a0882caD9fA3Fa75B212328692dE" - - "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" - - "0xac5b4ef7ede2f2843a704e96dcaa637f4ba3dc3f" - static_attributes: null - skip_simulation: true - creation_tx: "0x497aa03ce84d236c183204ddfc6762c8e4158da1ebc5e7e18e7f6cceaa497a2a" \ No newline at end of file diff --git a/substreams/ethereum-curve/test_assets.yaml b/substreams/ethereum-curve/integration_test.tycho.yaml similarity index 100% rename from substreams/ethereum-curve/test_assets.yaml rename to substreams/ethereum-curve/integration_test.tycho.yaml diff --git a/substreams/ethereum-curve/src/abi/mod.rs b/substreams/ethereum-curve/src/abi/mod.rs index 1a77e48..f0e96ac 100644 --- a/substreams/ethereum-curve/src/abi/mod.rs +++ b/substreams/ethereum-curve/src/abi/mod.rs @@ -1,9 +1,9 @@ #![allow(clippy::all)] pub mod crypto_pool_factory; -pub mod crypto_swap_ng_factory; -pub mod erc20; -pub mod meta_pool_factory; -pub mod meta_registry; pub mod stableswap_factory; +pub mod crypto_swap_ng_factory; +pub mod meta_registry; pub mod tricrypto_factory; pub mod twocrypto_factory; +pub mod erc20; +pub mod meta_pool_factory; diff --git a/substreams/ethereum-template/integration_test.tycho.yaml b/substreams/ethereum-template/integration_test.tycho.yaml new file mode 100644 index 0000000..a5040d2 --- /dev/null +++ b/substreams/ethereum-template/integration_test.tycho.yaml @@ -0,0 +1,38 @@ +substreams_yaml_path: ./substreams.yaml +adapter_contract: "SwapAdapter.evm.runtime" +adapter_build_signature: "constructor(address)" +adapter_build_args: "0x0000000000000000000000000000000000000000" +skip_balance_check: false +initialized_accounts: + - "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84" # Needed for .... +protocol_type_names: + - "type_name_1" + - "type_name_2" +tests: + - name: test_pool_creation + start_block: 123 + stop_block: 456 + initialized_accounts: + - "0x0c0e5f2fF0ff18a3be9b835635039256dC4B4963" # Needed for .... + expected_components: + - id: "0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7" + tokens: + - "0xdac17f958d2ee523a2206206994597c13d831ec7" + - "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" + - "0x6b175474e89094c44da98b954eedeac495271d0f" + static_attributes: + attr_1: "value" + attr_2: "value" + creation_tx: "0x20793bbf260912aae189d5d261ff003c9b9166da8191d8f9d63ff1c7722f3ac6" + skip_simulation: false + - name: test_something_else + start_block: 123 + stop_block: 456 + expected_components: + - id: "0xdc24316b9ae028f1497c275eb9192a3ea0f67022" + tokens: + - "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" + - "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84" + static_attributes: null + creation_tx: "0xfac67ecbd423a5b915deff06045ec9343568edaec34ae95c43d35f2c018afdaa" + skip_simulation: true # If true, always add a reason \ No newline at end of file diff --git a/substreams/ethereum-template/test_assets.yaml b/substreams/ethereum-template/test_assets.yaml deleted file mode 100644 index 93711a1..0000000 --- a/substreams/ethereum-template/test_assets.yaml +++ /dev/null @@ -1,36 +0,0 @@ -substreams_yaml_path: ./substreams.yaml -adapter_contract: "SwapAdapter.evm.runtime" -adapter_build_signature: "constructor(address)" -adapter_build_args: "0x0000000000000000000000000000000000000000" -skip_balance_check: false -protocol_type_names: - - "type_name_1" - - "type_name_2" -tests: - - name: test_pool_creation - start_block: 123 - stop_block: 456 - initialized_accounts: - - "0x0c0e5f2fF0ff18a3be9b835635039256dC4B4963" # Needed for .... - expected_state: - protocol_components: - - id: "0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7" - tokens: - - "0xdac17f958d2ee523a2206206994597c13d831ec7" - - "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" - - "0x6b175474e89094c44da98b954eedeac495271d0f" - static_attributes: - creation_tx: "0x20793bbf260912aae189d5d261ff003c9b9166da8191d8f9d63ff1c7722f3ac6" - skip_simulation: false - - name: test_something_else - start_block: 123 - stop_block: 456 - expected_state: - protocol_components: - - id: "0xdc24316b9ae028f1497c275eb9192a3ea0f67022" - tokens: - - "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" - - "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84" - static_attributes: - creation_tx: "0xfac67ecbd423a5b915deff06045ec9343568edaec34ae95c43d35f2c018afdaa" - skip_simulation: true # If true, always add a reason diff --git a/testing/README.md b/testing/README.md index bef2a35..13dcad4 100644 --- a/testing/README.md +++ b/testing/README.md @@ -14,7 +14,8 @@ The testing suite builds the `.spkg` for your Substreams module, indexes a speci ## Test Configuration -Tests are defined in a `yaml` file. A template can be found at `substreams/ethereum-template/test_assets.yaml`. The configuration file should include: +Tests are defined in a `yaml` file. A template can be found at +`substreams/ethereum-template/integration_test.tycho.yaml`. The configuration file should include: - The target Substreams config file. - The expected protocol types. diff --git a/testing/src/runner/models.py b/testing/src/runner/models.py new file mode 100644 index 0000000..dab75d4 --- /dev/null +++ b/testing/src/runner/models.py @@ -0,0 +1,104 @@ +from pydantic import BaseModel, Field +from typing import List, Dict, Optional + +from tycho_client.dto import ProtocolComponent + + +class ProtocolComponentExpectation(BaseModel): + """Represents a ProtocolComponent with its main attributes.""" + + id: str = Field(..., description="Identifier of the protocol component") + tokens: List[str] = Field( + ..., + description="List of token addresses associated with the protocol component", + ) + static_attributes: Optional[Dict[str, Optional[str]]] = Field( + default_factory=dict, description="Static attributes of the protocol component" + ) + creation_tx: str = Field( + ..., description="Hash of the transaction that created the protocol component" + ) + + def __init__(self, **data): + super().__init__(**data) + self.id = self.id.lower() + self.tokens = sorted([t.lower() for t in self.tokens]) + + def compare(self, other: "ProtocolComponentExpectation") -> Optional[str]: + """Compares the current instance with another ProtocolComponent instance and returns a message with the differences or None if there are no differences.""" + differences = [] + for field_name, field_value in self.__dict__.items(): + other_value = getattr(other, field_name, None) + if field_value != other_value: + differences.append( + f"Field '{field_name}' mismatch: '{field_value}' != '{other_value}'" + ) + if not differences: + return None + + return "\n".join(differences) + + @staticmethod + def from_dto(dto: ProtocolComponent) -> "ProtocolComponentExpectation": + return ProtocolComponentExpectation( + id=dto.id, + tokens=[t.hex() for t in dto.tokens], + static_attributes={ + key: value.hex() for key, value in dto.static_attributes.items() + }, + creation_tx=dto.creation_tx.hex(), + ) + + +class ProtocolComponentWithTestConfig(ProtocolComponentExpectation): + """Represents a ProtocolComponent with its main attributes and test configuration.""" + + skip_simulation: Optional[bool] = Field( + False, + description="Flag indicating whether to skip simulation for this component", + ) + + def into_protocol_component(self) -> ProtocolComponentExpectation: + return ProtocolComponentExpectation(**self.dict()) + + +class IntegrationTest(BaseModel): + """Configuration for an individual test.""" + + name: str = Field(..., description="Name of the test") + start_block: int = Field(..., description="Starting block number for the test") + stop_block: int = Field(..., description="Stopping block number for the test") + initialized_accounts: Optional[List[str]] = Field( + None, description="List of initialized account addresses" + ) + expected_components: List[ProtocolComponentWithTestConfig] = Field( + ..., description="List of protocol components expected in the indexed state" + ) + + +class IntegrationTestsConfig(BaseModel): + """Main integration test configuration.""" + + substreams_yaml_path: str = Field( + "./substreams.yaml", description="Path of the Substreams YAML file" + ) + adapter_contract: str = Field( + ..., description="Name of the SwapAdapter contract for this protocol" + ) + adapter_build_signature: Optional[str] = Field( + None, description="Signatre of the SwapAdapter constructor for this protocol" + ) + adapter_build_args: Optional[str] = Field( + None, description="Arguments for the SwapAdapter constructor for this protocol" + ) + initialized_accounts: Optional[List[str]] = Field( + None, + description="List of initialized account addresses. These accounts will be initialized for every tests", + ) + skip_balance_check: bool = Field( + ..., description="Flag to skip balance check for all tests" + ) + protocol_type_names: List[str] = Field( + ..., description="List of protocol type names for the tested protocol" + ) + tests: List[IntegrationTest] = Field(..., description="List of integration tests") diff --git a/testing/src/runner/runner.py b/testing/src/runner/runner.py index f39be57..8740f12 100644 --- a/testing/src/runner/runner.py +++ b/testing/src/runner/runner.py @@ -7,6 +7,7 @@ from collections import defaultdict from datetime import datetime from decimal import Decimal from pathlib import Path +from typing import List import yaml from protosim_py.evm.decoders import ThirdPartyPoolTychoDecoder @@ -27,6 +28,11 @@ from tycho_client.dto import ( ) from tycho_client.rpc_client import TychoRPCClient +from models import ( + IntegrationTestsConfig, + ProtocolComponentWithTestConfig, + ProtocolComponentExpectation, +) from adapter_handler import AdapterContractHandler from evm import get_token_balance, get_block_header from tycho import TychoRunner @@ -47,10 +53,10 @@ class TestResult: return cls(success=False, message=message) -def load_config(yaml_path: str) -> dict: - """Load YAML configuration from a specified file path.""" +def parse_config(yaml_path: str) -> IntegrationTestsConfig: with open(yaml_path, "r") as file: - return yaml.safe_load(file) + yaml_content = yaml.safe_load(file) + return IntegrationTestsConfig(**yaml_content) class SimulationFailure(BaseModel): @@ -66,13 +72,13 @@ class TestRunner: ): self.repo_root = os.getcwd() config_path = os.path.join( - self.repo_root, "substreams", package, "test_assets.yaml" + self.repo_root, "substreams", package, "integration_test.tycho.yaml" ) - self.config = load_config(config_path) + self.config: IntegrationTestsConfig = parse_config(config_path) self.spkg_src = os.path.join(self.repo_root, "substreams", package) self.adapters_src = os.path.join(self.repo_root, "evm") self.tycho_runner = TychoRunner( - db_url, with_binary_logs, self.config["initialized_accounts"] + db_url, with_binary_logs, self.config.initialized_accounts ) self.tycho_rpc_client = TychoRPCClient() self._token_factory_func = token_factory(self.tycho_rpc_client) @@ -83,32 +89,35 @@ class TestRunner: def run_tests(self) -> None: """Run all tests specified in the configuration.""" print(f"Running tests ...") - for test in self.config["tests"]: + for test in self.config.tests: self.tycho_runner.empty_database(self.db_url) spkg_path = self.build_spkg( - os.path.join(self.spkg_src, self.config["substreams_yaml_path"]), - lambda data: self.update_initial_block(data, test["start_block"]), + os.path.join(self.spkg_src, self.config.substreams_yaml_path), + lambda data: self.update_initial_block(data, test.start_block), ) self.tycho_runner.run_tycho( spkg_path, - test["start_block"], - test["stop_block"], - self.config["protocol_type_names"], - test.get("initialized_accounts", []), + test.start_block, + test.stop_block, + self.config.protocol_type_names, + test.initialized_accounts or [], ) result = self.tycho_runner.run_with_rpc_server( - self.validate_state, test["expected_state"], test["stop_block"] + self.validate_state, test.expected_components, test.stop_block ) if result.success: - print(f"✅ {test['name']} passed.") - + print(f"✅ {test.name} passed.") else: - print(f"❗️ {test['name']} failed: {result.message}") + print(f"❗️ {test.name} failed: {result.message}") - def validate_state(self, expected_state: dict, stop_block: int) -> TestResult: + def validate_state( + self, + expected_components: List[ProtocolComponentWithTestConfig], + stop_block: int, + ) -> TestResult: """Validate the current protocol state against the expected state.""" protocol_components = self.tycho_rpc_client.get_protocol_components( ProtocolComponentsParams(protocol_system="test_protocol") @@ -121,43 +130,18 @@ class TestRunner: } try: - for expected_component in expected_state.get("protocol_components", []): - comp_id = expected_component["id"].lower() + for expected_component in expected_components: + comp_id = expected_component.id.lower() if comp_id not in components_by_id: return TestResult.Failed( f"'{comp_id}' not found in protocol components." ) - # TODO: Manipulate pydantic objects instead of dict - component = components_by_id[comp_id].dict() - for key, value in expected_component.items(): - if key not in ["tokens", "static_attributes", "creation_tx"]: - continue - if key not in component: - return TestResult.Failed( - f"Missing '{key}' in component '{comp_id}'." - ) - if key == "tokens": - if set(map(HexBytes, value)) != set(component[key]): - return TestResult.Failed( - f"Token mismatch for key '{key}': {value} != {component[key]}" - ) - elif key == "creation_tx": - if HexBytes(value) != component[key]: - return TestResult.Failed( - f"Creation tx mismatch for key '{key}': {value} != {component[key]}" - ) - elif isinstance(value, list): - if set(map(str.lower, value)) != set( - map(str.lower, component[key]) - ): - return TestResult.Failed( - f"List mismatch for key '{key}': {value} != {component[key]}" - ) - elif value is not None and value.lower() != component[key]: - return TestResult.Failed( - f"Value mismatch for key '{key}': {value} != {component[key]}" - ) + diff = ProtocolComponentExpectation.from_dto( + components_by_id[comp_id] + ).compare(expected_component.into_protocol_component()) + if diff is not None: + return TestResult.Failed(diff) token_balances: dict[str, dict[HexBytes, int]] = defaultdict(dict) for component in protocol_components: @@ -178,7 +162,7 @@ class TestRunner: tycho_balance = int(balance_hex) token_balances[comp_id][token] = tycho_balance - if self.config["skip_balance_check"] is not True: + if self.config.skip_balance_check is not True: node_balance = get_token_balance(token, comp_id, stop_block) if node_balance != tycho_balance: return TestResult.Failed( @@ -198,11 +182,7 @@ class TestRunner: pc for pc in protocol_components if pc.id - in [ - c["id"].lower() - for c in expected_state["protocol_components"] - if c.get("skip_simulation", False) is False - ] + in [c.id for c in expected_components if c.skip_simulation is False] ] simulation_failures = self.simulate_get_amount_out( stop_block, protocol_states, filtered_components, contract_states @@ -231,7 +211,7 @@ class TestRunner: contract_states: list[ResponseAccount], ) -> dict[str, list[SimulationFailure]]: TychoDBSingleton.initialize() - protocol_type_names = self.config["protocol_type_names"] + protocol_type_names = self.config.protocol_type_names block_header = get_block_header(block_number) block: EVMBlock = EVMBlock( @@ -245,17 +225,17 @@ class TestRunner: adapter_contract = os.path.join( self.adapters_src, "out", - f"{self.config['adapter_contract']}.sol", - f"{self.config['adapter_contract']}.evm.runtime", + f"{self.config.adapter_contract}.sol", + f"{self.config.adapter_contract}.evm.runtime", ) if not os.path.exists(adapter_contract): print("Adapter contract not found. Building it ...") AdapterContractHandler.build_target( self.adapters_src, - self.config["adapter_contract"], - self.config["adapter_build_signature"], - self.config["adapter_build_args"], + self.config.adapter_contract, + self.config.adapter_build_signature, + self.config.adapter_build_args, ) decoder = ThirdPartyPoolTychoDecoder( diff --git a/testing/src/runner/tycho.py b/testing/src/runner/tycho.py index 43956fe..52512c4 100644 --- a/testing/src/runner/tycho.py +++ b/testing/src/runner/tycho.py @@ -82,11 +82,14 @@ class TychoRunner: "--stop-block", # +2 is to make up for the cache in the index side. str(end_block + 2), - "--initialization-block", - str(start_block), ] + ( - ["--initialized-accounts", ",".join(all_accounts)] + [ + "--initialized-accounts", + ",".join(all_accounts), + "--initialization-block", + str(start_block), + ] if all_accounts else [] ),