Skip to content

Commit

Permalink
Merge pull request #153 from propeller-heads/execution/tnl/ENG-4271-b…
Browse files Browse the repository at this point in the history
…ase-quickstart

feat: Configurable chain in quickstart
  • Loading branch information
tamaralipows authored Mar 3, 2025
2 parents e8a5b58 + efa639f commit 42925aa
Show file tree
Hide file tree
Showing 10 changed files with 94 additions and 62 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/tests-and-lints-template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ permissions:

env:
CARGO_TERM_COLOR: always
ETH_RPC_URL: ${{ secrets.eth_rpc_url }}
RPC_URL: ${{ secrets.eth_rpc_url }}

jobs:
compile_and_test:
Expand Down
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ lazy_static = "1.4.0"
# Tycho dependencies
tycho-core = { git = "https://github.com/propeller-heads/tycho-indexer.git", package = "tycho-core", tag = "0.56.5" }
tycho-client = { git = "https://github.com/propeller-heads/tycho-indexer.git", package = "tycho-client", tag = "0.56.5" }
tycho-execution = { git = "https://github.com/propeller-heads/tycho-execution.git", package = "tycho-execution", features = ["evm"], rev = "6f572eed01552f4a43181187cfef0c49d0fd9d80" }
tycho-execution = { git = "https://github.com/propeller-heads/tycho-execution.git", package = "tycho-execution", features = ["evm"], tag = "0.58.0" }

# EVM dependencies
foundry-config = { git = "https://github.com/foundry-rs/foundry", rev = "57bb12e", optional = true }
Expand Down
2 changes: 1 addition & 1 deletion examples/price_printer/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ quotes from each pool.
## How to run

```bash
export ETH_RPC_URL=<your-node-rpc-url>
export RPC_URL=<your-node-rpc-url>
cargo run --release --example price_printer -- --tvl-threshold 1000 --chain <ethereum | base>
```
11 changes: 7 additions & 4 deletions examples/quickstart/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,20 @@ This quickstart guide enables you to:
## How to run

```bash
export ETH_RPC_URL=<your-eth-rpc-url>
export RPC_URL=<your-rpc-url>
cargo run --release --example quickstart
```

By default, the example will trade 1 WETH -> USDC. If you want a different trade you can do:
By default, the example will trade 1 WETH -> USDC on Ethereum Mainnet. If you want a different trade or chain,
you can do:

```bash
cargo run --release --example quickstart -- --sell-token "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" --buy-token "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" --sell-amount 10
export TYCHO_URL=<tycho-api-url-for-chain>
export TYCHO_API_KEY=<tycho-api-key-for-chain>
cargo run --release --example quickstart -- --sell-token "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" --buy-token "0x4200000000000000000000000000000000000006" --sell-amount 10 --chain "base"
```

for 10000 USDC -> WBTC.
for 10 USDC -> WETH on Base.

To be able to execute or simulate the best swap, you need to pass your private key:

Expand Down
123 changes: 76 additions & 47 deletions examples/quickstart/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::{
default::Default,
env,
str::FromStr,
sync::LazyLock,
};

