Skip to content

Commit 4cdcd30

Browse files
committed
chore: bump version to 0.4.0 and enhance codebase quality
- Upgrade workspace version from 0.3.4 to 0.4.0 - Remove chrono dependency (unused) - Remove prohibited comment style from fetcher.rs - Add comprehensive unit tests for client, error, networks, and types modules - Add builder methods for ServiceEndpoint and FeedbackInput types - Change reputation API to accept slice references instead of owned Vec for client_addresses - Replace generic contract error with specific MissingRegisteredEvent error
1 parent e6723e7 commit 4cdcd30

11 files changed

Lines changed: 406 additions & 24 deletions

File tree

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,20 @@ default-members = ["erc8004"]
44
resolver = "3"
55

66
[workspace.package]
7-
version = "0.3.4"
7+
version = "0.4.0"
88
edition = "2024"
99
license = "MIT OR Apache-2.0"
1010
repository = "https://github.com/qntx/erc8004"
1111
description = "ERC-8004 Trustless Agents Rust SDK."
1212

1313
[workspace.dependencies]
14-
erc8004 = { version = "0.3", path = "erc8004" }
15-
erc8004-events = { version = "0.3", path = "erc8004-events" }
14+
erc8004 = { version = "0.4", path = "erc8004" }
15+
erc8004-events = { version = "0.4", path = "erc8004-events" }
1616

1717
alloy = { version = "1.8.3", features = ["full"] }
1818
anyhow = "1.0.102"
1919
arrow-array = "58.1.0"
2020
arrow-schema = "58.1.0"
21-
chrono = { version = "0.4.44", features = ["serde"] }
2221
clap = { version = "4.6.0", features = ["derive"] }
2322
parquet = { version = "58.1.0", features = ["arrow"] }
2423
serde = { version = "1.0.228", features = ["derive"] }

erc8004-events/src/fetcher.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,6 @@ impl Batcher {
8484
}
8585
}
8686

87-
// ── Error classification ─────────────────────────────────────────────
88-
8987
/// Broad classification of RPC errors.
9088
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9189
enum RpcErrorKind {

erc8004/examples/reputation_summary.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
3737
}
3838

3939
// Get the aggregated summary (filtering by known clients to avoid Sybil).
40-
let summary = reputation
41-
.get_summary(agent_id, clients.clone(), "", "")
42-
.await?;
40+
let summary = reputation.get_summary(agent_id, &clients, "", "").await?;
4341

