From 2725d5c099a3f9c0fff5bec9705f3ebbc6420ad3 Mon Sep 17 00:00:00 2001
From: Dusan Stanivukovic <dusan.stanivukovic@gmail.com>
Date: Wed, 15 Jan 2025 13:26:06 +0100
Subject: [PATCH] Use traces to extract the input for `settlement::Observer`
 (#3233)

# Description
Implements first part of
https://github.com/cowprotocol/services/issues/3177

# Changes

- [ ] Uses `trace_transaction` function to get traces and extract the
calldata of `settle` call

## How to test
Existing tests show that no regression bugs were introduced.

Final e2e test should cover this part of the code. Although by manually
testing the `trace_transaction` I am pretty sure the code is correct.
<!--
## Related Issues

Fixes #
-->
---
 crates/autopilot/src/domain/eth/mod.rs        |  11 +-
 crates/autopilot/src/domain/settlement/mod.rs | 287 +++++++++++++++++-
 .../src/domain/settlement/observer.rs         |  15 +-
 .../src/domain/settlement/transaction/mod.rs  |  48 ++-
 crates/autopilot/src/infra/blockchain/mod.rs  |  20 +-
 5 files changed, 347 insertions(+), 34 deletions(-)

diff --git a/crates/autopilot/src/domain/eth/mod.rs b/crates/autopilot/src/domain/eth/mod.rs
index 9b187526ba..29ca478683 100644
--- a/crates/autopilot/src/domain/eth/mod.rs
+++ b/crates/autopilot/src/domain/eth/mod.rs
@@ -312,6 +312,13 @@ pub struct TradeEvent {
     pub order_uid: domain::OrderUid,
 }
 
+/// A trace of a Call type of action.
+#[derive(Debug, Clone, Default)]
+pub struct TraceCall {
+    pub to: Address,
+    pub input: Calldata,
+}
+
 /// Any type of on-chain transaction.
 #[derive(Debug, Clone, Default)]
 pub struct Transaction {
@@ -319,8 +326,6 @@ pub struct Transaction {
     pub hash: TxId,
     /// The address of the sender of the transaction.
     pub from: Address,
-    /// The call data of the transaction.
-    pub input: Calldata,
     /// The block number of the block that contains the transaction.
     pub block: BlockNo,
     /// The timestamp of the block that contains the transaction.
@@ -329,4 +334,6 @@ pub struct Transaction {
     pub gas: Gas,
     /// The effective gas price of the transaction.
     pub gas_price: EffectiveGasPrice,
+    /// Traces of all Calls contained in the transaction.
+    pub trace_calls: Vec<TraceCall>,
 }
diff --git a/crates/autopilot/src/domain/settlement/mod.rs b/crates/autopilot/src/domain/settlement/mod.rs
index 7908789719..dbcb4e14c4 100644
--- a/crates/autopilot/src/domain/settlement/mod.rs
+++ b/crates/autopilot/src/domain/settlement/mod.rs
@@ -29,8 +29,6 @@ pub struct Settlement {
     gas: eth::Gas,
     /// The effective gas price of the settlement transaction.
     gas_price: eth::EffectiveGasPrice,
-    /// The address of the solver that submitted the settlement transaction.
-    solver: eth::Address,
     /// The block number of the block that contains the settlement transaction.
     block: eth::BlockNo,
     /// The associated auction.
@@ -146,7 +144,6 @@ impl Settlement {
             .collect();
 
         Ok(Self {
-            solver: settled.solver,
             block: settled.block,
             gas: settled.gas,
             gas_price: settled.gas_price,
@@ -216,14 +213,246 @@ impl From<infra::persistence::DatabaseError> for Error {
 #[cfg(test)]
 mod tests {
     use {
-        crate::{
-            domain,
-            domain::{auction, eth},
-        },
+        crate::domain::{self, auction, eth},
         hex_literal::hex,
         std::collections::{HashMap, HashSet},
     };
 
+    // https://etherscan.io/tx/0x030623e438f28446329d8f4ff84db897907fcac59b9943b31b7be66f23c877af
+    // A transfer transaction that emits a settlement event, but it's not actually a
+    // swap.
+    #[test]
+    fn not_a_swap() {
+        let calldata = hex!(
+            "
+        13d79a0b
+        0000000000000000000000000000000000000000000000000000000000000080
+        00000000000000000000000000000000000000000000000000000000000000a0
+        00000000000000000000000000000000000000000000000000000000000000c0
+        00000000000000000000000000000000000000000000000000000000000000e0
+        0000000000000000000000000000000000000000000000000000000000000000
+        0000000000000000000000000000000000000000000000000000000000000000
+        0000000000000000000000000000000000000000000000000000000000000000
+        0000000000000000000000000000000000000000000000000000000000000060
+        0000000000000000000000000000000000000000000000000000000000000080
+        00000000000000000000000000000000000000000000000000000000000017a0
+        0000000000000000000000000000000000000000000000000000000000000000
+        0000000000000000000000000000000000000000000000000000000000000011
+        0000000000000000000000000000000000000000000000000000000000000220
+        0000000000000000000000000000000000000000000000000000000000000360
+        00000000000000000000000000000000000000000000000000000000000004a0
+        00000000000000000000000000000000000000000000000000000000000005e0
+        0000000000000000000000000000000000000000000000000000000000000720
+        0000000000000000000000000000000000000000000000000000000000000860
+        00000000000000000000000000000000000000000000000000000000000009a0
+        0000000000000000000000000000000000000000000000000000000000000ae0
+        0000000000000000000000000000000000000000000000000000000000000c20
+        0000000000000000000000000000000000000000000000000000000000000d60
+        0000000000000000000000000000000000000000000000000000000000000ea0
+        0000000000000000000000000000000000000000000000000000000000000fe0
+        0000000000000000000000000000000000000000000000000000000000001120
+        0000000000000000000000000000000000000000000000000000000000001260
+        00000000000000000000000000000000000000000000000000000000000013a0
+        00000000000000000000000000000000000000000000000000000000000014e0
+        0000000000000000000000000000000000000000000000000000000000001620
+        0000000000000000000000009008d19f58aabd9ed0d60971565aa8510560ab41
+        0000000000000000000000000000000000000000000000000000000000000000
+        0000000000000000000000000000000000000000000000000000000000000060
+        00000000000000000000000000000000000000000000000000000000000000a4
+        ec6cb13f00000000000000000000000000000000000000000000000000000000
+        0000004000000000000000000000000000000000000000000000000000000000
+        0000000100000000000000000000000000000000000000000000000000000000
+        000000388674df3c7f96ac76c6fa06813b758322b5b64ce14bf46f0f9b4ec6f2
+        d015ff9a9008d19f58aabd9ed0d60971565aa8510560ab41678716a000000000
+        0000000000000000000000000000000000000000000000000000000000000000
+        0000000000000000000000009008d19f58aabd9ed0d60971565aa8510560ab41
+        0000000000000000000000000000000000000000000000000000000000000000
+        0000000000000000000000000000000000000000000000000000000000000060
+        00000000000000000000000000000000000000000000000000000000000000a4
+        ec6cb13f00000000000000000000000000000000000000000000000000000000
+        0000004000000000000000000000000000000000000000000000000000000000
+        0000000100000000000000000000000000000000000000000000000000000000
+        000000383f19e12409d5913e40bfde35a1607a1a43f1f9d26e76dd2d9d409cc7
+        50125f769008d19f58aabd9ed0d60971565aa8510560ab41678716a000000000
+        0000000000000000000000000000000000000000000000000000000000000000
+        0000000000000000000000009008d19f58aabd9ed0d60971565aa8510560ab41
+        0000000000000000000000000000000000000000000000000000000000000000
+        0000000000000000000000000000000000000000000000000000000000000060
+        00000000000000000000000000000000000000000000000000000000000000a4
+        ec6cb13f00000000000000000000000000000000000000000000000000000000
+        0000004000000000000000000000000000000000000000000000000000000000
+        0000000100000000000000000000000000000000000000000000000000000000
+        00000038a91a488e41e46dedbe71ae0c0d9f41be8de9f45ffc62271970362777
+        9e302e8d9008d19f58aabd9ed0d60971565aa8510560ab41678716a000000000
+        0000000000000000000000000000000000000000000000000000000000000000
+        0000000000000000000000009008d19f58aabd9ed0d60971565aa8510560ab41
+        0000000000000000000000000000000000000000000000000000000000000000
+        0000000000000000000000000000000000000000000000000000000000000060
+        00000000000000000000000000000000000000000000000000000000000000a4
+        ec6cb13f00000000000000000000000000000000000000000000000000000000
+        0000004000000000000000000000000000000000000000000000000000000000
+        0000000100000000000000000000000000000000000000000000000000000000
+        0000003820621f141681025ffcbce03b51c3ea78c93da8739df9202210647968
+        6d2efad59008d19f58aabd9ed0d60971565aa8510560ab41678716a000000000
+        0000000000000000000000000000000000000000000000000000000000000000
+        0000000000000000000000009008d19f58aabd9ed0d60971565aa8510560ab41
+        0000000000000000000000000000000000000000000000000000000000000000
+        0000000000000000000000000000000000000000000000000000000000000060
+        00000000000000000000000000000000000000000000000000000000000000a4
+        ec6cb13f00000000000000000000000000000000000000000000000000000000
+        0000004000000000000000000000000000000000000000000000000000000000
+        0000000100000000000000000000000000000000000000000000000000000000
+        000000385846bbbcee39039678e529973cf6962b90d49663d5ff49220adfb240
+        4805d11e9008d19f58aabd9ed0d60971565aa8510560ab41678716a000000000
+        0000000000000000000000000000000000000000000000000000000000000000
+        0000000000000000000000009008d19f58aabd9ed0d60971565aa8510560ab41
+        0000000000000000000000000000000000000000000000000000000000000000
+        0000000000000000000000000000000000000000000000000000000000000060
+        00000000000000000000000000000000000000000000000000000000000000a4
+        ec6cb13f00000000000000000000000000000000000000000000000000000000
+        0000004000000000000000000000000000000000000000000000000000000000
+        0000000100000000000000000000000000000000000000000000000000000000
+        000000388a608a3e6fd6f95797d376ffa35ae077d78440b627dce2b3d2278e04
+        8d37f7c09008d19f58aabd9ed0d60971565aa8510560ab41678716a000000000
+        0000000000000000000000000000000000000000000000000000000000000000
+        0000000000000000000000009008d19f58aabd9ed0d60971565aa8510560ab41
+        0000000000000000000000000000000000000000000000000000000000000000
+        0000000000000000000000000000000000000000000000000000000000000060
+        00000000000000000000000000000000000000000000000000000000000000a4
+        ec6cb13f00000000000000000000000000000000000000000000000000000000
+        0000004000000000000000000000000000000000000000000000000000000000
+        0000000100000000000000000000000000000000000000000000000000000000
+        00000038436b1ae2723b42982a435ede8f92033fbdf1505e5dac97c1099b5ffd
+        fc1debda9008d19f58aabd9ed0d60971565aa8510560ab41678716a000000000
+        0000000000000000000000000000000000000000000000000000000000000000
+        0000000000000000000000009008d19f58aabd9ed0d60971565aa8510560ab41
+        0000000000000000000000000000000000000000000000000000000000000000
+        0000000000000000000000000000000000000000000000000000000000000060
+        00000000000000000000000000000000000000000000000000000000000000a4
+        ec6cb13f00000000000000000000000000000000000000000000000000000000
+        0000004000000000000000000000000000000000000000000000000000000000
+        0000000100000000000000000000000000000000000000000000000000000000
+        00000038c31724565adeb2d0e4d6a6980f45b9fd91bb0b858fa8c8ada300e697
+        45d1ab379008d19f58aabd9ed0d60971565aa8510560ab41678716a000000000
+        0000000000000000000000000000000000000000000000000000000000000000
+        0000000000000000000000009008d19f58aabd9ed0d60971565aa8510560ab41
+        0000000000000000000000000000000000000000000000000000000000000000
+        0000000000000000000000000000000000000000000000000000000000000060
+        00000000000000000000000000000000000000000000000000000000000000a4
+        ec6cb13f00000000000000000000000000000000000000000000000000000000
+        0000004000000000000000000000000000000000000000000000000000000000
+        0000000100000000000000000000000000000000000000000000000000000000
+        0000003812789b110b2bf7353d054d978dd30972f2f33e54c4c7b93a1f992d20
+        fd2f29619008d19f58aabd9ed0d60971565aa8510560ab41678716a000000000
+        0000000000000000000000000000000000000000000000000000000000000000
+        0000000000000000000000009008d19f58aabd9ed0d60971565aa8510560ab41
+        0000000000000000000000000000000000000000000000000000000000000000
+        0000000000000000000000000000000000000000000000000000000000000060
+        00000000000000000000000000000000000000000000000000000000000000a4
+        ec6cb13f00000000000000000000000000000000000000000000000000000000
+        0000004000000000000000000000000000000000000000000000000000000000
+        0000000100000000000000000000000000000000000000000000000000000000
+        00000038f527dd3ad4938713bff3fb23a62d7b454caf40bc80f3eeed77c3d269
+        d9ff8eb69008d19f58aabd9ed0d60971565aa8510560ab41678716a000000000
+        0000000000000000000000000000000000000000000000000000000000000000
+        0000000000000000000000009008d19f58aabd9ed0d60971565aa8510560ab41
+        0000000000000000000000000000000000000000000000000000000000000000
+        0000000000000000000000000000000000000000000000000000000000000060
+        00000000000000000000000000000000000000000000000000000000000000a4
+        ec6cb13f00000000000000000000000000000000000000000000000000000000
+        0000004000000000000000000000000000000000000000000000000000000000
+        0000000100000000000000000000000000000000000000000000000000000000
+        000000388a63a1ed3d671e94aafe29b9ad479340ee04ad1fb5796c73cb71b25a
+        e8e2b0a49008d19f58aabd9ed0d60971565aa8510560ab41678716a000000000
+        0000000000000000000000000000000000000000000000000000000000000000
+        0000000000000000000000009008d19f58aabd9ed0d60971565aa8510560ab41
+        0000000000000000000000000000000000000000000000000000000000000000
+        0000000000000000000000000000000000000000000000000000000000000060
+        00000000000000000000000000000000000000000000000000000000000000a4
+        ec6cb13f00000000000000000000000000000000000000000000000000000000
+        0000004000000000000000000000000000000000000000000000000000000000
+        0000000100000000000000000000000000000000000000000000000000000000
+        000000384acca1a9694919daee72c0f20b64a2c0103750c26ce9b25cc864609f
+        2f4977269008d19f58aabd9ed0d60971565aa8510560ab41678716a000000000
+        0000000000000000000000000000000000000000000000000000000000000000
+        0000000000000000000000009008d19f58aabd9ed0d60971565aa8510560ab41
+        0000000000000000000000000000000000000000000000000000000000000000
+        0000000000000000000000000000000000000000000000000000000000000060
+        00000000000000000000000000000000000000000000000000000000000000a4
+        ec6cb13f00000000000000000000000000000000000000000000000000000000
+        0000004000000000000000000000000000000000000000000000000000000000
+        0000000100000000000000000000000000000000000000000000000000000000
+        00000038bdbeba9388a200bfa8c3f153c31a539622e2994a2cc59bfdc8c562a3
+        2f20b9919008d19f58aabd9ed0d60971565aa8510560ab41678716a000000000
+        0000000000000000000000000000000000000000000000000000000000000000
+        0000000000000000000000009008d19f58aabd9ed0d60971565aa8510560ab41
+        0000000000000000000000000000000000000000000000000000000000000000
+        0000000000000000000000000000000000000000000000000000000000000060
+        00000000000000000000000000000000000000000000000000000000000000a4
+        ec6cb13f00000000000000000000000000000000000000000000000000000000
+        0000004000000000000000000000000000000000000000000000000000000000
+        0000000100000000000000000000000000000000000000000000000000000000
+        000000383649cc08adbf44018756fec310e9348d64139f33530166ef53f1834f
+        2f5c8c469008d19f58aabd9ed0d60971565aa8510560ab41678716a000000000
+        0000000000000000000000000000000000000000000000000000000000000000
+        0000000000000000000000009008d19f58aabd9ed0d60971565aa8510560ab41
+        0000000000000000000000000000000000000000000000000000000000000000
+        0000000000000000000000000000000000000000000000000000000000000060
+        00000000000000000000000000000000000000000000000000000000000000a4
+        ec6cb13f00000000000000000000000000000000000000000000000000000000
+        0000004000000000000000000000000000000000000000000000000000000000
+        0000000100000000000000000000000000000000000000000000000000000000
+        0000003864bee671badbb222a43006744c89b70a368ca34a356dcf41f755923e
+        aab5b4029008d19f58aabd9ed0d60971565aa8510560ab41678716a000000000
+        0000000000000000000000000000000000000000000000000000000000000000
+        0000000000000000000000009008d19f58aabd9ed0d60971565aa8510560ab41
+        0000000000000000000000000000000000000000000000000000000000000000
+        0000000000000000000000000000000000000000000000000000000000000060
+        00000000000000000000000000000000000000000000000000000000000000a4
+        ec6cb13f00000000000000000000000000000000000000000000000000000000
+        0000004000000000000000000000000000000000000000000000000000000000
+        0000000100000000000000000000000000000000000000000000000000000000
+        000000382285ea60762c9a58d407e5aa3b8c3287628290793bc7260b0e0324ed
+        6f8bb1269008d19f58aabd9ed0d60971565aa8510560ab41678716a000000000
+        0000000000000000000000000000000000000000000000000000000000000000
+        000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2
+        0000000000000000000000000000000000000000000000000000000000000000
+        0000000000000000000000000000000000000000000000000000000000000060
+        0000000000000000000000000000000000000000000000000000000000000044
+        a9059cbb000000000000000000000000a03be496e67ec29bc62f01a428683d7f
+        9c2049300000000000000000000000000000000000000000000000002136c5d9
+        c1570aef00000000000000000000000000000000000000000000000000000000
+        0000000000000000000000000000000000000000000000000000000000000000"
+        )
+        .to_vec();
+
+        let domain_separator = eth::DomainSeparator(hex!(
+            "c078f884a2676e1345748b1feace7b0abee5d00ecadb6e574dcdd109a63e8943"
+        ));
+        let settlement_contract = eth::Address(eth::H160::from_slice(&hex!(
+            "9008d19f58aabd9ed0d60971565aa8510560ab41"
+        )));
+        let transaction = super::transaction::Transaction::try_new(
+            &domain::eth::Transaction {
+                trace_calls: vec![domain::eth::TraceCall {
+                    to: settlement_contract,
+                    input: calldata.into(),
+                }],
+                ..Default::default()
+            },
+            &domain_separator,
+            settlement_contract,
+        )
+        .unwrap_err();
+
+        // These transfer transactions don't have the auction_id attached so overall bad
+        // calldata is expected
+        assert!(matches!(
+            transaction,
+            super::transaction::Error::Decoding(_)
+        ));
+    }
+
     // https://etherscan.io/tx/0xc48dc0d43ffb43891d8c3ad7bcf05f11465518a2610869b20b0b4ccb61497634
     #[test]
     fn settlement() {
@@ -306,12 +535,19 @@ mod tests {
         let domain_separator = eth::DomainSeparator(hex!(
             "c078f884a2676e1345748b1feace7b0abee5d00ecadb6e574dcdd109a63e8943"
         ));
-        let transaction = super::transaction::Transaction::new(
+        let settlement_contract = eth::Address(eth::H160::from_slice(&hex!(
+            "9008d19f58aabd9ed0d60971565aa8510560ab41"
+        )));
+        let transaction = super::transaction::Transaction::try_new(
             &domain::eth::Transaction {
-                input: calldata.into(),
+                trace_calls: vec![domain::eth::TraceCall {
+                    to: settlement_contract,
+                    input: calldata.into(),
+                }],
                 ..Default::default()
             },
             &domain_separator,
+            settlement_contract,
         )
         .unwrap();
 
@@ -442,12 +678,19 @@ mod tests {
         let domain_separator = eth::DomainSeparator(hex!(
             "c078f884a2676e1345748b1feace7b0abee5d00ecadb6e574dcdd109a63e8943"
         ));
-        let transaction = super::transaction::Transaction::new(
+        let settlement_contract = eth::Address(eth::H160::from_slice(&hex!(
+            "9008d19f58aabd9ed0d60971565aa8510560ab41"
+        )));
+        let transaction = super::transaction::Transaction::try_new(
             &domain::eth::Transaction {
-                input: calldata.into(),
+                trace_calls: vec![domain::eth::TraceCall {
+                    to: settlement_contract,
+                    input: calldata.into(),
+                }],
                 ..Default::default()
             },
             &domain_separator,
+            settlement_contract,
         )
         .unwrap();
 
@@ -610,12 +853,19 @@ mod tests {
         let domain_separator = eth::DomainSeparator(hex!(
             "c078f884a2676e1345748b1feace7b0abee5d00ecadb6e574dcdd109a63e8943"
         ));
-        let transaction = super::transaction::Transaction::new(
+        let settlement_contract = eth::Address(eth::H160::from_slice(&hex!(
+            "9008d19f58aabd9ed0d60971565aa8510560ab41"
+        )));
+        let transaction = super::transaction::Transaction::try_new(
             &domain::eth::Transaction {
-                input: calldata.into(),
+                trace_calls: vec![domain::eth::TraceCall {
+                    to: settlement_contract,
+                    input: calldata.into(),
+                }],
                 ..Default::default()
             },
             &domain_separator,
+            settlement_contract,
         )
         .unwrap();
 
@@ -784,12 +1034,19 @@ mod tests {
         let domain_separator = eth::DomainSeparator(hex!(
             "c078f884a2676e1345748b1feace7b0abee5d00ecadb6e574dcdd109a63e8943"
         ));
-        let transaction = super::transaction::Transaction::new(
+        let settlement_contract = eth::Address(eth::H160::from_slice(&hex!(
+            "9008d19f58aabd9ed0d60971565aa8510560ab41"
+        )));
+        let transaction = super::transaction::Transaction::try_new(
             &domain::eth::Transaction {
-                input: calldata.into(),
+                trace_calls: vec![domain::eth::TraceCall {
+                    to: settlement_contract,
+                    input: calldata.into(),
+                }],
                 ..Default::default()
             },
             &domain_separator,
+            settlement_contract,
         )
         .unwrap();
 
diff --git a/crates/autopilot/src/domain/settlement/observer.rs b/crates/autopilot/src/domain/settlement/observer.rs
index 631539f9ef..d142646728 100644
--- a/crates/autopilot/src/domain/settlement/observer.rs
+++ b/crates/autopilot/src/domain/settlement/observer.rs
@@ -66,7 +66,8 @@ impl Observer {
         let transaction = match self.eth.transaction(event.transaction).await {
             Ok(transaction) => {
                 let separator = self.eth.contracts().settlement_domain_separator();
-                settlement::Transaction::new(&transaction, separator)
+                let settlement_contract = self.eth.contracts().settlement().address().into();
+                settlement::Transaction::try_new(&transaction, separator, settlement_contract)
             }
             Err(err) => {
                 tracing::warn!(hash = ?event.transaction, ?err, "no tx found");
@@ -95,7 +96,17 @@ impl Observer {
                 (auction_id, settlement)
             }
             Err(err) => {
-                tracing::warn!(hash = ?event.transaction, ?err, "invalid settlement transaction");
+                match err {
+                    settlement::transaction::Error::MissingCalldata => {
+                        tracing::error!(hash = ?event.transaction, ?err, "invalid settlement transaction")
+                    }
+                    settlement::transaction::Error::MissingAuctionId
+                    | settlement::transaction::Error::Decoding(_)
+                    | settlement::transaction::Error::SignatureRecover(_)
+                    | settlement::transaction::Error::OrderUidRecover(_) => {
+                        tracing::warn!(hash = ?event.transaction, ?err, "invalid settlement transaction")
+                    }
+                }
                 // default values so we don't get stuck on invalid settlement transactions
                 (0.into(), None)
             }
diff --git a/crates/autopilot/src/domain/settlement/transaction/mod.rs b/crates/autopilot/src/domain/settlement/transaction/mod.rs
index 3b0e3f4540..d5877d00db 100644
--- a/crates/autopilot/src/domain/settlement/transaction/mod.rs
+++ b/crates/autopilot/src/domain/settlement/transaction/mod.rs
@@ -1,6 +1,10 @@
-use crate::{
-    boundary,
-    domain::{self, auction::order, eth},
+use {
+    crate::{
+        boundary,
+        domain::{self, auction::order, eth},
+    },
+    ethcontract::common::FunctionExt,
+    std::sync::LazyLock,
 };
 
 mod tokenized;
@@ -12,8 +16,6 @@ pub struct Transaction {
     pub hash: eth::TxId,
     /// The associated auction id.
     pub auction_id: domain::auction::Id,
-    /// The address of the solver that submitted the transaction.
-    pub solver: eth::Address,
     /// The block number of the block that contains the transaction.
     pub block: eth::BlockNo,
     /// The timestamp of the block that contains the transaction.
@@ -27,18 +29,33 @@ pub struct Transaction {
 }
 
 impl Transaction {
-    pub fn new(
+    pub fn try_new(
         transaction: &eth::Transaction,
         domain_separator: &eth::DomainSeparator,
+        settlement_contract: eth::Address,
     ) -> Result<Self, Error> {
+        // find trace call to settlement contract
+        let calldata = transaction
+            .trace_calls
+            .iter()
+            .find(|trace| is_settlement_trace(trace, settlement_contract))
+            .map(|trace| trace.input.clone())
+            // all transactions emitting settlement events should have a /settle call, 
+            // otherwise it's an execution client bug
+            .ok_or(Error::MissingCalldata)?;
+
         /// Number of bytes that may be appended to the calldata to store an
         /// auction id.
         const META_DATA_LEN: usize = 8;
 
-        let (data, metadata) = transaction
-            .input
-            .0
-            .split_at(transaction.input.0.len() - META_DATA_LEN);
+        let (data, metadata) = calldata.0.split_at(
+            calldata
+                .0
+                .len()
+                .checked_sub(META_DATA_LEN)
+                // should contain at least 4 bytes for function selector and 8 bytes for auction id
+                .ok_or(Error::MissingCalldata)?,
+        );
         let metadata: Option<[u8; META_DATA_LEN]> = metadata.try_into().ok();
         let auction_id = metadata
             .map(crate::domain::auction::Id::from_be_bytes)
@@ -46,7 +63,6 @@ impl Transaction {
         Ok(Self {
             hash: transaction.hash,
             auction_id,
-            solver: transaction.from,
             block: transaction.block,
             timestamp: transaction.timestamp,
             gas: transaction.gas,
@@ -116,6 +132,14 @@ impl Transaction {
     }
 }
 
+fn is_settlement_trace(trace: &eth::TraceCall, settlement_contract: eth::Address) -> bool {
+    static SETTLE_FUNCTION_SELECTOR: LazyLock<[u8; 4]> = LazyLock::new(|| {
+        let abi = &contracts::GPv2Settlement::raw_contract().interface.abi;
+        abi.function("settle").unwrap().selector()
+    });
+    trace.to == settlement_contract && trace.input.0.starts_with(&*SETTLE_FUNCTION_SELECTOR)
+}
+
 /// Trade containing onchain observable data specific to a settlement
 /// transaction.
 #[derive(Debug, Clone)]
@@ -152,6 +176,8 @@ pub struct ClearingPrices {
 
 #[derive(Debug, thiserror::Error)]
 pub enum Error {
+    #[error("settle calldata must exist for all transactions emitting settlement event")]
+    MissingCalldata,
     #[error("missing auction id")]
     MissingAuctionId,
     #[error(transparent)]
diff --git a/crates/autopilot/src/infra/blockchain/mod.rs b/crates/autopilot/src/infra/blockchain/mod.rs
index 6b8f3234cc..79f0ef1d75 100644
--- a/crates/autopilot/src/infra/blockchain/mod.rs
+++ b/crates/autopilot/src/infra/blockchain/mod.rs
@@ -103,9 +103,10 @@ impl Ethereum {
     }
 
     pub async fn transaction(&self, hash: eth::TxId) -> Result<eth::Transaction, Error> {
-        let (transaction, receipt) = tokio::try_join!(
+        let (transaction, receipt, traces) = tokio::try_join!(
             self.web3.eth().transaction(hash.0.into()),
-            self.web3.eth().transaction_receipt(hash.0)
+            self.web3.eth().transaction_receipt(hash.0),
+            self.web3.trace().transaction(hash.0),
         )?;
         let transaction = transaction.ok_or(Error::TransactionNotFound)?;
         let receipt = receipt.ok_or(Error::TransactionNotFound)?;
@@ -121,13 +122,15 @@ impl Ethereum {
             .block(block_hash.into())
             .await?
             .ok_or(Error::TransactionNotFound)?;
-        into_domain(transaction, receipt, block.timestamp).map_err(Error::IncompleteTransactionData)
+        into_domain(transaction, receipt, traces, block.timestamp)
+            .map_err(Error::IncompleteTransactionData)
     }
 }
 
 fn into_domain(
     transaction: web3::types::Transaction,
     receipt: web3::types::TransactionReceipt,
+    traces: Vec<web3::types::Trace>,
     timestamp: U256,
 ) -> anyhow::Result<eth::Transaction> {
     Ok(eth::Transaction {
@@ -136,7 +139,6 @@ fn into_domain(
             .from
             .ok_or(anyhow::anyhow!("missing from"))?
             .into(),
-        input: transaction.input.0.into(),
         block: receipt
             .block_number
             .ok_or(anyhow::anyhow!("missing block_number"))?
@@ -151,6 +153,16 @@ fn into_domain(
             .ok_or(anyhow::anyhow!("missing effective_gas_price"))?
             .into(),
         timestamp: timestamp.as_u32(),
+        trace_calls: traces
+            .into_iter()
+            .filter_map(|trace| match trace.action {
+                web3::types::Action::Call(call) => Some(eth::TraceCall {
+                    to: call.to.into(),
+                    input: call.input.0.into(),
+                }),
+                _ => None,
+            })
+            .collect(),
     })
 }