use alloy::{
Expand All @@ -19,10 +20,11 @@ use alloy::{
signers::local::PrivateKeySigner,
transports::http::{Client, Http},
};
use alloy_primitives::{Address, Bytes as AlloyBytes, B256, U256};
use alloy_primitives::{Address, Bytes as AlloyBytes, TxKind, B256, U256};
use alloy_sol_types::SolValue;
use clap::Parser;
use dialoguer::{theme::ColorfulTheme, Select};
use foundry_config::NamedChain;
use futures::StreamExt;
use num_bigint::BigUint;
use num_traits::ToPrimitive;
Expand All @@ -37,14 +39,10 @@ use tycho_execution::encoding::{
};
use tycho_simulation::{
evm::{
engine_db::tycho_db::PreCachedDB,
protocol::{
filters::{balancer_pool_filter, uniswap_v4_pool_with_hook_filter},
u256_num::biguint_to_u256,
uniswap_v2::state::UniswapV2State,
uniswap_v3::state::UniswapV3State,
filters::uniswap_v4_pool_with_hook_filter, u256_num::biguint_to_u256,
uniswap_v2::state::UniswapV2State, uniswap_v3::state::UniswapV3State,
uniswap_v4::state::UniswapV4State,
vm::state::EVMPoolState,
},
stream::ProtocolStreamBuilder,
},
Expand All @@ -57,6 +55,23 @@ use tycho_simulation::{

const FAKE_PK: &str = "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234";

static ROUTER_ADDRESSES: LazyLock<HashMap<Chain, Bytes>> = LazyLock::new(|| {
HashMap::from([
(
Chain::Ethereum,
"0x023eea66B260FA2E109B0764774837629cC41FeF"
.parse::<Bytes>()
.expect("Failed to create router address"),
),
(
Chain::Base,
"0x94ebf984511b06bab48545495b754760bfaa566e"
.parse::<Bytes>()
.expect("Failed to create router address"),
),
])
});

#[derive(Parser)]
struct Cli {
#[arg(short, long, default_value = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2")]
Expand All @@ -70,6 +85,8 @@ struct Cli {
tvl_threshold: f64,
#[arg(short, long, default_value = FAKE_PK)]
swapper_pk: String,
#[arg(short, long, default_value = "ethereum")]
chain: String,
}

#[tokio::main]
Expand All @@ -87,15 +104,12 @@ async fn main() {
let cli = Cli::parse();
let tvl_filter = ComponentFilter::with_tvl_range(cli.tvl_threshold, cli.tvl_threshold);

let all_tokens = load_all_tokens(
tycho_url.as_str(),
false,
Some(tycho_api_key.as_str()),
Chain::Ethereum,
None,
None,
)
.await;
let chain = Chain::from_str(&cli.chain).expect("Invalid chain");
println!("Loading tokens from Tycho... {}", tycho_url.as_str());
let all_tokens =
load_all_tokens(tycho_url.as_str(), false, Some(tycho_api_key.as_str()), chain, None, None)
.await;
println!("Tokens loaded: {}", all_tokens.len());

let sell_token_address =
Bytes::from_str(&cli.sell_token).expect("Invalid address for sell token");
Expand All @@ -118,14 +132,9 @@ async fn main() {
let mut pairs: HashMap<String, ProtocolComponent> = HashMap::new();
let mut amounts_out: HashMap<String, BigUint> = HashMap::new();

let mut protocol_stream = ProtocolStreamBuilder::new(&tycho_url, Chain::Ethereum)
let mut protocol_stream = ProtocolStreamBuilder::new(&tycho_url, chain)
.exchange::<UniswapV2State>("uniswap_v2", tvl_filter.clone(), None)
.exchange::<UniswapV3State>("uniswap_v3", tvl_filter.clone(), None)
.exchange::<EVMPoolState<PreCachedDB>>(
"vm:balancer_v2",
tvl_filter.clone(),
Some(balancer_pool_filter),
)
.exchange::<UniswapV4State>(
"uniswap_v4",
tvl_filter.clone(),
Expand All @@ -141,7 +150,7 @@ async fn main() {

// Initialize the encoder
let encoder = EVMEncoderBuilder::new()
.chain(Chain::Ethereum)
.chain(chain)
.initialize_tycho_router_with_permit2(cli.swapper_pk.clone())
.expect("Failed to create encoder builder")
.build()
Expand All @@ -152,14 +161,15 @@ async fn main() {
)
.expect("Failed to private key signer");
let tx_signer = EthereumWallet::from(wallet.clone());

let named_chain = NamedChain::from_str(&cli.chain).expect("Invalid chain");
let provider = ProviderBuilder::new()
.with_chain(named_chain)
.wallet(tx_signer.clone())
.on_http(
env::var("ETH_RPC_URL")
.expect("ETH_RPC_URL env var not set")
env::var("RPC_URL")
.expect("RPC_URL env var not set")
.parse()
.expect("Failed to parse ETH_RPC_URL"),
.expect("Failed to parse RPC_URL"),
);

while let Some(message_result) = protocol_stream.next().await {
Expand Down Expand Up @@ -194,6 +204,7 @@ async fn main() {
amount_in.clone(),
Bytes::from(wallet.address().to_vec()),
expected_amount,
chain,
);

if cli.swapper_pk == FAKE_PK {
Expand Down Expand Up @@ -227,6 +238,7 @@ async fn main() {
wallet.address(),
Address::from_slice(&sell_token_address),
tx.clone(),
named_chain as u64,
)
.await;

Expand Down Expand Up @@ -274,6 +286,7 @@ async fn main() {
wallet.address(),
&sell_token_address,
tx.clone(),
named_chain as u64,
)
.await
{
Expand All @@ -289,6 +302,7 @@ async fn main() {
}
}
}
// println!("Full simulation logs: {:?}", output);
return;
}
"execute" => {
Expand All @@ -298,6 +312,7 @@ async fn main() {
wallet.address(),
&sell_token_address,
tx,
named_chain as u64,
)
.await
{
Expand Down Expand Up @@ -397,6 +412,7 @@ fn get_best_swap(
}
}

#[allow(clippy::too_many_arguments)]
fn encode(
encoder: EVMTychoEncoder,
component: ProtocolComponent,
Expand All @@ -405,6 +421,7 @@ fn encode(
sell_amount: BigUint,
user_address: Bytes,
expected_amount: BigUint,
chain: Chain,
) -> Transaction {
// Prepare data to encode. First we need to create a swap object
let simple_swap = Swap::new(
Expand All @@ -415,6 +432,10 @@ fn encode(
// the amount or the total remaining balance.
0f64,
);
let router_address = ROUTER_ADDRESSES
.get(&chain)
.expect("Router address not found")
.clone();

// Then we create a solution object with the previous swap
let solution = Solution {
Expand All @@ -428,8 +449,7 @@ fn encode(
exact_out: false, // it's an exact in solution
checked_amount: None, // the amount out will not be checked in execution
swaps: vec![simple_swap],
router_address: Bytes::from_str("0x023eea66B260FA2E109B0764774837629cC41FeF")
.expect("Failed to create router address"),
router_address,
..Default::default()
};

Expand All @@ -451,6 +471,7 @@ async fn get_tx_requests(
user_address: Address,
sell_token_address: Address,
tx: Transaction,
chain_id: u64,
) -> (TransactionRequest, TransactionRequest) {
let block = provider
.get_block_by_number(BlockNumberOrTag::Latest, false)
Expand All @@ -477,25 +498,31 @@ async fn get_tx_requests(
.await
.expect("Failed to get nonce");

let approval_request = TransactionRequest::default()
.from(user_address)
.to(sell_token_address)
.input(TransactionInput { input: Some(AlloyBytes::from(data)), data: None })
.gas_limit(100_000u64)
.max_fee_per_gas(max_fee_per_gas.into())
.max_priority_fee_per_gas(max_priority_fee_per_gas.into())
.nonce(nonce);

let swap_request = TransactionRequest::default()
.to(Address::from_slice(&tx.to))
.from(user_address)
.value(biguint_to_u256(&tx.value))
.input(TransactionInput { input: Some(AlloyBytes::from(tx.data)), data: None })
.max_fee_per_gas(max_fee_per_gas.into())
.max_priority_fee_per_gas(max_priority_fee_per_gas.into())
.gas_limit(300_000u64)
.nonce(nonce + 1);
let approval_request = TransactionRequest {
to: Some(TxKind::Call(sell_token_address)),
from: Some(user_address),
value: None,
input: TransactionInput { input: Some(AlloyBytes::from(data)), data: None },
gas: Some(100_000u64),
chain_id: Some(chain_id),
max_fee_per_gas: Some(max_fee_per_gas.into()),
max_priority_fee_per_gas: Some(max_priority_fee_per_gas.into()),
nonce: Some(nonce),
..Default::default()
};

let swap_request = TransactionRequest {
to: Some(TxKind::Call(Address::from_slice(&tx.to))),
from: Some(user_address),
value: Some(biguint_to_u256(&tx.value)),
input: TransactionInput { input: Some(AlloyBytes::from(tx.data)), data: None },
gas: Some(300_000u64),
chain_id: Some(chain_id),
max_fee_per_gas: Some(max_fee_per_gas.into()),
max_priority_fee_per_gas: Some(max_priority_fee_per_gas.into()),
nonce: Some(nonce + 1),
..Default::default()
};
(approval_request, swap_request)
}

Expand Down Expand Up @@ -535,6 +562,7 @@ async fn execute_swap_transaction(
wallet_address: Address,
sell_token_address: &Bytes,
tx: Transaction,
chain_id: u64,
) -> Result<(), Box<dyn std::error::Error>> {
println!("Executing by performing an approval (for permit2) and a swap transaction...");
let (approval_request, swap_request) = get_tx_requests(
Expand All @@ -543,6 +571,7 @@ async fn execute_swap_transaction(
wallet_address,
Address::from_slice(sell_token_address),
tx.clone(),
chain_id,
)
.await;

Expand Down
4 changes: 2 additions & 2 deletions src/evm/engine_db/simulation_db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -465,9 +465,9 @@ mod tests {

fn get_client() -> Arc<RootProvider<BoxTransport>> {
let runtime = get_runtime().unwrap();
let eth_rpc_url = env::var("ETH_RPC_URL").unwrap_or_else(|_| {
let eth_rpc_url = env::var("RPC_URL").unwrap_or_else(|_| {
dotenv().expect("Missing .env file");
env::var("ETH_RPC_URL").expect("Missing ETH_RPC_URL in .env file")
env::var("RPC_URL").expect("Missing RPC_URL in .env file")
});
let client = runtime.block_on(async {
ProviderBuilder::new()
Expand Down
2 changes: 1 addition & 1 deletion src/evm/protocol/vm/erc20_token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ mod tests {

fn new_state() -> SimulationDB<RootProvider<BoxTransport>> {
dotenv().ok();
let eth_rpc_url = env::var("ETH_RPC_URL").expect("Missing ETH_RPC_URL in environment");
let eth_rpc_url = env::var("RPC_URL").expect("Missing RPC_URL in environment");
let runtime = tokio::runtime::Handle::try_current()
.is_err()
.then(|| tokio::runtime::Runtime::new().unwrap())
Expand Down
4 changes: 2 additions & 2 deletions src/evm/protocol/vm/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -444,9 +444,9 @@ mod tests {
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
#[cfg_attr(not(feature = "network_tests"), ignore)]
async fn test_get_code_for_address() {
let rpc_url = env::var("ETH_RPC_URL").unwrap_or_else(|_| {
let rpc_url = env::var("RPC_URL").unwrap_or_else(|_| {
dotenv().expect("Missing .env file");
env::var("ETH_RPC_URL").expect("Missing ETH_RPC_URL in .env file")
env::var("RPC_URL").expect("Missing RPC_URL in .env file")
});

let address = "0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640";
Expand Down
2 changes: 1 addition & 1 deletion src/evm/simulation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -620,7 +620,7 @@ mod tests {
}
fn new_state() -> SimulationDB<RootProvider<BoxTransport>> {
dotenv().ok();
let eth_rpc_url = env::var("ETH_RPC_URL").expect("Missing ETH_RPC_URL in environment");
let eth_rpc_url = env::var("RPC_URL").expect("Missing RPC_URL in environment");
let runtime = tokio::runtime::Handle::try_current()
.is_err()
.then(|| tokio::runtime::Runtime::new().unwrap())
Expand Down

0 comments on commit 42925aa

Please sign in to comment.