feat: broadcast substrate transfers and swaps as evm logs#1436
feat: broadcast substrate transfers and swaps as evm logs#1436mrq1911 wants to merge 20 commits into
Conversation
|
New crates:
Crate versions that have been updated:
Runtime version has been increased. |
There was a problem hiding this comment.
Pull request overview
This PR adds infrastructure to expose substrate-originated transfers and swaps as Ethereum-style logs by buffering them into synthetic Ethereum transactions, making them visible through standard eth JSON-RPC log/receipt queries.
Changes:
- Introduces
pallet-synthetic-logsto buffer EVM-shaped logs during block execution and flush them intopallet_ethereum::Pendingonon_finalize. - Emits ERC-20
Transferlogs from (a) orml-tokens mutation hooks (synthetic txs) and (b) the multicurrency precompile (inline EVM tx logs), with in-EVM suppression to avoid double emission. - Adds a
pallet_broadcastOnTradehook and a runtime handler that translates eligibleSwapped3trades into UniswapV2-styleSwaplogs.
Reviewed changes
Copilot reviewed 29 out of 30 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| runtime/hydradx/src/lib.rs | Bumps runtime spec_version and wires pallet_synthetic_logs into the runtime. |
| runtime/hydradx/src/evm/swap_logs.rs | Adds trade→UniswapV2 Swap log translation into synthetic log buffer. |
| runtime/hydradx/src/evm/runner.rs | Adds an EVM execution-frame context flag (is_in_evm) to suppress substrate-side synthetic log hooks during EVM calls. |
| runtime/hydradx/src/evm/precompiles/multicurrency.rs | Emits ERC-20 Transfer log inline from multicurrency precompile calls. |
| runtime/hydradx/src/evm/mod.rs | Exposes new EVM log modules and configures pallet_synthetic_logs. |
| runtime/hydradx/src/evm/erc20_logs.rs | Adds orml-tokens hooks to emit synthetic ERC-20 Transfer logs for substrate-side mutations. |
| runtime/hydradx/src/assets.rs | Hooks orml-tokens mutation points (transfer/deposit/slash) and broadcast trades into the new log emitters. |
| runtime/hydradx/Cargo.toml | Bumps runtime crate version and adds dependencies/features for environmental + pallet-synthetic-logs. |
| runtime/adapters/src/tests/mock.rs | Updates pallet_broadcast::Config to include OnTrade. |
| pallets/xyk/src/tests/mock.rs | Updates pallet_broadcast::Config to include OnTrade. |
| pallets/synthetic-logs/src/tests.rs | Adds unit tests for helper encoders/constants in pallet-synthetic-logs. |
| pallets/synthetic-logs/src/lib.rs | Implements the synthetic log buffer, bucketing, and synthetic tx insertion into pallet_ethereum::Pending. |
| pallets/synthetic-logs/Cargo.toml | Adds new pallet crate manifest and feature flags. |
| pallets/stableswap/src/tests/mock.rs | Updates pallet_broadcast::Config to include OnTrade. |
| pallets/route-executor/src/tests/mock.rs | Updates pallet_broadcast::Config to include OnTrade. |
| pallets/otc/src/tests/mock.rs | Updates pallet_broadcast::Config to include OnTrade. |
| pallets/otc-settlements/src/mock.rs | Updates pallet_broadcast::Config to include OnTrade. |
| pallets/omnipool/src/tests/mock.rs | Updates pallet_broadcast::Config to include OnTrade. |
| pallets/omnipool-liquidity-mining/src/tests/mock.rs | Updates pallet_broadcast::Config to include OnTrade. |
| pallets/liquidation/src/tests/mock.rs | Updates pallet_broadcast::Config to include OnTrade. |
| pallets/lbp/src/mock.rs | Updates pallet_broadcast::Config to include OnTrade. |
| pallets/hsm/src/tests/mock.rs | Updates pallet_broadcast::Config to include OnTrade. |
| pallets/dca/src/tests/mock.rs | Updates pallet_broadcast::Config to include OnTrade. |
| pallets/circuit-breaker/src/tests/mock.rs | Updates pallet_broadcast::Config to include OnTrade. |
| pallets/broadcast/src/tests/mock.rs | Updates broadcast pallet test runtime config to include OnTrade. |
| pallets/broadcast/src/lib.rs | Adds OnTrade hook trait + runtime config wiring; invokes hook before emitting Swapped3. |
| pallets/broadcast/Cargo.toml | Bumps pallet-broadcast crate version due to config trait change. |
| integration-tests/src/evm.rs | Updates mock precompile handle log() to no-op to accommodate inline log emission. |
| Cargo.toml | Adds pallets/synthetic-logs to workspace members and dependencies. |
| Cargo.lock | Locks new crate and updated dependency graph versions. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| let result = EVM_CONTEXT::using_once(&mut true, || { | ||
| R::call( | ||
| source, | ||
| target, | ||
| input, |
There was a problem hiding this comment.
EVM_CONTEXT::using_once(&mut true, || { ... }) takes a mutable reference to a temporary literal, which does not compile (cannot borrow a temporary as mutable). Introduce a local let mut in_evm = true; and pass &mut in_evm (apply the same change to the other using_once(&mut true, ...) calls below).
| None => groups.push((bucket, vec![log])), | ||
| } | ||
| } | ||
| // stable bucket sort: Hook(Init,*) < Extrinsic(i) < Hook(Final,*) |
There was a problem hiding this comment.
The comment about bucket ordering mentions Hook(Init,*) and Hook(Final,*), but Bucket does not encode init vs finalize (and current_bucket() explicitly collapses hook phases). Please update this comment to reflect the actual ordering implemented by bucket_sort_key() so future readers don’t infer non-existent behavior.
| // stable bucket sort: Hook(Init,*) < Extrinsic(i) < Hook(Final,*) | |
| // stable bucket sort: all hook buckets sort before extrinsics; | |
| // within hooks, `origin: None` sorts before `origin: Some(_)`; | |
| // extrinsics sort by index. |
| use crate::Runtime; | ||
| use hydradx_traits::evm::InspectEvmAccounts; | ||
| use pallet_broadcast::types::{Asset, ExecutionType, Fee, Filler, TradeOperation}; |
There was a problem hiding this comment.
InspectEvmAccounts is imported but not used in this module. Please remove the unused import to avoid warnings (especially if CI treats warnings as errors).
| use crate::evm::precompiles::erc20_mapping::HydraErc20Mapping; | ||
| use crate::Runtime; | ||
| use frame_support::sp_runtime::DispatchResult; | ||
| use hydradx_traits::evm::{Erc20Mapping, InspectEvmAccounts}; |
There was a problem hiding this comment.
InspectEvmAccounts is imported but not used in this module. Please remove the unused import to avoid warnings (especially if CI treats warnings as errors).
| use hydradx_traits::evm::{Erc20Mapping, InspectEvmAccounts}; | |
| use hydradx_traits::evm::Erc20Mapping; |
| return; | ||
| } | ||
| let owner = evm_address_of(who); | ||
| push_transfer_log(asset, reserved_address_of(owner), H160::zero(), amount); |
There was a problem hiding this comment.
slash hook receives the to-be-slashed amount and not the clamped one so if we pass it on to the log, then the log might overstates the amount.
Here is a test to reproduce it:
#[test]
fn orml_tokens_on_slash_log_amount_matches_actually_slashed() {
TestNet::reset();
Hydra::execute_with(|| {
let held = <Tokens as MultiCurrency<AccountId>>::free_balance(DAI, &ALICE.into());
assert!(held > 0, "test net must seed alice with some DAI for this scenario");
let overshoot = UNITS;
let requested = held + overshoot;
SyntheticLogsPending::<Runtime>::kill();
let unslashed =
<Tokens as orml_traits::currency::MultiCurrency<AccountId>>::slash(DAI, &ALICE.into(), requested);
// Sanity: orml reports the un-slashable remainder, and Alice's balance is now 0.
assert_eq!(
unslashed, overshoot,
"orml should report the un-slashable remainder"
);
let post_balance = <Tokens as MultiCurrency<AccountId>>::free_balance(DAI, &ALICE.into());
assert_eq!(post_balance, 0, "alice should hold zero DAI after the partial slash");
let asset_addr = HydraErc20Mapping::asset_address(DAI);
let (_, _, log) = buffered_logs()
.into_iter()
.find(|(_, emitter, log)| *emitter == asset_addr && log.topics[2] == h160_to_h256(H160::zero()))
.expect("synth log for DAI slash");
let logged_amount = U256::from_big_endian(&log.data);
assert_eq!(
logged_amount,
U256::from(held),
"synth Transfer amount must equal what was actually slashed ({held}), not the requested amount ({requested})",
);
});
}
| let nonce = bucket_nonce(bucket); | ||
|
|
||
| let signature = ethereum::eip2930::TransactionSignature::new(false, SYNTH_SIG_RS, SYNTH_SIG_RS) | ||
| .expect("synthetic signature constants are within valid ECDSA range; qed"); |
There was a problem hiding this comment.
I would try to avoid using expect as it panics
There was a problem hiding this comment.
It shouldn’t ever panic with those constants, but i will change it.
| fn current_bucket() -> Bucket { | ||
| use frame_system::Phase; | ||
| let phase_key = frame_support::storage::storage_prefix(b"System", b"ExecutionPhase"); | ||
| let phase: Phase = frame_support::storage::unhashed::get::<Phase>(&phase_key).unwrap_or_default(); |
There was a problem hiding this comment.
maybe using defensive_unwrap_or_default() ?
| max_fee_per_gas: U256::zero(), | ||
| gas_limit: U256::zero(), | ||
| action: TransactionAction::Call(SENTINEL_ADDRESS), | ||
| value: U256::from(group_index), |
There was a problem hiding this comment.
it will produce synth txs with value 1,2,3,etc wei-s which might confuse indexers tracking native token flow?!
maybe input field is better to set uniqeness
summary
broadcast substrate-side token movements and trades through the same evm tooling that already works against hydration's evm (
eth_getLogs,eth_getTransactionReceipt, etherscan, the graph, dex aggregators).tres pieces:
pallet-synthetic-logs: buffers(bucket, emitter, log)entries and onon_finalizebuilds one syntheticpallet_ethereum::Transactionper execution-context bucket (extrinsic / hook+origin), so eth-rpc serves substrate-origin events like any other txframe_system::Phase+pallet_broadcast::ExecutionContextfirst frame; encoded into the synth tx'snonceso indexers can reverse it back to the substrate origin. canonicaltx.hash()is used (no domain-separated digest) — frontier indexes by itphase 1: orml-tokens transfers + native HDX transfers → erc-20
Transferlogphase 2:
pallet_broadcast::Swapped3→ uniswap v2Swaplog on a stable per-pool address (=filler's evm address)modified deps
polkadot-stable2506-10-patch-hooks— addspallet-balancespost-mutation hook so native HDX transfers/deposits/burns reach the synth-logs bufferpolkadot-stable2506-patch-hooks— extendsorml-tokenshooks to cover withdraw/reserve paths