use clear_signing::decoder::parse_signature;
use clear_signing::resolver::ResolvedDescriptor;
use clear_signing::token::{
CompositeDataProvider, StaticTokenSource, TokenMeta, WellKnownTokenSource,
};
use clear_signing::types::descriptor::Descriptor;
use clear_signing::{format_calldata, DisplayEntry, DisplayModel, TransactionContext};
fn load_descriptor(fixture: &str) -> Descriptor {
let path = format!("{}/tests/fixtures/{fixture}", env!("CARGO_MANIFEST_DIR"));
let json = std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("read {path}: {e}"));
Descriptor::from_json(&json).unwrap_or_else(|e| panic!("parse {path}: {e}"))
}
fn build_calldata(selector: &[u8; 4], args: &[Vec<u8>]) -> Vec<u8> {
let mut data = Vec::with_capacity(4 + args.len() * 32);
data.extend_from_slice(selector);
for arg in args {
data.extend_from_slice(arg);
}
data
}
fn address_word(hex_addr: &str) -> Vec<u8> {
let hex_str = hex_addr
.strip_prefix("0x")
.or_else(|| hex_addr.strip_prefix("0X"))
.unwrap_or(hex_addr);
let addr_bytes = hex::decode(hex_str).expect("valid hex address");
let mut word = vec![0u8; 12];
word.extend_from_slice(&addr_bytes);
assert_eq!(word.len(), 32);
word
}
fn uint_word(val: u128) -> Vec<u8> {
let mut word = vec![0u8; 16];
word.extend_from_slice(&val.to_be_bytes());
assert_eq!(word.len(), 32);
word
}
fn max_uint256_word() -> Vec<u8> {
vec![0xff; 32]
}
fn aave_token_source() -> CompositeDataProvider {
let mut custom = StaticTokenSource::new();
custom.insert(
1,
"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
TokenMeta {
symbol: "USDC".to_string(),
decimals: 6,
name: "USD Coin".to_string(),
},
);
custom.insert(
1,
"0x6b175474e89094c44da98b954eedeac495271d0f",
TokenMeta {
symbol: "DAI".to_string(),
decimals: 18,
name: "Dai Stablecoin".to_string(),
},
);
custom.insert(
1,
"0xdac17f958d2ee523a2206206994597c13d831ec7",
TokenMeta {
symbol: "USDT".to_string(),
decimals: 6,
name: "Tether USD".to_string(),
},
);
custom.insert(
8453,
"0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
TokenMeta {
symbol: "USDC".to_string(),
decimals: 6,
name: "USD Coin".to_string(),
},
);
CompositeDataProvider::new(vec![
Box::new(custom),
Box::new(WellKnownTokenSource::new()),
])
}
fn wrap_rd(descriptor: Descriptor, chain_id: u64, address: &str) -> Vec<ResolvedDescriptor> {
vec![ResolvedDescriptor {
descriptor,
chain_id,
address: address.to_lowercase(),
}]
}
fn get_entry_value(model: &DisplayModel, label: &str) -> String {
for entry in &model.entries {
match entry {
DisplayEntry::Item(item) if item.label == label => return item.value.clone(),
_ => {}
}
}
panic!("no entry with label '{label}' found in {:?}", model.entries);
}
#[tokio::test]
async fn aave_supply_usdc_mainnet() {
let descriptor = load_descriptor("aave-lpv3.json");
let sig = parse_signature("supply(address,uint256,address,uint16)").unwrap();
let tokens = aave_token_source();
let usdc_addr = "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48";
let on_behalf = "1111111111111111111111111111111111111111";
let calldata = build_calldata(
&sig.selector,
&[
address_word(usdc_addr),
uint_word(1_000_000_000), address_word(on_behalf),
uint_word(0), ],
);
let to = "0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2";
let descriptors = wrap_rd(descriptor, 1, to);
let tx = TransactionContext {
chain_id: 1,
to,
calldata: &calldata,
value: None,
from: None,
implementation_address: None,
};
let result = format_calldata(&descriptors, &tx, &tokens).await.unwrap();
assert_eq!(result.intent, "Supply");
assert_eq!(get_entry_value(&result, "Amount to supply"), "1000 USDC");
}
#[tokio::test]
async fn aave_supply_usdc_base() {
let descriptor = load_descriptor("aave-lpv3.json");
let sig = parse_signature("supply(address,uint256,address,uint16)").unwrap();
let tokens = aave_token_source();
let usdc_base = "833589fcd6edb6e08f4c7c32d4f71b54bda02913";
let on_behalf = "2222222222222222222222222222222222222222";
let calldata = build_calldata(
&sig.selector,
&[
address_word(usdc_base),
uint_word(500_000_000), address_word(on_behalf),
uint_word(0),
],
);
let to = "0xA238Dd80C259a72e81d7e4664a9801593F98d1c5";
let descriptors = wrap_rd(descriptor, 8453, to);
let tx = TransactionContext {
chain_id: 8453,
to,
calldata: &calldata,
value: None,
from: None,
implementation_address: None,
};
let result = format_calldata(&descriptors, &tx, &tokens).await.unwrap();
assert_eq!(result.intent, "Supply");
assert_eq!(get_entry_value(&result, "Amount to supply"), "500 USDC");
}
#[tokio::test]
async fn aave_repay_all_dai() {
let descriptor = load_descriptor("aave-lpv3.json");
let sig = parse_signature("repay(address,uint256,uint256,address)").unwrap();
let tokens = aave_token_source();
let dai_addr = "6b175474e89094c44da98b954eedeac495271d0f";
let on_behalf = "3333333333333333333333333333333333333333";
let calldata = build_calldata(
&sig.selector,
&[
address_word(dai_addr),
max_uint256_word(), uint_word(2), address_word(on_behalf),
],
);
let to = "0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2";
let descriptors = wrap_rd(descriptor, 1, to);
let tx = TransactionContext {
chain_id: 1,
to,
calldata: &calldata,
value: None,
from: None,
implementation_address: None,
};
let result = format_calldata(&descriptors, &tx, &tokens).await.unwrap();
assert_eq!(result.intent, "Repay loan");
assert_eq!(get_entry_value(&result, "Amount to repay"), "All DAI");
assert_eq!(get_entry_value(&result, "Interest rate mode"), "variable");
}
#[tokio::test]
async fn aave_withdraw_max() {
let descriptor = load_descriptor("aave-lpv3.json");
let sig = parse_signature("withdraw(address,uint256,address)").unwrap();
let tokens = aave_token_source();
let usdc_addr = "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48";
let to_addr = "4444444444444444444444444444444444444444";
let calldata = build_calldata(
&sig.selector,
&[
address_word(usdc_addr),
max_uint256_word(), address_word(to_addr),
],
);
let to = "0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2";
let descriptors = wrap_rd(descriptor, 1, to);
let tx = TransactionContext {
chain_id: 1,
to,
calldata: &calldata,
value: None,
from: None,
implementation_address: None,
};
let result = format_calldata(&descriptors, &tx, &tokens).await.unwrap();
assert_eq!(result.intent, "Withdraw");
assert_eq!(get_entry_value(&result, "Amount to withdraw"), "Max USDC");
}
#[tokio::test]
async fn aave_borrow_variable() {
let descriptor = load_descriptor("aave-lpv3.json");
let sig = parse_signature("borrow(address,uint256,uint256,uint16,address)").unwrap();
let tokens = aave_token_source();
let usdt_addr = "dac17f958d2ee523a2206206994597c13d831ec7";
let on_behalf = "5555555555555555555555555555555555555555";
let calldata = build_calldata(
&sig.selector,
&[
address_word(usdt_addr),
uint_word(5_000_000), uint_word(2), uint_word(0), address_word(on_behalf),
],
);
let to = "0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2";
let descriptors = wrap_rd(descriptor, 1, to);
let tx = TransactionContext {
chain_id: 1,
to,
calldata: &calldata,
value: None,
from: None,
implementation_address: None,
};
let result = format_calldata(&descriptors, &tx, &tokens).await.unwrap();
assert_eq!(result.intent, "Borrow");
assert_eq!(get_entry_value(&result, "Amount to borrow"), "5 USDT");
assert_eq!(get_entry_value(&result, "Interest Rate mode"), "variable");
}
#[tokio::test]
async fn aave_set_collateral() {
let descriptor = load_descriptor("aave-lpv3.json");
let sig = parse_signature("setUserUseReserveAsCollateral(address,bool)").unwrap();
let tokens = aave_token_source();
let usdc_addr = "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48";
let calldata = build_calldata(
&sig.selector,
&[
address_word(usdc_addr),
uint_word(1), ],
);
let to = "0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2";
let descriptors = wrap_rd(descriptor, 1, to);
let tx = TransactionContext {
chain_id: 1,
to,
calldata: &calldata,
value: None,
from: None,
implementation_address: None,
};
let result = format_calldata(&descriptors, &tx, &tokens).await.unwrap();
assert_eq!(result.intent, "Manage collateral");
assert_eq!(get_entry_value(&result, "Enable use as collateral"), "true");
}
#[tokio::test]
async fn aave_deposit_usdc_mainnet() {
let descriptor = load_descriptor("aave-lpv2.json");
let sig = parse_signature("deposit(address,uint256,address,uint16)").unwrap();
let tokens = aave_token_source();
let usdc_addr = "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48";
let on_behalf = "1111111111111111111111111111111111111111";
let calldata = build_calldata(
&sig.selector,
&[
address_word(usdc_addr),
uint_word(1_000_000_000), address_word(on_behalf),
uint_word(0),
],
);
let to = "0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9";
let descriptors = wrap_rd(descriptor, 1, to);
let tx = TransactionContext {
chain_id: 1,
to,
calldata: &calldata,
value: None,
from: None,
implementation_address: None,
};
let result = format_calldata(&descriptors, &tx, &tokens).await.unwrap();
assert_eq!(result.intent, "Supply");
assert_eq!(get_entry_value(&result, "Amount to supply"), "1000 USDC");
}
#[tokio::test]
async fn gateway_deposit_eth() {
let descriptor = load_descriptor("aave-gateway.json");
let sig = parse_signature("depositETH(address,address,uint16)").unwrap();
let tokens = aave_token_source();
let pool_addr = "87870bca3f3fd6335c3f4ce8392d69350b4fa4e2";
let on_behalf = "6666666666666666666666666666666666666666";
let calldata = build_calldata(
&sig.selector,
&[
address_word(pool_addr),
address_word(on_behalf),
uint_word(0),
],
);
let value = uint_word(1_000_000_000_000_000_000);
let to = "0xd01607c3C5eCABa394D8be377a08590149325722";
let descriptors = wrap_rd(descriptor, 1, to);
let tx = TransactionContext {
chain_id: 1,
to,
calldata: &calldata,
value: Some(&value),
from: None,
implementation_address: None,
};
let result = format_calldata(&descriptors, &tx, &tokens).await.unwrap();
assert_eq!(result.intent, "Supply");
let amount = get_entry_value(&result, "Amount to supply");
assert_eq!(amount, "1 ETH");
}
#[tokio::test]
async fn gateway_borrow_eth_with_from() {
let descriptor = load_descriptor("aave-gateway.json");
let sig = parse_signature("borrowETH(address,uint256,uint16)").unwrap();
let tokens = aave_token_source();
let pool_addr = "87870bca3f3fd6335c3f4ce8392d69350b4fa4e2";
let calldata = build_calldata(
&sig.selector,
&[
address_word(pool_addr),
uint_word(500_000_000_000_000_000), uint_word(0),
],
);
let from_addr = "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
let to = "0xd01607c3C5eCABa394D8be377a08590149325722";
let descriptors = wrap_rd(descriptor, 1, to);
let tx = TransactionContext {
chain_id: 1,
to,
calldata: &calldata,
value: None,
from: Some(from_addr),
implementation_address: None,
};
let result = format_calldata(&descriptors, &tx, &tokens).await.unwrap();
assert_eq!(result.intent, "Borrow");
let debtor = get_entry_value(&result, "Debtor");
assert!(
debtor.to_lowercase().contains("aaaaaa"),
"debtor should contain the from address: {debtor}"
);
}
#[tokio::test]
async fn real_wallet_supply_eth_mainnet() {
let descriptor = load_descriptor("aave-gateway.json");
let tokens = aave_token_source();
let calldata = hex::decode(
"474cf53d\
00000000000000000000000087870bca3f3fd6335c3f4ce8392d69350b4fa4e2\
000000000000000000000000bf01daf454dce008d3e2bfd47d5e186f71477253\
0000000000000000000000000000000000000000000000000000000000000000",
)
.unwrap();
let value_bytes =
hex::decode("00000000000000000000000000000000000000000000000000005af3107a4000").unwrap();
let to = "0xd01607c3C5eCABa394D8be377a08590149325722";
let descriptors = wrap_rd(descriptor, 1, to);
let tx = TransactionContext {
chain_id: 1,
to,
calldata: &calldata,
value: Some(&value_bytes),
from: Some("0xbf01daf454dce008d3e2bfd47d5e186f71477253"),
implementation_address: None,
};
let result = format_calldata(&descriptors, &tx, &tokens).await.unwrap();
assert_eq!(result.intent, "Supply");
assert_eq!(get_entry_value(&result, "Amount to supply"), "0.0001 ETH");
let interp = result.interpolated_intent.as_deref().unwrap();
assert!(
interp.contains("0.0001 ETH"),
"interpolated intent should contain '0.0001 ETH': {interp}"
);
assert!(
!interp.contains("100000000000000"),
"interpolated intent should NOT contain raw wei: {interp}"
);
}
#[tokio::test]
async fn aave_interpolated_intent() {
let descriptor = load_descriptor("aave-lpv3.json");
let sig = parse_signature("supply(address,uint256,address,uint16)").unwrap();
let tokens = aave_token_source();
let usdc_addr = "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48";
let on_behalf = "1111111111111111111111111111111111111111";
let calldata = build_calldata(
&sig.selector,
&[
address_word(usdc_addr),
uint_word(1_000_000_000), address_word(on_behalf),
uint_word(0),
],
);
let to = "0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2";
let descriptors = wrap_rd(descriptor, 1, to);
let tx = TransactionContext {
chain_id: 1,
to,
calldata: &calldata,
value: None,
from: None,
implementation_address: None,
};
let result = format_calldata(&descriptors, &tx, &tokens).await.unwrap();
let intent = result.interpolated_intent.as_deref().unwrap();
assert!(
intent.contains("1000 USDC"),
"interpolated intent should contain formatted amount: {intent}"
);
assert!(
intent.contains("1111111111"),
"interpolated intent should contain the address: {intent}"
);
}
#[tokio::test]
async fn graceful_fallback_unknown_selector() {
let descriptor = load_descriptor("aave-lpv3.json");
let tokens = aave_token_source();
let calldata = hex::decode(
"deadbeef000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000003e8",
)
.unwrap();
let to = "0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2";
let descriptors = wrap_rd(descriptor, 1, to);
let tx = TransactionContext {
chain_id: 1,
to,
calldata: &calldata,
value: None,
from: None,
implementation_address: None,
};
let result = format_calldata(&descriptors, &tx, &tokens).await.unwrap();
assert!(result.intent.contains("0xdeadbeef"));
assert_eq!(
result.fallback_reason(),
Some(&clear_signing::FallbackReason::FormatNotFound)
);
assert!(
result.diagnostics().iter().any(|diagnostic| diagnostic
.message
.contains("no descriptor format matched selector")),
"expected format-not-found diagnostic"
);
}
#[tokio::test]
async fn aave_withdraw_bytes32_optimism_graceful_fallback() {
let descriptor = load_descriptor("aave-lpv3.json");
let tokens = aave_token_source();
let calldata =
hex::decode("8e19899e0000000000000000000000000000ffffffffffffffffffffffffffffffff0005")
.unwrap();
let to = "0x794a61358d6845594f94dc1db02a252b5b4814ad";
let descriptors = wrap_rd(descriptor, 10, to);
let tx = TransactionContext {
chain_id: 10,
to,
calldata: &calldata,
value: Some(&[0x00]),
from: Some("0xbf01daf454dce008d3e2bfd47d5e186f71477253"),
implementation_address: None,
};
let result = format_calldata(&descriptors, &tx, &tokens).await.unwrap();
assert!(
result.intent.contains("0x8e19899e"),
"should fall back to unknown function: {}",
result.intent
);
assert_eq!(
result.fallback_reason(),
Some(&clear_signing::FallbackReason::FormatNotFound)
);
assert!(
result.diagnostics().iter().any(|diagnostic| diagnostic
.message
.contains("no descriptor format matched selector")),
"expected format-not-found diagnostic"
);
assert_eq!(result.entries.len(), 1);
}
#[tokio::test]
async fn real_wallet_withdraw_usdc_mainnet() {
let descriptor = load_descriptor("aave-lpv3.json");
let tokens = aave_token_source();
let calldata = hex::decode(
"69328dec\
000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48\
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\
000000000000000000000000bf01daf454dce008d3e2bfd47d5e186f71477253",
)
.unwrap();
let to = "0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2";
let descriptors = wrap_rd(descriptor, 1, to);
let tx = TransactionContext {
chain_id: 1,
to,
calldata: &calldata,
value: Some(&[0x00]),
from: Some("0xbf01daf454dce008d3e2bfd47d5e186f71477253"),
implementation_address: None,
};
let result = format_calldata(&descriptors, &tx, &tokens).await.unwrap();
assert_eq!(result.intent, "Withdraw");
assert_eq!(get_entry_value(&result, "Amount to withdraw"), "Max USDC");
let to_value = get_entry_value(&result, "To recipient");
assert!(
to_value
.to_lowercase()
.contains("bf01daf454dce008d3e2bfd47d5e186f71477253"),
"recipient should be the from address: {to_value}"
);
let interp = result.interpolated_intent.as_deref().unwrap();
assert!(
interp.contains("Max USDC"),
"interpolated intent should contain 'Max USDC': {interp}"
);
assert!(
interp.to_lowercase().contains("bf01daf454dce"),
"interpolated intent should contain recipient: {interp}"
);
}
#[tokio::test]
async fn real_wallet_withdraw_0_1_usdc_mainnet() {
let descriptor = load_descriptor("aave-lpv3.json");
let tokens = aave_token_source();
let calldata = hex::decode(
"69328dec\
000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48\
00000000000000000000000000000000000000000000000000000000000186a0\
000000000000000000000000bf01daf454dce008d3e2bfd47d5e186f71477253",
)
.unwrap();
let to = "0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2";
let descriptors = wrap_rd(descriptor, 1, to);
let tx = TransactionContext {
chain_id: 1,
to,
calldata: &calldata,
value: Some(&[0x00]),
from: Some("0xbf01daf454dce008d3e2bfd47d5e186f71477253"),
implementation_address: None,
};
let result = format_calldata(&descriptors, &tx, &tokens).await.unwrap();
assert_eq!(result.intent, "Withdraw");
assert_eq!(get_entry_value(&result, "Amount to withdraw"), "0.1 USDC");
let interp = result.interpolated_intent.as_deref().unwrap();
assert!(
interp.contains("0.1 USDC"),
"interpolated intent should contain '0.1 USDC': {interp}"
);
assert!(
!interp.contains("100000"),
"interpolated intent should NOT contain raw '100000': {interp}"
);
}
#[tokio::test]
async fn real_tx_borrow_usdt_enum_interpolation() {
let descriptor = load_descriptor("aave-lpv3.json");
let tokens = aave_token_source();
let calldata = hex::decode(
"a415bcad\
000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7\
0000000000000000000000000000000000000000000000000000000005f5e100\
0000000000000000000000000000000000000000000000000000000000000002\
0000000000000000000000000000000000000000000000000000000000000000\
000000000000000000000000694278718ecb91113a4c6141cc579dc105187a8a",
)
.unwrap();
let to = "0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2";
let descriptors = wrap_rd(descriptor, 1, to);
let tx = TransactionContext {
chain_id: 1,
to,
calldata: &calldata,
value: None,
from: Some("0x694278718ecb91113a4c6141cc579dc105187a8a"),
implementation_address: None,
};
let result = format_calldata(&descriptors, &tx, &tokens).await.unwrap();
assert_eq!(result.intent, "Borrow");
assert_eq!(get_entry_value(&result, "Amount to borrow"), "100 USDT");
assert_eq!(get_entry_value(&result, "Interest Rate mode"), "variable");
let interp = result.interpolated_intent.as_deref().unwrap();
assert!(
interp.contains("100 USDT"),
"interpolated intent should contain '100 USDT': {interp}"
);
assert!(
interp.contains("variable"),
"interpolated intent should contain 'variable' (not raw '2'): {interp}"
);
assert!(
!interp.contains("with 2 rate"),
"interpolated intent should NOT contain raw enum value 'with 2 rate': {interp}"
);
}
#[tokio::test]
async fn real_tx_repay_usdc_enum_interpolation() {
let descriptor = load_descriptor("aave-lpv3.json");
let tokens = aave_token_source();
let calldata = hex::decode(
"573ade81\
000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48\
0000000000000000000000000000000000000000000000000000000005ebeee9\
0000000000000000000000000000000000000000000000000000000000000002\
000000000000000000000000ad8c1b5b4d5dfe7fbe508ba57b1e05b33391f94a",
)
.unwrap();
let to = "0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2";
let descriptors = wrap_rd(descriptor, 1, to);
let tx = TransactionContext {
chain_id: 1,
to,
calldata: &calldata,
value: None,
from: Some("0xad8c1b5b4d5dfe7fbe508ba57b1e05b33391f94a"),
implementation_address: None,
};
let result = format_calldata(&descriptors, &tx, &tokens).await.unwrap();
assert_eq!(result.intent, "Repay loan");
assert_eq!(
get_entry_value(&result, "Amount to repay"),
"99.348201 USDC"
);
assert_eq!(get_entry_value(&result, "Interest rate mode"), "variable");
let interp = result.interpolated_intent.as_deref().unwrap();
assert!(
interp.contains("99.348201 USDC"),
"interpolated intent should contain formatted amount: {interp}"
);
assert!(
interp.contains("variable"),
"interpolated intent should resolve enum to 'variable': {interp}"
);
}
#[tokio::test]
async fn real_tx_repay_all_usdt_enum_interpolation() {
let descriptor = load_descriptor("aave-lpv3.json");
let tokens = aave_token_source();
let calldata = hex::decode(
"573ade81\
000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7\
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\
0000000000000000000000000000000000000000000000000000000000000002\
0000000000000000000000000b5a6a15b975fd35f0b301748c8dabd35b50d8c5",
)
.unwrap();
let to = "0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2";
let descriptors = wrap_rd(descriptor, 1, to);
let tx = TransactionContext {
chain_id: 1,
to,
calldata: &calldata,
value: None,
from: Some("0x0b5a6a15b975fd35f0b301748c8dabd35b50d8c5"),
implementation_address: None,
};
let result = format_calldata(&descriptors, &tx, &tokens).await.unwrap();
assert_eq!(result.intent, "Repay loan");
assert_eq!(get_entry_value(&result, "Amount to repay"), "All USDT");
assert_eq!(get_entry_value(&result, "Interest rate mode"), "variable");
let interp = result.interpolated_intent.as_deref().unwrap();
assert!(
interp.contains("All USDT"),
"interpolated intent should contain threshold message: {interp}"
);
assert!(
interp.contains("variable"),
"interpolated intent should resolve enum to 'variable': {interp}"
);
assert!(
!interp.contains("with 2"),
"interpolated intent should NOT contain raw '2': {interp}"
);
}
#[tokio::test]
async fn real_tx_withdraw_max_weth() {
let descriptor = load_descriptor("aave-lpv3.json");
let mut custom = StaticTokenSource::new();
custom.insert(
1,
"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
TokenMeta {
symbol: "WETH".to_string(),
decimals: 18,
name: "Wrapped Ether".to_string(),
},
);
let tokens = CompositeDataProvider::new(vec![
Box::new(custom),
Box::new(WellKnownTokenSource::new()),
]);
let calldata = hex::decode(
"69328dec\
000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2\
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\
0000000000000000000000002f45665810956929bbfaa984d70a511ad08b0b54",
)
.unwrap();
let to = "0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2";
let descriptors = wrap_rd(descriptor, 1, to);
let tx = TransactionContext {
chain_id: 1,
to,
calldata: &calldata,
value: None,
from: Some("0x2f45665810956929bbfaa984d70a511ad08b0b54"),
implementation_address: None,
};
let result = format_calldata(&descriptors, &tx, &tokens).await.unwrap();
assert_eq!(result.intent, "Withdraw");
assert_eq!(get_entry_value(&result, "Amount to withdraw"), "Max WETH");
let interp = result.interpolated_intent.as_deref().unwrap();
assert!(
interp.contains("Max WETH"),
"interpolated intent should contain 'Max WETH': {interp}"
);
}
#[tokio::test]
async fn real_tx_supply_with_permit_usdc() {
let descriptor = load_descriptor("aave-lpv3.json");
let tokens = aave_token_source();
let calldata = hex::decode(
"02c205f0\
000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48\
000000000000000000000000000000000000000000000000000000031eb3c600\
00000000000000000000000044f2a3aa7fdda16a7bf66c68fba96508078d2bdc\
0000000000000000000000000000000000000000000000000000000000000000\
0000000000000000000000000000000000000000000000000000000069b6a5b9\
000000000000000000000000000000000000000000000000000000000000001c\
3c0980cd1d3bbc8d4e6d2e393e0913eeeaf25785629f0a535650dcf99b5176dd\
0d838b39d2ef75a01d05e11911d6cf9abc53ef9a51f845a5e3445a9c853fe9a2",
)
.unwrap();
let to = "0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2";
let descriptors = wrap_rd(descriptor, 1, to);
let tx = TransactionContext {
chain_id: 1,
to,
calldata: &calldata,
value: None,
from: Some("0x44f2a3aa7fdda16a7bf66c68fba96508078d2bdc"),
implementation_address: None,
};
let result = format_calldata(&descriptors, &tx, &tokens).await.unwrap();
assert_eq!(result.intent, "Supply");
assert_eq!(get_entry_value(&result, "Amount to supply"), "13400 USDC");
let interp = result.interpolated_intent.as_deref().unwrap();
assert!(
interp.contains("13400 USDC"),
"interpolated intent should contain '13400 USDC': {interp}"
);
}