Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(CA): adding transaction simulation #859

Merged
merged 1 commit into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ export RPC_PROXY_PROVIDER_PIMLICO_API_KEY=""
export RPC_PROXY_PROVIDER_SOLSCAN_API_V2_TOKEN=""
export RPC_PROXY_PROVIDER_BUNGEE_API_KEY=""
export RPC_PROXY_PROVIDER_LAVA_API_KEY=""
export RPC_PROXY_PROVIDER_TENDERLY_API_KEY=""
export RPC_PROXY_PROVIDER_TENDERLY_ACCOUNT_ID=""
export RPC_PROXY_PROVIDER_TENDERLY_PROJECT_ID=""

# PostgreSQL URI connection string
export RPC_PROXY_POSTGRES_URI="postgres://postgres@localhost/postgres"
Expand Down
3 changes: 3 additions & 0 deletions .env.terraform.example
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ export TF_VAR_pimlico_api_key=""
export TF_VAR_solscan_api_v2_token=""
export TF_VAR_bungee_api_key=""
export TF_VAR_lava_api_key=""
export TF_VAR_tenderly_api_key=""
export TF_VAR_tenderly_account_id=""
export TF_VAR_tenderly_project_id=""
export TF_VAR_grafana_endpoint=$(aws grafana list-workspaces | jq -r '.workspaces[] | select( .tags.Env == "prod") | select( .tags.Name == "grafana-9") | .endpoint')
export TF_VAR_registry_api_auth_token=""
export TF_VAR_debug_secret=""
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/event_pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ jobs:
RPC_PROXY_PROVIDER_SOLSCAN_API_V2_TOKEN: ""
RPC_PROXY_PROVIDER_BUNGEE_API_KEY: ""
RPC_PROXY_PROVIDER_LAVA_API_KEY: ""
RPC_PROXY_PROVIDER_TENDERLY_API_KEY: ""
RPC_PROXY_PROVIDER_TENDERLY_ACCOUNT_ID: ""
RPC_PROXY_PROVIDER_TENDERLY_PROJECT_ID: ""
- run: docker logs mock-bundler-anvil-1
if: failure()
- run: docker logs mock-bundler-alto-1
Expand Down
12 changes: 12 additions & 0 deletions src/env/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,15 @@ mod test {
),
("RPC_PROXY_PROVIDER_BUNGEE_API_KEY", "BUNGEE_API_KEY"),
("RPC_PROXY_PROVIDER_LAVA_API_KEY", "LAVA_API_KEY"),
("RPC_PROXY_PROVIDER_TENDERLY_API_KEY", "TENDERLY_KEY"),
(
"RPC_PROXY_PROVIDER_TENDERLY_ACCOUNT_ID",
"TENDERLY_ACCOUNT_ID",
),
(
"RPC_PROXY_PROVIDER_TENDERLY_PROJECT_ID",
"TENDERLY_PROJECT_ID",
),
(
"RPC_PROXY_PROVIDER_PROMETHEUS_QUERY_URL",
"PROMETHEUS_QUERY_URL",
Expand Down Expand Up @@ -277,6 +286,9 @@ mod test {
solscan_api_v2_token: "SOLSCAN_API_V2_TOKEN".to_string(),
bungee_api_key: "BUNGEE_API_KEY".to_string(),
lava_api_key: "LAVA_API_KEY".to_string(),
tenderly_api_key: "TENDERLY_KEY".to_string(),
tenderly_account_id: "TENDERLY_ACCOUNT_ID".to_string(),
tenderly_project_id: "TENDERLY_PROJECT_ID".to_string(),
override_bundler_urls: None,
},
rate_limiting: RateLimitingConfig {
Expand Down
145 changes: 130 additions & 15 deletions src/handlers/chain_agnostic/mod.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
use {
crate::{error::RpcError, handlers::MessageSource, utils::crypto::get_erc20_contract_balance},
alloy::primitives::{Address, U256},
ethers::types::H160 as EthersH160,
crate::{
error::RpcError,
handlers::MessageSource,
providers::{
tenderly::{AssetChangeType, TokenStandard},
SimulationProvider,
},
utils::crypto::get_erc20_contract_balance,
},
alloy::primitives::{address, Address, B256, U256},
ethers::{types::H160 as EthersH160, utils::keccak256},
phf::phf_map,
serde::{Deserialize, Serialize},
std::{collections::HashMap, str::FromStr},
std::{collections::HashMap, str::FromStr, sync::Arc},
tracing::debug,
yttrium::chain_abstraction::api::Transaction,
};

pub mod route;
Expand All @@ -17,14 +27,14 @@ pub const BRIDGING_AMOUNT_SLIPPAGE: i8 = 2; // 2%
pub const BRIDGING_TIMEOUT: u64 = 1800; // 30 minutes

/// Available assets for Bridging
pub static BRIDGING_AVAILABLE_ASSETS: phf::Map<&'static str, phf::Map<&'static str, &'static str>> = phf_map! {
pub static BRIDGING_AVAILABLE_ASSETS: phf::Map<&'static str, phf::Map<&'static str, Address>> = phf_map! {
"USDC" => phf_map! {
// Optimism
"eip155:10" => "0x0b2c639c533813f4aa9d7837caf62653d097ff85",
"eip155:10" => address!("0b2c639c533813f4aa9d7837caf62653d097ff85"),
// Base
"eip155:8453" => "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"eip155:8453" => address!("833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"),
// Arbitrum
"eip155:42161" => "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
"eip155:42161" => address!("af88d065e77c8cC2239327C5EDb3A432268e5831"),
},
};
pub const USDC_DECIMALS: u8 = 6;
Expand Down Expand Up @@ -55,13 +65,24 @@ pub enum BridgingStatus {
Error,
}

/// Return available assets contracts addresses for the given chain_id
pub fn get_bridging_assets_contracts_for_chain(chain_id: &str) -> Vec<String> {
BRIDGING_AVAILABLE_ASSETS
.entries()
.filter_map(|(_token_symbol, chain_map)| {
chain_map
.entries()
.find(|(chain, _)| **chain == chain_id)
.map(|(_, contract_address)| contract_address.to_string())
})
.collect()
}

/// Check is the address is supported bridging asset and return the token symbol and decimals
pub fn find_supported_bridging_asset(chain_id: &str, contract: Address) -> Option<(String, u8)> {
for (symbol, chain_map) in BRIDGING_AVAILABLE_ASSETS.entries() {
for (chain, contract_address) in chain_map.entries() {
if *chain == chain_id
&& contract == Address::from_str(contract_address).unwrap_or_default()
{
if *chain == chain_id && contract == *contract_address {
return Some((symbol.to_string(), USDC_DECIMALS));
}
}
Expand Down Expand Up @@ -114,10 +135,7 @@ pub async fn check_bridging_for_erc20_transfer(
let mut contracts_per_chain: HashMap<(String, String), Vec<String>> = HashMap::new();
for (token_symbol, chain_map) in BRIDGING_AVAILABLE_ASSETS.entries() {
for (chain_id, contract_address) in chain_map.entries() {
if *chain_id == exclude_chain_id
&& Address::from_str(contract_address).unwrap_or_default()
== exclude_contract_address
{
if *chain_id == exclude_chain_id && *contract_address == exclude_contract_address {
continue;
}
contracts_per_chain
Expand Down Expand Up @@ -153,3 +171,100 @@ pub async fn check_bridging_for_erc20_transfer(
}
Ok(None)
}

/// Compute the simulation state override balance for a given balance
pub fn compute_simulation_balance(balance: u128) -> B256 {
let mut buf = [0u8; 32];
buf[16..32].copy_from_slice(&balance.to_be_bytes());
B256::from(buf)
}

/// Compute the storage slot for a given address and slot number to use in the
/// simulation state overrides
/// https://docs.tenderly.co/simulations/state-overrides#storage-slot-calculation
pub fn compute_simulation_storage_slot(address: Address, slot_number: u64) -> B256 {
let mut input = [0u8; 64];
// Place the address as the first 32-byte segment
input[0..32].copy_from_slice(address.into_word().as_slice());
// Place the u64 slot_number at the end of the second 32-byte segment
input[56..64].copy_from_slice(&slot_number.to_be_bytes());
B256::from(&keccak256(input))
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Erc20AssetChange {
pub chain_id: String,
pub asset_contract: Address,
pub amount: U256,
pub receiver: Address,
}

/// Get the ERC20 assets changes from the transaction simulation result
pub async fn get_assets_changes_from_simulation(
simulation_provider: Arc<dyn SimulationProvider>,
transaction: &Transaction,
) -> Result<Vec<Erc20AssetChange>, RpcError> {
// Fill the state overrides for the source address for each of the supported
// assets on the initial tx chain
let state_overrides = {
let mut state_overrides = HashMap::new();
let assets_contracts =
get_bridging_assets_contracts_for_chain(&transaction.chain_id.clone());
let mut account_state = HashMap::new();
account_state.insert(
// Since we are using only USDC for the bridging,
// we can hardcode the storage slot for the contract which is 9
compute_simulation_storage_slot(transaction.from, 9),
compute_simulation_balance(99000000000),
);
for contract in assets_contracts {
state_overrides.insert(
Address::from_str(&contract).unwrap_or_default(),
geekbrother marked this conversation as resolved.
Show resolved Hide resolved
account_state.clone(),
);
}
state_overrides
};

let simulation_result = &simulation_provider
.simulate_transaction(
transaction.chain_id.clone(),
transaction.from,
transaction.to,
transaction.data.clone(),
state_overrides,
)
.await?;

if simulation_result
.transaction
.transaction_info
.asset_changes
.is_none()
{
debug!("The transaction does not change any assets");
return Ok(vec![]);
}

let mut asset_changes = Vec::new();
for asset_changed in simulation_result
.transaction
.transaction_info
.asset_changes
.clone()
.unwrap_or_default()
{
if asset_changed.asset_type.clone() == AssetChangeType::Transfer
&& asset_changed.token_info.standard.clone() == TokenStandard::Erc20
{
asset_changes.push(Erc20AssetChange {
chain_id: transaction.chain_id.clone(),
asset_contract: asset_changed.token_info.contract_address,
amount: asset_changed.raw_amount,
receiver: asset_changed.to,
})
}
}

Ok(asset_changes)
}
Loading
Loading