Improve README, add foundry to docker, add handler to build targets
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
SUBSTREAMS_PATH=../substreams/ethereum-curve
|
export SUBSTREAMS_PACKAGE=ethereum-curve
|
||||||
RPC_URL=https://mainnet.infura.io/v3/your-infura-key
|
export RPC_URL=https://mainnet.infura.io/v3/your-infura-key
|
||||||
DATABASE_URL: "postgres://postgres:mypassword@db:5432/tycho_indexer_0"
|
export DATABASE_URL: "postgres://postgres:mypassword@db:5432/tycho_indexer_0"
|
||||||
SUBSTREAMS_API_TOKEN="changeme"
|
export SUBSTREAMS_API_TOKEN="changeme"
|
||||||
DOMAIN_OWNER="AWSAccountId"
|
export DOMAIN_OWNER="AWSAccountId"
|
||||||
@@ -4,6 +4,14 @@ FROM --platform=linux/amd64 continuumio/miniconda3:24.4.0-0
|
|||||||
# Set the working directory in the container to /app
|
# Set the working directory in the container to /app
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install Foundry
|
||||||
|
RUN apt-get update && apt-get install -y curl
|
||||||
|
RUN curl -L https://foundry.paradigm.xyz | bash
|
||||||
|
RUN /bin/bash -c "source $HOME/.bashrc && $HOME/.foundry/bin/foundryup"
|
||||||
|
#
|
||||||
|
# Add Foundry to PATH
|
||||||
|
ENV PATH /root/.foundry/bin:$PATH
|
||||||
|
|
||||||
# Add current directory code to /app in container
|
# Add current directory code to /app in container
|
||||||
ADD . /app/testing
|
ADD . /app/testing
|
||||||
|
|
||||||
@@ -22,7 +30,7 @@ RUN apt-get update \
|
|||||||
&& apt-get clean
|
&& apt-get clean
|
||||||
|
|
||||||
ARG PIP_INDEX_URL
|
ARG PIP_INDEX_URL
|
||||||
RUN /bin/bash -c "source activate myenv && cd testing && pip install --no-cache-dir -r src/requirements.txt && cd -"
|
RUN /bin/bash -c "source activate myenv && cd testing && pip install --no-cache-dir -r requirements.txt && cd -"
|
||||||
|
|
||||||
# Make port 80 available to the world outside this container
|
# Make port 80 available to the world outside this container
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
|
|||||||
@@ -33,9 +33,27 @@ Please place this Runtime file under the respective `substream` directory inside
|
|||||||
Export the required environment variables for the execution. You can find the available environment variables in the `.env.default` file.
|
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.
|
Please create a `.env` file in the `testing` directory and set the required environment variables.
|
||||||
|
|
||||||
The variable SUBSTREAMS_PATH should be a relative reference to the directory containing the Substreams module that you want to test.
|
#### Environment Variables
|
||||||
|
|
||||||
Example: `SUBSTREAMS_PATH=../substreams/ethereum-curve`
|
**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).
|
||||||
|
- **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: Build and the Testing Script
|
### Step 2: Build and the Testing Script
|
||||||
|
|
||||||
|
|||||||
0
testing/__init__.py
Normal file
0
testing/__init__.py
Normal file
@@ -20,7 +20,6 @@ services:
|
|||||||
args:
|
args:
|
||||||
PIP_INDEX_URL: ${PIP_INDEX_URL}
|
PIP_INDEX_URL: ${PIP_INDEX_URL}
|
||||||
volumes:
|
volumes:
|
||||||
# - ${SUBSTREAMS_PATH}:/app/substreams/my_substream
|
|
||||||
- ../substreams:/app/substreams
|
- ../substreams:/app/substreams
|
||||||
- ../proto:/app/proto
|
- ../proto:/app/proto
|
||||||
- ./tycho-indexer:/bin/tycho-indexer
|
- ./tycho-indexer:/bin/tycho-indexer
|
||||||
@@ -33,13 +32,15 @@ services:
|
|||||||
- db
|
- db
|
||||||
env_file:
|
env_file:
|
||||||
- ".env"
|
- ".env"
|
||||||
|
environment:
|
||||||
|
- DATABASE_URL=postgres://postgres:mypassword@db:5432/tycho_indexer_0
|
||||||
command:
|
command:
|
||||||
- "python"
|
- "python"
|
||||||
- "testing/src/runner/cli.py"
|
- "testing/src/runner/cli.py"
|
||||||
- "--package"
|
- "--package"
|
||||||
- "ethereum-balancer"
|
- ${SUBSTREAMS_PACKAGE}
|
||||||
- "--with_binary_logs"
|
- "--tycho-logs"
|
||||||
- "--db_url"
|
- "--db-url"
|
||||||
- "postgres://postgres:mypassword@db:5432/tycho_indexer_0"
|
- "postgres://postgres:mypassword@db:5432/tycho_indexer_0"
|
||||||
volumes:
|
volumes:
|
||||||
postgres_data:
|
postgres_data:
|
||||||
@@ -12,7 +12,7 @@ set +a
|
|||||||
# Check if DOMAIN_OWNER is set
|
# Check if DOMAIN_OWNER is set
|
||||||
if [ -z "$DOMAIN_OWNER" ]; then
|
if [ -z "$DOMAIN_OWNER" ]; then
|
||||||
echo "DOMAIN_OWNER environment variable is not set."
|
echo "DOMAIN_OWNER environment variable is not set."
|
||||||
exit 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Fetch the CODEARTIFACT_AUTH_TOKEN
|
# Fetch the CODEARTIFACT_AUTH_TOKEN
|
||||||
|
|||||||
44
testing/src/runner/adapter_handler.py
Normal file
44
testing/src/runner/adapter_handler.py
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
|
class AdapterContractHandler:
|
||||||
|
@staticmethod
|
||||||
|
def build_target(
|
||||||
|
src_path: str,
|
||||||
|
adapter_contract: str,
|
||||||
|
signature: Optional[str],
|
||||||
|
args: Optional[str],
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Runs the buildRuntime Bash script in a subprocess with the provided arguments.
|
||||||
|
|
||||||
|
:param src_path: Path to the script to be executed.
|
||||||
|
:param adapter_contract: The contract name to be passed to the script.
|
||||||
|
:param signature: The constructor signature to be passed to the script.
|
||||||
|
:param args: The constructor arguments to be passed to the script.
|
||||||
|
"""
|
||||||
|
|
||||||
|
script_path = "scripts/buildRuntime.sh"
|
||||||
|
cmd = [script_path, "-c", adapter_contract]
|
||||||
|
if signature:
|
||||||
|
cmd.extend(["-s", signature, "-a", args])
|
||||||
|
try:
|
||||||
|
# Running the bash script with the provided arguments
|
||||||
|
result = subprocess.run(
|
||||||
|
[script_path, "-c", adapter_contract, "-s", signature, "-a", args],
|
||||||
|
cwd=src_path,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Print standard output and error for debugging
|
||||||
|
print("Output:\n", result.stdout)
|
||||||
|
if result.stderr:
|
||||||
|
print("Errors:\n", result.stderr)
|
||||||
|
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
print(f"An error occurred: {e}")
|
||||||
|
print("Error Output:\n", e.stderr)
|
||||||
@@ -10,6 +10,8 @@ import traceback
|
|||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from adapter_handler import AdapterContractHandler
|
||||||
from tycho_client.decoders import ThirdPartyPoolTychoDecoder
|
from tycho_client.decoders import ThirdPartyPoolTychoDecoder
|
||||||
from tycho_client.models import Blockchain, EVMBlock
|
from tycho_client.models import Blockchain, EVMBlock
|
||||||
from tycho_client.tycho_adapter import TychoPoolStateStreamAdapter
|
from tycho_client.tycho_adapter import TychoPoolStateStreamAdapter
|
||||||
@@ -46,13 +48,19 @@ class SimulationFailure(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class TestRunner:
|
class TestRunner:
|
||||||
def __init__(self, package: str, with_binary_logs: bool, db_url: str, vm_traces: bool):
|
def __init__(
|
||||||
|
self, package: str, with_binary_logs: bool, db_url: str, vm_traces: bool
|
||||||
|
):
|
||||||
self.repo_root = os.getcwd()
|
self.repo_root = os.getcwd()
|
||||||
config_path = os.path.join(self.repo_root, "substreams", package, "test_assets.yaml")
|
config_path = os.path.join(
|
||||||
|
self.repo_root, "substreams", package, "test_assets.yaml"
|
||||||
|
)
|
||||||
self.config = load_config(config_path)
|
self.config = load_config(config_path)
|
||||||
self.spkg_src = os.path.join(self.repo_root, "substreams", package)
|
self.spkg_src = os.path.join(self.repo_root, "substreams", package)
|
||||||
self.adapters_src = os.path.join(self.repo_root, "evm")
|
self.adapters_src = os.path.join(self.repo_root, "evm")
|
||||||
self.tycho_runner = TychoRunner(db_url, with_binary_logs, self.config["initialized_accounts"])
|
self.tycho_runner = TychoRunner(
|
||||||
|
db_url, with_binary_logs, self.config["initialized_accounts"]
|
||||||
|
)
|
||||||
self.tycho_rpc_client = TychoRPCClient()
|
self.tycho_rpc_client = TychoRPCClient()
|
||||||
self.db_url = db_url
|
self.db_url = db_url
|
||||||
self._vm_traces = vm_traces
|
self._vm_traces = vm_traces
|
||||||
@@ -113,7 +121,7 @@ class TestRunner:
|
|||||||
)
|
)
|
||||||
if isinstance(value, list):
|
if isinstance(value, list):
|
||||||
if set(map(str.lower, value)) != set(
|
if set(map(str.lower, value)) != set(
|
||||||
map(str.lower, component[key])
|
map(str.lower, component[key])
|
||||||
):
|
):
|
||||||
return TestResult.Failed(
|
return TestResult.Failed(
|
||||||
f"List mismatch for key '{key}': {value} != {component[key]}"
|
f"List mismatch for key '{key}': {value} != {component[key]}"
|
||||||
@@ -151,15 +159,20 @@ class TestRunner:
|
|||||||
f"from rpc call and {tycho_balance} from Substreams"
|
f"from rpc call and {tycho_balance} from Substreams"
|
||||||
)
|
)
|
||||||
contract_states = self.tycho_rpc_client.get_contract_state()
|
contract_states = self.tycho_rpc_client.get_contract_state()
|
||||||
filtered_components = {'protocol_components': [pc for pc in protocol_components["protocol_components"] if
|
filtered_components = {
|
||||||
pc["id"] in [c["id"].lower() for c in
|
"protocol_components": [
|
||||||
expected_state["protocol_components"] if
|
pc
|
||||||
c.get("skip_simulation", False) is False]]}
|
for pc in protocol_components["protocol_components"]
|
||||||
|
if pc["id"]
|
||||||
|
in [
|
||||||
|
c["id"].lower()
|
||||||
|
for c in expected_state["protocol_components"]
|
||||||
|
if c.get("skip_simulation", False) is False
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
simulation_failures = self.simulate_get_amount_out(
|
simulation_failures = self.simulate_get_amount_out(
|
||||||
stop_block,
|
stop_block, protocol_states, filtered_components, contract_states
|
||||||
protocol_states,
|
|
||||||
filtered_components,
|
|
||||||
contract_states,
|
|
||||||
)
|
)
|
||||||
if len(simulation_failures):
|
if len(simulation_failures):
|
||||||
error_msgs = []
|
error_msgs = []
|
||||||
@@ -178,11 +191,11 @@ class TestRunner:
|
|||||||
return TestResult.Failed(error_message)
|
return TestResult.Failed(error_message)
|
||||||
|
|
||||||
def simulate_get_amount_out(
|
def simulate_get_amount_out(
|
||||||
self,
|
self,
|
||||||
block_number: int,
|
block_number: int,
|
||||||
protocol_states: dict,
|
protocol_states: dict,
|
||||||
protocol_components: dict,
|
protocol_components: dict,
|
||||||
contract_state: dict,
|
contract_state: dict,
|
||||||
) -> dict[str, list[SimulationFailure]]:
|
) -> dict[str, list[SimulationFailure]]:
|
||||||
protocol_type_names = self.config["protocol_type_names"]
|
protocol_type_names = self.config["protocol_type_names"]
|
||||||
|
|
||||||
@@ -196,10 +209,24 @@ class TestRunner:
|
|||||||
failed_simulations: dict[str, list[SimulationFailure]] = dict()
|
failed_simulations: dict[str, list[SimulationFailure]] = dict()
|
||||||
for protocol in protocol_type_names:
|
for protocol in protocol_type_names:
|
||||||
adapter_contract = os.path.join(
|
adapter_contract = os.path.join(
|
||||||
self.adapters_src, "out", f"{self.config['adapter_contract']}.sol",
|
self.adapters_src,
|
||||||
f"{self.config['adapter_contract']}.evm.runtime"
|
"out",
|
||||||
|
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"],
|
||||||
|
)
|
||||||
|
|
||||||
|
decoder = ThirdPartyPoolTychoDecoder(
|
||||||
|
adapter_contract, 0, trace=self._vm_traces
|
||||||
)
|
)
|
||||||
decoder = ThirdPartyPoolTychoDecoder(adapter_contract, 0, trace=self._vm_traces)
|
|
||||||
stream_adapter = TychoPoolStateStreamAdapter(
|
stream_adapter = TychoPoolStateStreamAdapter(
|
||||||
tycho_url="0.0.0.0:4242",
|
tycho_url="0.0.0.0:4242",
|
||||||
protocol=protocol,
|
protocol=protocol,
|
||||||
@@ -216,10 +243,12 @@ class TestRunner:
|
|||||||
if not pool_state.balances:
|
if not pool_state.balances:
|
||||||
raise ValueError(f"Missing balances for pool {pool_id}")
|
raise ValueError(f"Missing balances for pool {pool_id}")
|
||||||
for sell_token, buy_token in itertools.permutations(
|
for sell_token, buy_token in itertools.permutations(
|
||||||
pool_state.tokens, 2
|
pool_state.tokens, 2
|
||||||
):
|
):
|
||||||
# Try to sell 0.1% of the protocol balance
|
# Try to sell 0.1% of the protocol balance
|
||||||
sell_amount = Decimal("0.001") * pool_state.balances[sell_token.address]
|
sell_amount = (
|
||||||
|
Decimal("0.001") * pool_state.balances[sell_token.address]
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
amount_out, gas_used, _ = pool_state.get_amount_out(
|
amount_out, gas_used, _ = pool_state.get_amount_out(
|
||||||
sell_token, sell_amount, buy_token
|
sell_token, sell_amount, buy_token
|
||||||
|
|||||||
Reference in New Issue
Block a user