4442
println!(
4543
"Summary: count={}, value={} (decimals={})",

erc8004/src/client.rs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,3 +187,76 @@ impl<P: Provider> Erc8004<P> {
187187
self.validation_address
188188
}
189189
}
190+
191+
#[cfg(test)]
192+
mod tests {
193+
use alloy::primitives::address;
194+
use alloy::providers::ProviderBuilder;
195+
196+
use super::*;
197+
use crate::networks::Network;
198+
199+
fn test_client() -> Erc8004<impl Provider> {
200+
let provider = ProviderBuilder::new().connect_http("https://localhost:1".parse().unwrap());
201+
Erc8004::new(provider)
202+
}
203+
204+
#[test]
205+
fn test_new_has_no_addresses() {
206+
let client = test_client();
207+
assert!(client.identity_address().is_none());
208+
assert!(client.reputation_address().is_none());
209+
assert!(client.validation_address().is_none());
210+
}
211+
212+
#[test]
213+
fn test_with_network_configures_handles() {
214+
let client = test_client().with_network(Network::EthereumMainnet);
215+
let addrs = Network::EthereumMainnet.addresses();
216+
assert_eq!(client.identity_address(), Some(addrs.identity));
217+
assert_eq!(client.reputation_address(), Some(addrs.reputation));
218+
assert!(client.identity().is_ok());
219+
assert!(client.reputation().is_ok());
220+
}
221+
222+
#[test]
223+
fn test_with_individual_addresses_stored_independently() {
224+
let id = address!("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
225+
let rep = address!("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
226+
let val = address!("cccccccccccccccccccccccccccccccccccccccc");
227+
let client = test_client()
228+
.with_identity_address(id)
229+
.with_reputation_address(rep)
230+
.with_validation_address(val);
231+
assert_eq!(client.identity_address(), Some(id));
232+
assert_eq!(client.reputation_address(), Some(rep));
233+
assert_eq!(client.validation_address(), Some(val));
234+
}
235+
236+
#[test]
237+
fn test_unconfigured_registries_return_error() {
238+
let client = test_client();
239+
for (label, result) in [
240+
("identity", client.identity().err()),
241+
("reputation", client.reputation().err()),
242+
("validation", client.validation().err()),
243+
] {
244+
let msg = result.map_or_else(|| unreachable!("{label} should fail"), |e| e.to_string());
245+
assert!(
246+
msg.contains("not configured"),
247+
"{label}: expected 'not configured', got: {msg}"
248+
);
249+
}
250+
}
251+
252+
#[test]
253+
fn test_with_addresses_struct() {
254+
let addrs = NetworkAddresses {
255+
identity: address!("1111111111111111111111111111111111111111"),
256+
reputation: address!("2222222222222222222222222222222222222222"),
257+
};
258+
let client = test_client().with_addresses(addrs);
259+
assert_eq!(client.identity_address(), Some(addrs.identity));
260+
assert_eq!(client.reputation_address(), Some(addrs.reputation));
261+
}
262+
}

erc8004/src/error.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ pub enum Erc8004Error {
4545
source: alloy::hex::FromHexError,
4646
},
4747

48+
/// A registration transaction succeeded but emitted no `Registered` event.
49+
#[error("transaction receipt contained no Registered event")]
50+
MissingRegisteredEvent,
51+
4852
/// The identity registry address returned by Reputation/Validation
4953
/// does not match the configured identity registry.
5054
#[error("identity registry mismatch: expected {expected}, got {actual}")]
@@ -58,3 +62,50 @@ pub enum Erc8004Error {
5862

5963
/// A convenience type alias used throughout the SDK.
6064
pub type Result<T> = core::result::Result<T, Erc8004Error>;
65+
66+
#[cfg(test)]
67+
mod tests {
68+
use super::*;
69+
70+
#[test]
71+
fn test_registry_not_configured_display() {
72+
let err = Erc8004Error::RegistryNotConfigured {
73+
registry: "identity",
74+
};
75+
assert_eq!(err.to_string(), "registry not configured: identity");
76+
}
77+
78+
#[test]
79+
fn test_agent_not_found_display() {
80+
let err = Erc8004Error::AgentNotFound {
81+
agent_id: alloy::primitives::U256::from(42),
82+
};
83+
assert_eq!(err.to_string(), "agent 42 does not exist");
84+
}
85+
86+
#[test]
87+
fn test_missing_registered_event_display() {
88+
let err = Erc8004Error::MissingRegisteredEvent;
89+
assert_eq!(
90+
err.to_string(),
91+
"transaction receipt contained no Registered event"
92+
);
93+
}
94+
95+
#[test]
96+
fn test_identity_registry_mismatch_display() {
97+
let expected = Address::ZERO;
98+
let actual = Address::repeat_byte(0x01);
99+
let err = Erc8004Error::IdentityRegistryMismatch { expected, actual };
100+
assert_eq!(
101+
err.to_string(),
102+
format!("identity registry mismatch: expected {expected}, got {actual}")
103+
);
104+
}
105+
106+
#[test]
107+
fn test_error_is_send_sync() {
108+
fn assert_send_sync<T: Send + Sync>() {}
109+
assert_send_sync::<Erc8004Error>();
110+
}
111+
}

erc8004/src/identity.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -267,10 +267,6 @@ impl<P: Provider> Identity<P> {
267267
.ok()
268268
.map(|e| e.inner.data.agentId)
269269
})
270-
.ok_or(Erc8004Error::Contract(
271-
alloy::contract::Error::UnknownFunction(
272-
"register: no Registered event found".to_owned(),
273-
),
274-
))
270+
.ok_or(Erc8004Error::MissingRegisteredEvent)
275271
}
276272
}

