Skip to content

Instantly share code, notes, and snippets.

@isSerge
Last active May 1, 2025 04:14
Show Gist options
  • Select an option

  • Save isSerge/74d63e672faf7f22bc938ad236c27c1b to your computer and use it in GitHub Desktop.

Select an option

Save isSerge/74d63e672faf7f22bc938ad236c27c1b to your computer and use it in GitHub Desktop.
Template variable key collision when a function/event argument is named "signature"
pub async fn handle_match<T: TriggerExecutionServiceTrait>(
matching_monitor: MonitorMatch,
trigger_service: &T,
trigger_scripts: &HashMap<String, (ScriptLanguage, String)>,
) -> Result<(), FilterError> {
match &matching_monitor {
MonitorMatch::EVM(evm_monitor_match) => {
let transaction = evm_monitor_match.transaction.clone();
// If sender does not exist, we replace with 0x0000000000000000000000000000000000000000
let sender = transaction.sender().unwrap_or(&Address::ZERO);
// Convert transaction data to a HashMap
let mut data = HashMap::new();
data.insert(
"transaction_hash".to_string(),
b256_to_string(*transaction.hash()),
);
data.insert("transaction_from".to_string(), h160_to_string(*sender));
data.insert(
"transaction_value".to_string(),
transaction.value().to_string(),
);
if let Some(to) = transaction.to() {
data.insert("transaction_to".to_string(), h160_to_string(*to));
}
data.insert(
"monitor_name".to_string(),
evm_monitor_match.monitor.name.clone(),
);
let matched_on: HashMap<String, String> = {
let matched_on = &evm_monitor_match.matched_on;
let mut map = HashMap::new();
for (idx, func) in matched_on.functions.iter().enumerate() {
map.insert(
format!("function_{}_signature", idx),
func.signature.clone(),
);
}
for (idx, event) in matched_on.events.iter().enumerate() {
map.insert(format!("event_{}_signature", idx), event.signature.clone());
}
map
};
data.extend(matched_on);
let matched_args: HashMap<String, String> =
if let Some(args) = &evm_monitor_match.matched_on_args {
let mut map = HashMap::new();
if let Some(functions) = &args.functions {
for (idx, func) in functions.iter().enumerate() {
if let Some(func_args) = &func.args {
for arg in func_args {
map.insert(
format!("function_{}_{}", idx, arg.name),
arg.value.clone(),
);
}
}
}
}
if let Some(events) = &args.events {
for (idx, event) in events.iter().enumerate() {
if let Some(event_args) = &event.args {
for arg in event_args {
map.insert(
format!("event_{}_{}", idx, arg.name),
arg.value.clone(),
);
}
}
}
}
map
} else {
HashMap::new()
};
data.extend(matched_args);
// Swallow any errors since it's logged in the trigger service and we want to continue
// processing other matches
let _ = trigger_service
.execute(
&evm_monitor_match
.monitor
.triggers
.iter()
.map(|s| s.to_string())
.collect::<Vec<_>>(),
data,
&matching_monitor,
trigger_scripts,
)
.await;
}
MonitorMatch::Stellar(stellar_monitor_match) => {
let transaction = stellar_monitor_match.transaction.clone();
// Convert transaction data to a HashMap
let mut data = HashMap::new();
data.insert(
"transaction_hash".to_string(),
transaction.hash().to_string(),
);
// TODO: Add sender and value to the data so it can be used in the body template of the
// trigger data.insert(
// "transaction_from".to_string(),
// transaction.sender().to_string(),
// );
// data.insert(
// "transaction_value".to_string(),
// transaction.value().to_string(),
// );
// if let Some(to) = transaction.to() {
// data.insert("transaction_to".to_string(), to.to_string());
// }
data.insert(
"monitor_name".to_string(),
stellar_monitor_match.monitor.name.clone(),
);
let matched_on: HashMap<String, String> = {
let matched_on = &stellar_monitor_match.matched_on;
let mut map = HashMap::new();
for (idx, func) in matched_on.functions.iter().enumerate() {
map.insert(
format!("function_{}_signature", idx),
func.signature.clone(),
);
}
for (idx, event) in matched_on.events.iter().enumerate() {
map.insert(format!("event_{}_signature", idx), event.signature.clone());
}
map
};
data.extend(matched_on);
let matched_args: HashMap<String, String> =
if let Some(args) = &stellar_monitor_match.matched_on_args {
let mut map = HashMap::new();
if let Some(functions) = &args.functions {
for (idx, func) in functions.iter().enumerate() {
if let Some(func_args) = &func.args {
for arg in func_args {
map.insert(
format!("function_{}_{}", idx, arg.name),
arg.value.clone(),
);
}
}
}
}
if let Some(events) = &args.events {
for (idx, event) in events.iter().enumerate() {
if let Some(event_args) = &event.args {
for arg in event_args {
map.insert(
format!("event_{}_{}", idx, arg.name),
arg.value.clone(),
);
}
}
}
}
map
} else {
HashMap::new()
};
data.extend(matched_args);
// Swallow any errors since it's logged in the trigger service and we want to continue
// processing other matches
let _ = trigger_service
.execute(
&stellar_monitor_match
.monitor
.triggers
.iter()
.map(|s| s.to_string())
.collect::<Vec<_>>(),
data,
&matching_monitor,
trigger_scripts,
)
.await;
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
models::{
EVMMatchArguments, EVMMatchParamEntry, EVMMatchParamsMap, EVMMonitorMatch,
FunctionCondition, MatchConditions, Monitor,
},
services::{
filter::evm_test_helpers::{TestReceiptBuilder, TestTransactionBuilder},
trigger::{TriggerError, TriggerExecutionServiceTrait},
},
utils::tests::evm::monitor::MonitorBuilder,
};
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
// Mock trigger service for testing
struct MockTriggerService {
captured_data: std::sync::Arc<std::sync::Mutex<HashMap<String, String>>>,
}
#[async_trait::async_trait]
impl TriggerExecutionServiceTrait for MockTriggerService {
async fn execute(
&self,
_triggers: &[String],
data: HashMap<String, String>,
_matching_monitor: &MonitorMatch,
_trigger_scripts: &HashMap<String, (ScriptLanguage, String)>,
) -> Result<(), TriggerError> {
let mut captured = self.captured_data.lock().unwrap();
// Simulate capturing the data that would be used for templating
*captured = data;
Ok(())
}
async fn load_scripts(
&self,
_monitors: &[Monitor],
) -> Result<HashMap<String, (ScriptLanguage, String)>, TriggerError> {
// Return empty scripts for testing
Ok(HashMap::new())
}
}
/// Create a mock EVM monitor match for testing.
fn create_mock_evm_monitor_match(monitor: Monitor) -> EVMMonitorMatch {
let transaction = TestTransactionBuilder::new().build();
let receipt = TestReceiptBuilder::new().build();
EVMMonitorMatch {
monitor,
transaction,
receipt,
network_slug: "ethereum_mainnet".to_string(),
// Initialize empty, will be filled in by the test
matched_on: MatchConditions {
functions: vec![],
events: vec![],
transactions: vec![],
},
// Initialize empty, will be filled in by the test
matched_on_args: None,
}
}
#[tokio::test]
async fn test_key_collision_between_function_signature_and_arg_signature() {
// Define elements for the test
let function_signature_string = "dangerousFunc(bytes32 signature)".to_string();
let argument_name_colliding = "signature".to_string();
let argument_value_string = "0xarg_value_colliding_with_signature".to_string();
let expected_colliding_key = "function_0_signature".to_string();
// Create monitor with function that has "signature" parameter
let mut monitor = MonitorBuilder::new().build();
monitor.match_conditions.functions.push(FunctionCondition {
signature: "dangerousFunc(bytes32 signature)".to_string(),
expression: None,
});
// Create match with argument named "signature"
let mut evm_match = create_mock_evm_monitor_match(monitor);
// Set the `matched_on` conditions. This generates the *first* value for the key
evm_match.matched_on = MatchConditions {
functions: vec![FunctionCondition {
signature: function_signature_string.clone(), // This value should be overwritten
expression: None,
}],
events: vec![],
transactions: vec![],
};
// Set the `matched_on_args` conditions. This generates the *second* value for the key
evm_match.matched_on_args = Some(EVMMatchArguments {
functions: Some(vec![EVMMatchParamsMap {
signature: function_signature_string.clone(),
args: Some(vec![EVMMatchParamEntry {
name: argument_name_colliding.clone(), // The argument causing the collision
value: argument_value_string.clone(), // The value that will overwrite
kind: "bytes32".to_string(),
indexed: false,
}]),
hex_signature: Some("0xdeadbeef".to_string()),
}]),
events: None,
});
let match_wrapper = MonitorMatch::EVM(Box::new(evm_match));
// // Create mock execution service
let captured_data = Arc::new(Mutex::new(HashMap::new()));
let mock_service = MockTriggerService {
captured_data: captured_data.clone(),
};
// Process the match
handle_match(match_wrapper, &mock_service, &HashMap::new())
.await
.unwrap();
let data = captured_data.lock().unwrap();
// Verify key exists
assert!(
data.contains_key(&expected_colliding_key),
"The key '{}' should exist in the final data map.",
expected_colliding_key
);
// Verify value is the argument value, not the function signature
let actual_value = data.get(&expected_colliding_key).unwrap();
assert_eq!(
actual_value,
&argument_value_string,
"Collision Confirmed! The value for key '{}' should be the argument value '{}', but it seems to be overwritten. Expected original value was '{}'.",
expected_colliding_key,
argument_value_string,
function_signature_string
);
// Verify other expected keys are present (sanity check)
assert!(data.contains_key("transaction_hash"));
assert!(data.contains_key("monitor_name"));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment