Skip to content

Commit 3202aef

Browse files
committed
Merge asset & scan address names; add EVM tests
Enhance address name resolution to prefer asset-based names and deduplicate results: made ChainAddress Hash+Eq, extended AddressNamesClient to query assets repository, added get_scan_names/get_asset_names helpers and a merge_names routine; added asset_id/asset_name helpers and unit tests. Added a test AddressName::mock utility in primitives testkit and exported it. Moved and expanded EVM simulation tests (some tests moved into decode.rs and client.rs), replaced inline JSON fixtures with external testdata files for various Permit/Permit2 cases, and removed the old tests.rs. These changes improve name lookup accuracy (asset-first) and consolidate EVM test fixtures for maintainability.
1 parent 56ceeec commit 3202aef

18 files changed

Lines changed: 655 additions & 586 deletions
Lines changed: 67 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
use std::collections::{HashMap, HashSet};
12
use std::error::Error;
23

3-
use primitives::{AddressName, ChainAddress};
4-
use storage::{Database, ScanAddressesRepository};
4+
use primitives::{AddressName, AddressType, Asset, AssetId, ChainAddress, VerificationStatus};
5+
use storage::{AssetsRepository, Database, ScanAddressesRepository};
56

67
#[derive(Clone)]
78
pub struct AddressNamesClient {
@@ -14,20 +15,78 @@ impl AddressNamesClient {
1415
}
1516

1617
pub fn get_address_names(&self, requests: Vec<ChainAddress>) -> Result<Vec<AddressName>, Box<dyn Error + Send + Sync>> {
17-
let requests = requests.into_iter().filter(|request| !request.address.is_empty()).collect::<Vec<_>>();
18-
18+
let requests: Vec<ChainAddress> = requests.into_iter().filter(|request| !request.address.is_empty()).collect();
1919
if requests.is_empty() {
2020
return Ok(vec![]);
2121
}
2222

2323
let queries = requests.iter().map(|request| (request.chain, request.address.as_str())).collect::<Vec<_>>();
24-
25-
Ok(self
24+
let scan_names = self
2625
.database
2726
.scan_addresses()?
2827
.get_scan_addresses(&queries)?
2928
.into_iter()
30-
.filter_map(|x| x.as_primitive())
31-
.collect())
29+
.filter_map(|row| row.as_primitive())
30+
.map(|name| (ChainAddress::new(name.chain, name.address.clone()), name))
31+
.collect::<HashMap<_, _>>();
32+
let asset_ids = requests
33+
.iter()
34+
.map(|request| AssetId::from(request.chain, Some(request.address.clone())).to_string())
35+
.collect::<Vec<_>>();
36+
let asset_names = self
37+
.database
38+
.assets()?
39+
.get_assets(asset_ids)?
40+
.into_iter()
41+
.filter_map(asset_entry)
42+
.collect::<HashMap<_, _>>();
43+
44+
Ok(map_requests(requests, &scan_names, &asset_names))
45+
}
46+
}
47+
48+
fn map_requests(requests: Vec<ChainAddress>, scan_names: &HashMap<ChainAddress, AddressName>, asset_names: &HashMap<ChainAddress, AddressName>) -> Vec<AddressName> {
49+
requests
50+
.into_iter()
51+
.filter_map(|request| asset_names.get(&request).or_else(|| scan_names.get(&request)).cloned())
52+
.scan(HashSet::new(), |seen, name| {
53+
seen.insert(ChainAddress::new(name.chain, name.address.clone())).then_some(name)
54+
})
55+
.collect()
56+
}
57+
58+
fn asset_entry(asset: Asset) -> Option<(ChainAddress, AddressName)> {
59+
let address = asset.token_id?;
60+
61+
Some((
62+
ChainAddress::new(asset.chain, address.clone()),
63+
AddressName {
64+
chain: asset.chain,
65+
address,
66+
name: asset.name,
67+
address_type: Some(AddressType::Contract),
68+
status: VerificationStatus::Verified,
69+
},
70+
))
71+
}
72+
73+
#[cfg(test)]
74+
mod tests {
75+
use std::collections::HashMap;
76+
77+
use super::map_requests;
78+
use primitives::{AddressName, AddressType, Chain, ChainAddress, VerificationStatus};
79+
80+
#[test]
81+
fn test_map_requests_prefers_asset_then_scan() {
82+
let asset_request = ChainAddress::new(Chain::Ethereum, "0xdAC17F958D2ee523a2206206994597C13D831ec7".to_string());
83+
let scan_request = ChainAddress::new(Chain::Ethereum, "0x123".to_string());
84+
let asset_name = AddressName::mock("0xdAC17F958D2ee523a2206206994597C13D831ec7", "USDT", AddressType::Contract, VerificationStatus::Verified);
85+
let scan_name = AddressName::mock("0x123", "Legacy Name", AddressType::Address, VerificationStatus::Unverified);
86+
87+
let scan_names = HashMap::from([(asset_request.clone(), scan_name.clone()), (scan_request.clone(), scan_name.clone())]);
88+
let asset_names = HashMap::from([(asset_request.clone(), asset_name.clone())]);
89+
90+
assert_eq!(map_requests(vec![asset_request, scan_request], &scan_names, &asset_names), vec![asset_name, scan_name]);
3291
}
3392
}

crates/primitives/src/chain_address.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use typeshare::typeshare;
55

66
use crate::Chain;
77

8-
#[derive(Serialize, Deserialize, Clone, Debug)]
8+
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Hash)]
99
#[typeshare(swift = "Equatable, Hashable, Sendable")]
1010
pub struct ChainAddress {
1111
pub chain: Chain,
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
use crate::{AddressName, AddressType, Chain, VerificationStatus};
2+
3+
impl AddressName {
4+
pub fn mock(address: &str, name: &str, address_type: AddressType, status: VerificationStatus) -> Self {
5+
Self {
6+
chain: Chain::Ethereum,
7+
address: address.to_string(),
8+
name: name.to_string(),
9+
address_type: Some(address_type),
10+
status,
11+
}
12+
}
13+
}

crates/primitives/src/testkit/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
pub mod address_name_mock;
12
pub mod asset_mock;
23
pub mod delegation_mock;
34
pub mod device_mock;

crates/signer/src/eip712/mod.rs

Lines changed: 4 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -11,45 +11,7 @@ mod tests {
1111

1212
#[test]
1313
fn hash_matches_reference_vector() {
14-
let json = r#"
15-
{
16-
"types": {
17-
"EIP712Domain": [
18-
{ "name": "name", "type": "string" },
19-
{ "name": "version", "type": "string" },
20-
{ "name": "chainId", "type": "uint256" },
21-
{ "name": "verifyingContract", "type": "address" }
22-
],
23-
"Person": [
24-
{ "name": "name", "type": "string" },
25-
{ "name": "wallet", "type": "address" }
26-
],
27-
"Mail": [
28-
{ "name": "from", "type": "Person" },
29-
{ "name": "to", "type": "Person" },
30-
{ "name": "contents", "type": "string" }
31-
]
32-
},
33-
"primaryType": "Mail",
34-
"domain": {
35-
"name": "Ether Mail",
36-
"version": "1",
37-
"chainId": 1,
38-
"verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
39-
},
40-
"message": {
41-
"from": {
42-
"name": "Cow",
43-
"wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"
44-
},
45-
"to": {
46-
"name": "Bob",
47-
"wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"
48-
},
49-
"contents": "Hello, Bob!"
50-
}
51-
}
52-
"#;
14+
let json = include_str!("../../testdata/eip712_reference_vector.json");
5315

5416
let our_hash = hash_typed_data(json).expect("hash succeeds");
5517
let expected = <[u8; 32]>::from_hex("be609aee343fb3c4b28e1df9e632fca64fcfaede20f02e86244efddf30957bd2").expect("valid hex");
@@ -66,48 +28,7 @@ mod tests {
6628

6729
#[test]
6830
fn hash_handles_arrays_and_nested_types() {
69-
let json = r#"
70-
{
71-
"types": {
72-
"EIP712Domain": [
73-
{ "name": "name", "type": "string" },
74-
{ "name": "version", "type": "string" },
75-
{ "name": "chainId", "type": "uint256" },
76-
{ "name": "verifyingContract", "type": "address" }
77-
],
78-
"Inner": [
79-
{ "name": "flag", "type": "bool" },
80-
{ "name": "payload", "type": "bytes32" }
81-
],
82-
"Group": [
83-
{ "name": "members", "type": "address[]" },
84-
{ "name": "name", "type": "string" },
85-
{ "name": "nested", "type": "Inner" },
86-
{ "name": "weights", "type": "uint64[3]" }
87-
]
88-
},
89-
"primaryType": "Group",
90-
"domain": {
91-
"name": "Test",
92-
"version": "1",
93-
"chainId": 31337,
94-
"verifyingContract": "0x0000000000000000000000000000000000000001"
95-
},
96-
"message": {
97-
"members": [
98-
"0x90f8bf6a479f320ead074411a4b0e7944ea8c9c1",
99-
"0xffcf8fdee72ac11b5c542428b35eef5769c409f0",
100-
"0x627306090abab3a6e1400e9345bc60c78a8bef57"
101-
],
102-
"name": "Team Rocket",
103-
"nested": {
104-
"flag": true,
105-
"payload": "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"
106-
},
107-
"weights": [ "1", "2", "3" ]
108-
}
109-
}
110-
"#;
31+
let json = include_str!("../../testdata/eip712_arrays_nested.json");
11132

11233
let digest = hash_typed_data(json).expect("hash succeeds");
11334
let expected = <[u8; 32]>::from_hex("6acbc18af9d2decca3d38571c2f595b1ebb1b93e9e7b046632df71f6ceb217f9").expect("valid hex");
@@ -116,45 +37,15 @@ mod tests {
11637

11738
#[test]
11839
fn hash_rejects_missing_message() {
119-
let json = r#"
120-
{
121-
"types": {
122-
"EIP712Domain": [],
123-
"Simple": [
124-
{ "name": "value", "type": "uint256" }
125-
]
126-
},
127-
"primaryType": "Simple",
128-
"domain": {},
129-
"message": null
130-
}
131-
"#;
40+
let json = include_str!("../../testdata/eip712_missing_message.json");
13241

13342
let err = hash_typed_data(json).expect_err("missing message returns error");
13443
assert!(err.to_string().contains("missing message"));
13544
}
13645

13746
#[test]
13847
fn hash_supports_signed_integers() {
139-
let json = r#"
140-
{
141-
"types": {
142-
"EIP712Domain": [],
143-
"Payload": [
144-
{ "name": "balance", "type": "int256" },
145-
{ "name": "delta", "type": "int32" },
146-
{ "name": "active", "type": "bool" }
147-
]
148-
},
149-
"primaryType": "Payload",
150-
"domain": {},
151-
"message": {
152-
"balance": "-0x0100",
153-
"delta": -42,
154-
"active": false
155-
}
156-
}
157-
"#;
48+
let json = include_str!("../../testdata/eip712_signed_integers.json");
15849

15950
let digest = hash_typed_data(json).expect("hash succeeds");
16051
let expected = <[u8; 32]>::from_hex("10e6c8b7c51b08488a421a5492d4524439470010eb2f8c80c22b9d918d79a5a9").expect("valid hex");
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{
2+
"types": {
3+
"EIP712Domain": [
4+
{ "name": "name", "type": "string" },
5+
{ "name": "version", "type": "string" },
6+
{ "name": "chainId", "type": "uint256" },
7+
{ "name": "verifyingContract", "type": "address" }
8+
],
9+
"Inner": [
10+
{ "name": "flag", "type": "bool" },
11+
{ "name": "payload", "type": "bytes32" }
12+
],
13+
"Group": [
14+
{ "name": "members", "type": "address[]" },
15+
{ "name": "name", "type": "string" },
16+
{ "name": "nested", "type": "Inner" },
17+
{ "name": "weights", "type": "uint64[3]" }
18+
]
19+
},
20+
"primaryType": "Group",
21+
"domain": {
22+
"name": "Test",
23+
"version": "1",
24+
"chainId": 31337,
25+
"verifyingContract": "0x0000000000000000000000000000000000000001"
26+
},
27+
"message": {
28+
"members": [
29+
"0x90f8bf6a479f320ead074411a4b0e7944ea8c9c1",
30+
"0xffcf8fdee72ac11b5c542428b35eef5769c409f0",
31+
"0x627306090abab3a6e1400e9345bc60c78a8bef57"
32+
],
33+
"name": "Team Rocket",
34+
"nested": {
35+
"flag": true,
36+
"payload": "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"
37+
},
38+
"weights": ["1", "2", "3"]
39+
}
40+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"types": {
3+
"EIP712Domain": [],
4+
"Simple": [
5+
{ "name": "value", "type": "uint256" }
6+
]
7+
},
8+
"primaryType": "Simple",
9+
"domain": {},
10+
"message": null
11+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"types": {
3+
"EIP712Domain": [
4+
{ "name": "name", "type": "string" },
5+
{ "name": "version", "type": "string" },
6+
{ "name": "chainId", "type": "uint256" },
7+
{ "name": "verifyingContract", "type": "address" }
8+
],
9+
"Person": [
10+
{ "name": "name", "type": "string" },
11+
{ "name": "wallet", "type": "address" }
12+
],
13+
"Mail": [
14+
{ "name": "from", "type": "Person" },
15+
{ "name": "to", "type": "Person" },
16+
{ "name": "contents", "type": "string" }
17+
]
18+
},
19+
"primaryType": "Mail",
20+
"domain": {
21+
"name": "Ether Mail",
22+
"version": "1",
23+
"chainId": 1,
24+
"verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
25+
},
26+
"message": {
27+
"from": {
28+
"name": "Cow",
29+
"wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"
30+
},
31+
"to": {
32+
"name": "Bob",
33+
"wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"
34+
},
35+
"contents": "Hello, Bob!"
36+
}
37+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"types": {
3+
"EIP712Domain": [],
4+
"Payload": [
5+
{ "name": "balance", "type": "int256" },
6+
{ "name": "delta", "type": "int32" },
7+
{ "name": "active", "type": "bool" }
8+
]
9+
},
10+
"primaryType": "Payload",
11+
"domain": {},
12+
"message": {
13+
"balance": "-0x0100",
14+
"delta": -42,
15+
"active": false
16+
}
17+
}

0 commit comments

Comments
 (0)