erc8004/src/networks.rs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,3 +218,75 @@ impl Network {
218218
format!("eip155:{}:{}", self.chain_id(), self.addresses().identity)
219219
}
220220
}
221+
222+
#[cfg(test)]
223+
mod tests {
224+
use super::*;
225+
226+
#[test]
227+
fn test_all_has_no_duplicate_chain_ids() {
228+
let mut ids: Vec<u64> = Network::ALL.iter().map(|n| n.chain_id()).collect();
229+
ids.sort_unstable();
230+
let before = ids.len();
231+
ids.dedup();
232+
assert_eq!(
233+
before,
234+
ids.len(),
235+
"Network::ALL contains duplicate chain IDs"
236+
);
237+
}
238+
239+
#[test]
240+
fn test_chain_id_round_trip() {
241+
for &network in Network::ALL {
242+
let id = network.chain_id();
243+
assert_eq!(
244+
Network::from_chain_id(id),
245+
Some(network),
246+
"from_chain_id({id}) should return the original variant"
247+
);
248+
}
249+
}
250+
251+
#[test]
252+
fn test_from_chain_id_unknown_returns_none() {
253+
assert_eq!(Network::from_chain_id(999_999_999), None);
254+
}
255+
256+
#[test]
257+
fn test_create2_mainnet_addresses_are_consistent() {
258+
let reference = Network::EthereumMainnet.addresses();
259+
for &network in Network::ALL {
260+
let addrs = network.addresses();
261+
if addrs.identity != reference.identity {
262+
continue;
263+
}
264+
assert_eq!(
265+
addrs.reputation,
266+
reference.reputation,
267+
"chain {} has matching identity but mismatched reputation",
268+
network.chain_id()
269+
);
270+
}
271+
}
272+
273+
#[test]
274+
fn test_testnet_addresses_differ_from_mainnet() {
275+
let mainnet = Network::EthereumMainnet.addresses();
276+
let testnet = Network::EthereumSepolia.addresses();
277+
assert_ne!(mainnet.identity, testnet.identity);
278+
assert_ne!(mainnet.reputation, testnet.reputation);
279+
}
280+
281+
#[test]
282+
fn test_agent_registry_prefix_format() {
283+
let network = Network::EthereumMainnet;
284+
let prefix = network.agent_registry_prefix();
285+
let expected = format!(
286+
"eip155:{}:{}",
287+
network.chain_id(),
288+
network.addresses().identity
289+
);
290+
assert_eq!(prefix, expected);
291+
}
292+
}

erc8004/src/reputation.rs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ impl<P: Provider> Reputation<P> {
146146
pub async fn read_all_feedback(
147147
&self,
148148
agent_id: U256,
149-
client_addresses: Vec<Address>,
149+
client_addresses: &[Address],
150150
tag1: &str,
151151
tag2: &str,
152152
include_revoked: bool,
@@ -155,7 +155,7 @@ impl<P: Provider> Reputation<P> {
155155
Ok(contract
156156
.readAllFeedback(
157157
agent_id,
158-
client_addresses,
158+
client_addresses.to_vec(),
159159
tag1.to_owned(),
160160
tag2.to_owned(),
161161
include_revoked,
@@ -175,13 +175,18 @@ impl<P: Provider> Reputation<P> {
175175
pub async fn get_summary(
176176
&self,
177177
agent_id: U256,
178-
client_addresses: Vec<Address>,
178+
client_addresses: &[Address],
179179
tag1: &str,
180180
tag2: &str,
181181
) -> Result<ReputationSummary> {
182182
let contract = ReputationRegistry::new(self.address, &self.provider);
183183
let r = contract
184-
.getSummary(agent_id, client_addresses, tag1.to_owned(), tag2.to_owned())
184+
.getSummary(
185+
agent_id,
186+
client_addresses.to_vec(),
187+
tag1.to_owned(),
188+
tag2.to_owned(),
189+
)
185190
.call()
186191
.await?;
187192
Ok(ReputationSummary {
@@ -224,11 +229,16 @@ impl<P: Provider> Reputation<P> {
224229
agent_id: U256,
225230
client_address: Address,
226231
feedback_index: u64,
227-
responders: Vec<Address>,
232+
responders: &[Address],
228233
) -> Result<u64> {
229234
let contract = ReputationRegistry::new(self.address, &self.provider);
230235
Ok(contract
231-
.getResponseCount(agent_id, client_address, feedback_index, responders)
236+
.getResponseCount(
237+
agent_id,
238+
client_address,
239+
feedback_index,
240+
responders.to_vec(),
241+
)
232242
.call()
233243
.await?)
234244
}

0 commit comments

Comments
 (0)