use super::*;
use alloy::primitives::{keccak256, Signature};
use crate::service::test_rpc::{ok, TestRpc};
use serde_json::{json, Value};
use std::sync::atomic::{AtomicUsize, Ordering};
struct TestSigner(AtomicUsize);
#[async_trait::async_trait]
impl Signer for TestSigner {
fn address(&self) -> Address { Address::repeat_byte(1) }
async fn sign_message(&self, _: &[u8]) -> Result<Signature> { panic!("unexpected message signing") }
async fn sign_hash(&self, _: B256) -> Result<Signature> {
self.0.fetch_add(1, Ordering::SeqCst);
Ok(Signature::from_scalars_and_parity(B256::repeat_byte(1), B256::repeat_byte(2), false))
}
}
#[test]
fn owner_envelope_and_digest_use_the_settler_domain() {
let config = evm::chain_config("1").unwrap();
let order = super::super::tests::build_order_for_record();
let signature = Bytes::from(vec![7; 65]);
let envelope = owner_envelope(signature.clone());
let decoded = order_types::IntentAbiCodec::encodeEnvelopeCall::abi_decode_raw(&envelope).unwrap();
assert_eq!(decoded.kind, 0);
assert_eq!(Bytes::abi_decode(&decoded.payload).unwrap(), signature);
let digest = owner_digest(config, &order);
assert_ne!(digest, owner_digest(evm::chain_config("8453").unwrap(), &order));
let mut wrong_settler = config;
wrong_settler.settler = Address::ZERO;
assert_ne!(digest, owner_digest(wrong_settler, &order));
assert_eq!(digest, order_types::signing_hash(&order, &eip712_domain! {
name: "AgentSwap IntentSettler", version: "3", chain_id: 1,
verifying_contract: config.settler,
}));
}
#[test]
fn ethereum_owner_place_checks_parity_and_self_submits_to_v7() {
if std::env::var_os("ETHEREUM_OWNER_TEST_CHILD").is_some() {
tokio::runtime::Runtime::new().unwrap().block_on(exercise_place());
return;
}
for bad_digest in [false, true] {
let rpc = TestRpc::start(move |body| Some(reply(body, bad_digest)));
let output = std::process::Command::new(std::env::current_exe().unwrap())
.args(["ethereum_owner_place_checks_parity_and_self_submits_to_v7", "--nocapture"])
.env("ETHEREUM_OWNER_TEST_CHILD", if bad_digest { "bad" } else { "good" })
.env("AGENTSWAP_RPC_URL_1", &rpc.url).output().unwrap();
assert!(output.status.success(), "{}\n{}", String::from_utf8_lossy(&output.stdout), String::from_utf8_lossy(&output.stderr));
assert_eq!(rpc.called("eth_sendRawTransaction"), usize::from(!bad_digest));
}
}
async fn exercise_place() {
let signer = Arc::new(TestSigner(AtomicUsize::new(0)));
let relay = Client::new("http://127.0.0.1:1", None);
let announcer = Announcer { relay: &relay, wait: submit::Wait::MCP, pacer: None };
let mut input = super::super::tests::input("USDC", "1");
input.chain_id = "1".into();
input.to = "WETH".into();
input.proxy_owner = format!("{:?}", signer.address());
input.self_submit = true;
input.duration_secs = Some(600);
let bad = std::env::var("ETHEREUM_OWNER_TEST_CHILD").unwrap() == "bad";
let result = super::super::place(announcer, input.clone(), signer.clone(), true).await;
if bad {
assert!(result.unwrap_err().to_string().contains("does not match the settler"));
assert_eq!(signer.0.load(Ordering::SeqCst), 0);
return;
}
let preview = result.unwrap();
assert!(preview.dry_run && preview.signature.is_none() && preview.authorization.is_none());
assert_eq!(signer.0.load(Ordering::SeqCst), 0);
input.dry_run = false;
let forced = super::super::place(announcer, input.clone(), signer.clone(), false).await.unwrap();
assert!(forced.dry_run && forced.signature.is_none());
let outcome = super::super::place(announcer, input.clone(), signer.clone(), true).await.unwrap();
assert_eq!(outcome.tx_status, Some(submit::TxStatus::Confirmed));
assert!(outcome.tx_explorer_url.unwrap().starts_with("https://etherscan.io/tx/"));
assert!(outcome.authorization.is_none() && outcome.relay.is_none());
assert_eq!(signer.0.load(Ordering::SeqCst), 2);
input.deadline_secs = Some(120);
let error = super::super::place(announcer, input.clone(), signer.clone(), true).await.unwrap_err();
assert!(error.to_string().contains("--deadline-secs applies to agent authorization"));
input.deadline_secs = None;
input.proxy_owner = format!("{:?}", Address::ZERO);
let error = super::super::place(announcer, input, signer.clone(), true).await.unwrap_err();
assert!(error.to_string().contains("require the owner signer"));
assert_eq!(signer.0.load(Ordering::SeqCst), 2);
}
fn reply(body: &Value, bad_digest: bool) -> Value {
let config = evm::chain_config("1").unwrap();
let value = match body["method"].as_str().unwrap() {
"eth_call" => return contract_reply(body, bad_digest),
"eth_chainId" => json!("0x1"),
"eth_getTransactionCount" => json!("0x0"),
"eth_estimateGas" => {
assert_eq!(body["params"][0]["to"], format!("{:?}", config.settler));
json!("0x30000")
}
"eth_feeHistory" => json!({"oldestBlock": "0x1", "baseFeePerGas": ["0x3b9aca00", "0x3b9aca00"],
"gasUsedRatio": [0.5], "reward": [["0x3b9aca00"]]}),
"eth_sendRawTransaction" => {
let raw = hex::decode(body["params"][0].as_str().unwrap().trim_start_matches("0x")).unwrap();
json!(keccak256(raw))
}
"eth_getTransactionReceipt" => json!({
"type": "0x2", "status": "0x1", "cumulativeGasUsed": "0x5208", "logs": [],
"logsBloom": format!("0x{}", "0".repeat(512)), "transactionHash": body["params"][0],
"transactionIndex": "0x0", "blockHash": format!("0x{}", "11".repeat(32)), "blockNumber": "0x1",
"gasUsed": "0x5208", "effectiveGasPrice": "0x3b9aca00", "from": Address::repeat_byte(1),
"to": config.settler, "contractAddress": null,
}),
method => panic!("unexpected method: {method}"),
};
ok(body, value)
}
fn contract_reply(body: &Value, bad_digest: bool) -> Value {
let config = evm::chain_config("1").unwrap();
let tx = &body["params"][0];
let data = tx.get("input").or_else(|| tx.get("data")).unwrap().as_str().unwrap();
let bytes = hex::decode(data.trim_start_matches("0x")).unwrap();
let result = match &bytes[..4] {
s if s == UserProxyFactoryV6::proxyOfCall::SELECTOR => {
assert_eq!(tx["to"], format!("{:?}", config.factory));
Address::repeat_byte(2).abi_encode()
}
s if s == IntentSettlerV3::orderHashCall::SELECTOR => {
assert_eq!(tx["to"], format!("{:?}", config.settler));
let call = IntentSettlerV3::orderHashCall::abi_decode(&bytes).unwrap();
order_types::order_id(&call.o).abi_encode()
}
s if s == IntentSettlerV3::orderDigestCall::SELECTOR => {
assert_eq!(tx["to"], format!("{:?}", config.settler));
let call = IntentSettlerV3::orderDigestCall::abi_decode(&bytes).unwrap();
if bad_digest { B256::ZERO.abi_encode() } else { owner_digest(config, &call.o).abi_encode() }
}
s if s == UserProxyV6::isIntentAuthorizedCall::SELECTOR => {
let call = UserProxyV6::isIntentAuthorizedCall::abi_decode(&bytes).unwrap();
let envelope = order_types::IntentAbiCodec::encodeEnvelopeCall::abi_decode_raw(&call.auth).unwrap();
assert_eq!(envelope.kind, 0);
assert_eq!(tx["from"], format!("{:?}", config.settler));
true.abi_encode()
}
_ => panic!("unexpected call: {data}"),
};
ok(body, json!(hex_bytes(&result)))
}