use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use crate::error::{IdentityError, Result};
pub type EthAddress = [u8; 20];
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Erc8004Addresses {
pub identity_registry: EthAddress,
pub reputation_registry: EthAddress,
pub validation_registry: EthAddress,
}
#[async_trait]
pub trait Erc8004Transport: Send + Sync {
async fn eth_call(&self, to: &EthAddress, data: &[u8]) -> Result<Vec<u8>>;
async fn eth_send_raw(&self, signed_tx: &[u8]) -> Result<String>;
}
pub trait OnChainAgentRegistry: Send + Sync {
fn mirror_register_agent(
&self,
did: &str,
agent_address: &EthAddress,
metadata_uri: &str,
) -> Result<()>;
fn lookup_agent_id_by_did(&self, did: &str) -> Option<u64>;
}
pub mod addresses {
use super::EthAddress;
pub const IDENTITY_REGISTRY: EthAddress = [
0x80, 0x04, 0xA8, 0x18, 0xBF, 0xB9, 0x12, 0x23, 0x3c, 0x49,
0x18, 0x71, 0xb3, 0xd8, 0x4c, 0x89, 0xA4, 0x94, 0xBD, 0x9e,
];
pub const REPUTATION_REGISTRY: EthAddress = [
0x80, 0x04, 0xB6, 0x63, 0x05, 0x6A, 0x59, 0x7D, 0xff, 0xe9,
0xEc, 0xcC, 0x19, 0x65, 0xA1, 0x93, 0xB7, 0x38, 0x87, 0x13,
];
pub const VALIDATION_REGISTRY: EthAddress = [
0x80, 0x04, 0xCb, 0x1B, 0xF3, 0x1D, 0xAf, 0x77, 0x88, 0x92,
0x3b, 0x40, 0x5b, 0x75, 0x4f, 0x57, 0xac, 0xEB, 0x42, 0x72,
];
}
pub mod selectors {
pub const REGISTER: [u8; 4] = [0x1a, 0xa3, 0xa0, 0x08];
pub const REGISTER_WITH_URI: [u8; 4] = [0xf2, 0xc2, 0x98, 0xbe];
pub const REGISTER_WITH_METADATA: [u8; 4] = [0x8e, 0xa4, 0x22, 0x86];
pub const GET_AGENT: [u8; 4] = [0x2d, 0xe5, 0xaa, 0xf7];
pub const SET_AGENT_URI: [u8; 4] = [0x0a, 0xf2, 0x8b, 0xd3];
pub const SET_AGENT_WALLET: [u8; 4] = [0x2d, 0x1e, 0xf5, 0xae];
pub const UNSET_AGENT_WALLET: [u8; 4] = [0x3f, 0xdd, 0xcf, 0x19];
pub const SET_METADATA: [u8; 4] = [0x46, 0x66, 0x48, 0xda];
pub const GET_METADATA: [u8; 4] = [0xcb, 0x47, 0x99, 0xf2];
pub const GET_AGENT_URI: [u8; 4] = [0xce, 0x91, 0xae, 0xde];
pub const GET_AGENT_WALLET: [u8; 4] = [0x00, 0x33, 0x95, 0x09];
pub const SUBMIT_FEEDBACK: [u8; 4] = [0xe5, 0x67, 0x9c, 0x29];
pub const GET_FEEDBACK: [u8; 4] = [0x2d, 0x15, 0x04, 0x57];
pub const GET_FEEDBACK_COUNT: [u8; 4] = [0x45, 0x37, 0xb7, 0x64];
pub const REVOKE_FEEDBACK: [u8; 4] = [0xa2, 0x83, 0x34, 0xce];
pub const APPEND_RESPONSE: [u8; 4] = [0x60, 0x1f, 0x56, 0x76];
pub const IS_FEEDBACK_REVOKED: [u8; 4] = [0xb0, 0x17, 0xcb, 0x04];
pub const GET_FEEDBACK_RESPONSES: [u8; 4] = [0xcc, 0x84, 0x63, 0x3b];
pub const VALIDATION_REQUEST: [u8; 4] = [0xaa, 0xf4, 0x00, 0xc4];
pub const VALIDATION_RESPONSE: [u8; 4] = [0x3d, 0x65, 0x9a, 0x96];
pub const GET_VALIDATION: [u8; 4] = [0xa8, 0x09, 0x1f, 0xc3];
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct AgentRecord {
pub agent_id: u64,
pub agent_address: EthAddress,
pub metadata_uri: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct MetadataEntry {
pub metadata_key: String,
pub metadata_value: Vec<u8>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FeedbackEntry {
pub subject_agent_id: u64,
pub rating: i8,
pub context_uri: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ValidationRequest {
pub validator_address: EthAddress,
pub agent_id: u64,
pub request_uri: String,
pub request_hash: [u8; 32],
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ValidationResult {
pub request_hash: [u8; 32],
pub response: u8,
pub response_uri: String,
pub response_hash: [u8; 32],
pub tag: String,
}
pub fn agent_id_to_uint256_be(agent_id: u64) -> [u8; 32] {
let mut out = [0u8; 32];
out[24..32].copy_from_slice(&agent_id.to_be_bytes());
out
}
pub fn agent_id_from_uint256_be(word: &[u8; 32]) -> Option<u64> {
if word[..24].iter().any(|b| *b != 0) {
return None;
}
Some(u64::from_be_bytes(word[24..32].try_into().ok()?))
}
pub mod abi {
use super::{EthAddress, MetadataEntry};
fn pad32_left(value: &[u8]) -> [u8; 32] {
assert!(value.len() <= 32);
let mut out = [0u8; 32];
out[32 - value.len()..].copy_from_slice(value);
out
}
fn encode_bytes_tail(bytes: &[u8]) -> Vec<u8> {
let len = bytes.len();
let mut out = Vec::with_capacity(32 + len.div_ceil(32) * 32);
out.extend_from_slice(&pad32_left(&(len as u64).to_be_bytes()));
out.extend_from_slice(bytes);
let pad = (32 - (len % 32)) % 32;
out.extend(std::iter::repeat_n(0u8, pad));
out
}
fn agent_id_word(agent_id: u64) -> [u8; 32] {
let mut out = [0u8; 32];
out[24..32].copy_from_slice(&agent_id.to_be_bytes());
out
}
pub fn encode_register(selector: [u8; 4]) -> Vec<u8> {
selector.to_vec()
}
pub fn encode_register_with_uri(selector: [u8; 4], agent_uri: &str) -> Vec<u8> {
let mut data = Vec::with_capacity(4 + 32 + 32 + agent_uri.len().div_ceil(32) * 32);
data.extend_from_slice(&selector);
data.extend_from_slice(&pad32_left(&(32u64).to_be_bytes()));
data.extend_from_slice(&encode_bytes_tail(agent_uri.as_bytes()));
data
}
pub fn encode_register_with_metadata(
selector: [u8; 4],
agent_uri: &str,
metadata: &[MetadataEntry],
) -> Vec<u8> {
let uri_block_len = 32 + agent_uri.len().div_ceil(32) * 32;
let metadata_offset = 64 + uri_block_len;
let metadata_body = encode_metadata_array_body(metadata);
let mut data =
Vec::with_capacity(4 + 64 + uri_block_len + 32 + metadata_body.len());
data.extend_from_slice(&selector);
data.extend_from_slice(&pad32_left(&(64u64).to_be_bytes()));
data.extend_from_slice(&pad32_left(&(metadata_offset as u64).to_be_bytes()));
data.extend_from_slice(&encode_bytes_tail(agent_uri.as_bytes()));
data.extend_from_slice(&pad32_left(&(metadata.len() as u64).to_be_bytes()));
data.extend_from_slice(&metadata_body);
data
}
fn encode_metadata_array_body(metadata: &[MetadataEntry]) -> Vec<u8> {
let n = metadata.len();
let tuples: Vec<Vec<u8>> = metadata.iter().map(encode_metadata_tuple).collect();
let offsets_block_len = n * 32;
let mut offsets = Vec::with_capacity(offsets_block_len);
let mut running = offsets_block_len;
for tuple in &tuples {
offsets.extend_from_slice(&pad32_left(&(running as u64).to_be_bytes()));
running += tuple.len();
}
let mut out = Vec::with_capacity(running);
out.extend_from_slice(&offsets);
for tuple in &tuples {
out.extend_from_slice(tuple);
}
out
}
fn encode_metadata_tuple(entry: &MetadataEntry) -> Vec<u8> {
let key_block_len = 32 + entry.metadata_key.len().div_ceil(32) * 32;
let value_block_len = 32 + entry.metadata_value.len().div_ceil(32) * 32;
let mut out = Vec::with_capacity(64 + key_block_len + value_block_len);
out.extend_from_slice(&pad32_left(&(64u64).to_be_bytes()));
out.extend_from_slice(&pad32_left(&((64 + key_block_len) as u64).to_be_bytes()));
out.extend_from_slice(&encode_bytes_tail(entry.metadata_key.as_bytes()));
out.extend_from_slice(&encode_bytes_tail(&entry.metadata_value));
out
}
pub fn encode_get_agent(selector: [u8; 4], agent_id: u64) -> Vec<u8> {
let mut data = Vec::with_capacity(4 + 32);
data.extend_from_slice(&selector);
data.extend_from_slice(&agent_id_word(agent_id));
data
}
pub fn encode_unset_agent_wallet(selector: [u8; 4], agent_id: u64) -> Vec<u8> {
let mut data = Vec::with_capacity(4 + 32);
data.extend_from_slice(&selector);
data.extend_from_slice(&agent_id_word(agent_id));
data
}
pub fn encode_submit_feedback(
selector: [u8; 4],
subject_agent_id: u64,
rating: i8,
context_uri: &str,
) -> Vec<u8> {
let mut data = Vec::with_capacity(4 + 3 * 32);
data.extend_from_slice(&selector);
data.extend_from_slice(&agent_id_word(subject_agent_id));
let mut rating_word = [0u8; 32];
let sign_ext = if rating < 0 { 0xffu8 } else { 0u8 };
for byte in rating_word.iter_mut().take(31) {
*byte = sign_ext;
}
rating_word[31] = rating as u8;
data.extend_from_slice(&rating_word);
data.extend_from_slice(&pad32_left(&(96u64).to_be_bytes()));
data.extend_from_slice(&encode_bytes_tail(context_uri.as_bytes()));
data
}
pub fn encode_validation_request(
selector: [u8; 4],
validator_address: &EthAddress,
agent_id: u64,
request_uri: &str,
request_hash: &[u8; 32],
) -> Vec<u8> {
let mut data = Vec::with_capacity(4 + 4 * 32 + request_uri.len().div_ceil(32) * 32 + 32);
data.extend_from_slice(&selector);
data.extend_from_slice(&pad32_left(validator_address));
data.extend_from_slice(&agent_id_word(agent_id));
data.extend_from_slice(&pad32_left(&(128u64).to_be_bytes()));
data.extend_from_slice(request_hash);
data.extend_from_slice(&encode_bytes_tail(request_uri.as_bytes()));
data
}
pub fn encode_validation_response(
selector: [u8; 4],
request_hash: &[u8; 32],
response: u8,
response_uri: &str,
response_hash: &[u8; 32],
tag: &str,
) -> Vec<u8> {
let head_len = 5 * 32;
let uri_block_len = 32 + response_uri.len().div_ceil(32) * 32;
let tag_offset = (head_len + uri_block_len) as u64;
let mut data = Vec::with_capacity(
4 + head_len + uri_block_len + 32 + tag.len().div_ceil(32) * 32,
);
data.extend_from_slice(&selector);
data.extend_from_slice(request_hash);
let mut response_word = [0u8; 32];
response_word[31] = response;
data.extend_from_slice(&response_word);
data.extend_from_slice(&pad32_left(&(160u64).to_be_bytes()));
data.extend_from_slice(response_hash);
data.extend_from_slice(&pad32_left(&tag_offset.to_be_bytes()));
data.extend_from_slice(&encode_bytes_tail(response_uri.as_bytes()));
data.extend_from_slice(&encode_bytes_tail(tag.as_bytes()));
data
}
pub fn encode_get_feedback(
selector: [u8; 4],
subject_agent_id: u64,
index: u128,
) -> Vec<u8> {
let mut data = Vec::with_capacity(4 + 2 * 32);
data.extend_from_slice(&selector);
data.extend_from_slice(&agent_id_word(subject_agent_id));
let mut idx_word = [0u8; 32];
idx_word[16..32].copy_from_slice(&index.to_be_bytes());
data.extend_from_slice(&idx_word);
data
}
pub fn encode_get_feedback_count(selector: [u8; 4], subject_agent_id: u64) -> Vec<u8> {
let mut data = Vec::with_capacity(4 + 32);
data.extend_from_slice(&selector);
data.extend_from_slice(&agent_id_word(subject_agent_id));
data
}
pub fn encode_revoke_feedback(
selector: [u8; 4],
agent_id: u64,
feedback_id: &[u8; 32],
) -> Vec<u8> {
let mut data = Vec::with_capacity(4 + 2 * 32);
data.extend_from_slice(&selector);
data.extend_from_slice(&agent_id_word(agent_id));
data.extend_from_slice(feedback_id);
data
}
pub fn encode_append_response(
selector: [u8; 4],
agent_id: u64,
feedback_id: &[u8; 32],
response_uri: &str,
) -> Vec<u8> {
let tail = encode_bytes_tail(response_uri.as_bytes());
let mut data = Vec::with_capacity(4 + 3 * 32 + tail.len());
data.extend_from_slice(&selector);
data.extend_from_slice(&agent_id_word(agent_id));
data.extend_from_slice(feedback_id);
data.extend_from_slice(&pad32_left(&(96u64).to_be_bytes()));
data.extend_from_slice(&tail);
data
}
pub fn encode_get_agent_uri(selector: [u8; 4], agent_id: u64) -> Vec<u8> {
let mut data = Vec::with_capacity(4 + 32);
data.extend_from_slice(&selector);
data.extend_from_slice(&agent_id_word(agent_id));
data
}
pub fn encode_get_agent_wallet(selector: [u8; 4], agent_id: u64) -> Vec<u8> {
let mut data = Vec::with_capacity(4 + 32);
data.extend_from_slice(&selector);
data.extend_from_slice(&agent_id_word(agent_id));
data
}
pub fn encode_is_feedback_revoked(
selector: [u8; 4],
agent_id: u64,
feedback_id: &[u8; 32],
) -> Vec<u8> {
let mut data = Vec::with_capacity(4 + 2 * 32);
data.extend_from_slice(&selector);
data.extend_from_slice(&agent_id_word(agent_id));
data.extend_from_slice(feedback_id);
data
}
pub fn encode_get_feedback_responses(
selector: [u8; 4],
agent_id: u64,
feedback_id: &[u8; 32],
) -> Vec<u8> {
let mut data = Vec::with_capacity(4 + 2 * 32);
data.extend_from_slice(&selector);
data.extend_from_slice(&agent_id_word(agent_id));
data.extend_from_slice(feedback_id);
data
}
pub fn decode_get_agent_uri(data: &[u8]) -> Option<String> {
if data.len() < 64 {
return None;
}
let offset = u64::from_be_bytes(data[24..32].try_into().ok()?) as usize;
if offset + 32 > data.len() {
return None;
}
let len = u64::from_be_bytes(data[offset + 24..offset + 32].try_into().ok()?) as usize;
if len == 0 {
return Some(String::new());
}
if offset + 32 + len > data.len() {
return None;
}
String::from_utf8(data[offset + 32..offset + 32 + len].to_vec()).ok()
}
pub fn decode_get_agent_wallet(data: &[u8]) -> Option<EthAddress> {
if data.len() < 32 {
return None;
}
let mut addr = [0u8; 20];
addr.copy_from_slice(&data[12..32]);
Some(addr)
}
pub fn decode_is_feedback_revoked(data: &[u8]) -> Option<bool> {
if data.len() < 32 {
return None;
}
Some(data[31] != 0)
}
pub fn decode_get_feedback_responses(data: &[u8]) -> Option<String> {
if data.len() < 64 {
return None;
}
let offset = u64::from_be_bytes(data[24..32].try_into().ok()?) as usize;
if offset + 32 > data.len() {
return None;
}
let len = u64::from_be_bytes(data[offset + 24..offset + 32].try_into().ok()?) as usize;
if len == 0 {
return Some(String::new());
}
if offset + 32 + len > data.len() {
return None;
}
String::from_utf8(data[offset + 32..offset + 32 + len].to_vec()).ok()
}
pub fn encode_get_validation(selector: [u8; 4], request_id: &[u8; 32]) -> Vec<u8> {
let mut data = Vec::with_capacity(4 + 32);
data.extend_from_slice(&selector);
data.extend_from_slice(request_id);
data
}
pub fn encode_set_agent_uri(
selector: [u8; 4],
agent_id: u64,
metadata_uri: &str,
) -> Vec<u8> {
let mut data = Vec::with_capacity(4 + 64 + 32 + metadata_uri.len().div_ceil(32) * 32);
data.extend_from_slice(&selector);
data.extend_from_slice(&agent_id_word(agent_id));
data.extend_from_slice(&pad32_left(&(64u64).to_be_bytes()));
data.extend_from_slice(&encode_bytes_tail(metadata_uri.as_bytes()));
data
}
pub fn encode_set_agent_wallet(
selector: [u8; 4],
agent_id: u64,
new_wallet: &EthAddress,
deadline: u128,
signature: &[u8],
) -> Vec<u8> {
let mut data = Vec::with_capacity(4 + 4 * 32 + 32 + signature.len().div_ceil(32) * 32);
data.extend_from_slice(&selector);
data.extend_from_slice(&agent_id_word(agent_id));
data.extend_from_slice(&pad32_left(new_wallet));
let mut deadline_word = [0u8; 32];
deadline_word[16..32].copy_from_slice(&deadline.to_be_bytes());
data.extend_from_slice(&deadline_word);
data.extend_from_slice(&pad32_left(&(128u64).to_be_bytes()));
data.extend_from_slice(&encode_bytes_tail(signature));
data
}
pub fn encode_set_metadata(
selector: [u8; 4],
agent_id: u64,
metadata_key: &str,
metadata_value: &[u8],
) -> Vec<u8> {
let key_block = 32 + metadata_key.len().div_ceil(32) * 32;
let value_offset = 96 + key_block;
let value_block = 32 + metadata_value.len().div_ceil(32) * 32;
let mut data = Vec::with_capacity(4 + 96 + key_block + value_block);
data.extend_from_slice(&selector);
data.extend_from_slice(&agent_id_word(agent_id));
data.extend_from_slice(&pad32_left(&(96u64).to_be_bytes()));
data.extend_from_slice(&pad32_left(&(value_offset as u64).to_be_bytes()));
data.extend_from_slice(&encode_bytes_tail(metadata_key.as_bytes()));
data.extend_from_slice(&encode_bytes_tail(metadata_value));
data
}
pub fn encode_get_metadata(
selector: [u8; 4],
agent_id: u64,
metadata_key: &str,
) -> Vec<u8> {
let mut data = Vec::with_capacity(4 + 64 + 32 + metadata_key.len().div_ceil(32) * 32);
data.extend_from_slice(&selector);
data.extend_from_slice(&agent_id_word(agent_id));
data.extend_from_slice(&pad32_left(&(64u64).to_be_bytes()));
data.extend_from_slice(&encode_bytes_tail(metadata_key.as_bytes()));
data
}
pub fn decode_get_metadata(data: &[u8]) -> Option<Vec<u8>> {
if data.len() < 64 {
return None;
}
let offset = u64::from_be_bytes(data[24..32].try_into().ok()?) as usize;
if offset + 32 > data.len() {
return None;
}
let len = u64::from_be_bytes(data[offset + 24..offset + 32].try_into().ok()?) as usize;
if len == 0 {
return Some(Vec::new());
}
if offset + 32 + len > data.len() {
return None;
}
Some(data[offset + 32..offset + 32 + len].to_vec())
}
pub fn decode_get_feedback(data: &[u8]) -> Option<(i8, String, bool)> {
if data.len() < 96 {
return None;
}
let rating = data[31] as i8;
let offset = u64::from_be_bytes(data[32 + 24..64].try_into().ok()?) as usize;
let exists = data[95] != 0;
if offset >= data.len() || offset + 32 > data.len() {
return None;
}
let len = u64::from_be_bytes(data[offset + 24..offset + 32].try_into().ok()?) as usize;
if offset + 32 + len > data.len() {
return None;
}
let uri = String::from_utf8(data[offset + 32..offset + 32 + len].to_vec()).ok()?;
Some((rating, uri, exists))
}
pub fn decode_get_feedback_count(data: &[u8]) -> Option<u128> {
if data.len() < 32 {
return None;
}
Some(u128::from_be_bytes(data[16..32].try_into().ok()?))
}
pub struct DecodedValidation {
pub validator: EthAddress,
pub agent_id: Option<u64>,
pub response: u8,
pub request_uri: String,
pub response_uri: String,
pub tag: String,
pub exists: bool,
}
pub fn decode_get_validation(data: &[u8]) -> Option<DecodedValidation> {
if data.len() < 7 * 32 {
return None;
}
let mut validator = [0u8; 20];
validator.copy_from_slice(&data[12..32]);
let mut agent_id_word = [0u8; 32];
agent_id_word.copy_from_slice(&data[32..64]);
let agent_id = super::agent_id_from_uint256_be(&agent_id_word);
let response = data[95];
let request_uri_offset = u64::from_be_bytes(data[96 + 24..128].try_into().ok()?) as usize;
let response_uri_offset = u64::from_be_bytes(data[128 + 24..160].try_into().ok()?) as usize;
let tag_offset = u64::from_be_bytes(data[160 + 24..192].try_into().ok()?) as usize;
let exists = data[223] != 0;
let request_uri = decode_string_at(data, request_uri_offset)?;
let response_uri = decode_string_at(data, response_uri_offset)?;
let tag = decode_string_at(data, tag_offset)?;
Some(DecodedValidation {
validator,
agent_id,
response,
request_uri,
response_uri,
tag,
exists,
})
}
fn decode_string_at(data: &[u8], offset: usize) -> Option<String> {
if offset + 32 > data.len() {
return None;
}
let len = u64::from_be_bytes(data[offset + 24..offset + 32].try_into().ok()?) as usize;
if offset + 32 + len > data.len() {
return None;
}
String::from_utf8(data[offset + 32..offset + 32 + len].to_vec()).ok()
}
pub fn decode_get_agent(data: &[u8]) -> Option<(EthAddress, String)> {
if data.len() < 64 {
return None;
}
let mut addr = [0u8; 20];
addr.copy_from_slice(&data[12..32]);
let offset = u64::from_be_bytes(data[32 + 24..64].try_into().ok()?) as usize;
if offset >= data.len() || offset + 32 > data.len() {
return None;
}
let len = u64::from_be_bytes(data[offset + 24..offset + 32].try_into().ok()?) as usize;
if offset + 32 + len > data.len() {
return None;
}
let uri = String::from_utf8(data[offset + 32..offset + 32 + len].to_vec()).ok()?;
Some((addr, uri))
}
}
pub struct Erc8004Adapter<T: Erc8004Transport> {
transport: T,
addresses: Erc8004Addresses,
}
impl<T: Erc8004Transport> Erc8004Adapter<T> {
pub fn new(transport: T, addresses: Erc8004Addresses) -> Self {
Self { transport, addresses }
}
pub fn addresses(&self) -> &Erc8004Addresses {
&self.addresses
}
pub fn build_register_calldata(&self) -> Vec<u8> {
abi::encode_register(selectors::REGISTER)
}
pub fn build_register_with_uri_calldata(&self, agent_uri: &str) -> Vec<u8> {
abi::encode_register_with_uri(selectors::REGISTER_WITH_URI, agent_uri)
}
pub fn build_register_with_metadata_calldata(
&self,
agent_uri: &str,
metadata: &[MetadataEntry],
) -> Vec<u8> {
abi::encode_register_with_metadata(
selectors::REGISTER_WITH_METADATA,
agent_uri,
metadata,
)
}
pub fn build_unset_agent_wallet_calldata(&self, agent_id: u64) -> Vec<u8> {
abi::encode_unset_agent_wallet(selectors::UNSET_AGENT_WALLET, agent_id)
}
pub async fn get_agent(&self, agent_id: u64) -> Result<AgentRecord> {
let data = abi::encode_get_agent(selectors::GET_AGENT, agent_id);
let ret = self
.transport
.eth_call(&self.addresses.identity_registry, &data)
.await?;
let (addr, uri) = abi::decode_get_agent(&ret).ok_or_else(|| {
IdentityError::VerificationFailed(
"failed to decode ERC-8004 getAgent return".into(),
)
})?;
Ok(AgentRecord {
agent_id,
agent_address: addr,
metadata_uri: uri,
})
}
pub fn build_submit_feedback_calldata(&self, entry: &FeedbackEntry) -> Vec<u8> {
abi::encode_submit_feedback(
selectors::SUBMIT_FEEDBACK,
entry.subject_agent_id,
entry.rating,
&entry.context_uri,
)
}
pub fn build_revoke_feedback_calldata(
&self,
agent_id: u64,
feedback_id: &[u8; 32],
) -> Vec<u8> {
abi::encode_revoke_feedback(selectors::REVOKE_FEEDBACK, agent_id, feedback_id)
}
pub fn build_append_response_calldata(
&self,
agent_id: u64,
feedback_id: &[u8; 32],
response_uri: &str,
) -> Vec<u8> {
abi::encode_append_response(
selectors::APPEND_RESPONSE,
agent_id,
feedback_id,
response_uri,
)
}
pub fn build_validation_request_calldata(&self, request: &ValidationRequest) -> Vec<u8> {
abi::encode_validation_request(
selectors::VALIDATION_REQUEST,
&request.validator_address,
request.agent_id,
&request.request_uri,
&request.request_hash,
)
}
pub fn build_validation_response_calldata(&self, result: &ValidationResult) -> Vec<u8> {
abi::encode_validation_response(
selectors::VALIDATION_RESPONSE,
&result.request_hash,
result.response,
&result.response_uri,
&result.response_hash,
&result.tag,
)
}
pub async fn get_feedback(
&self,
subject_agent_id: u64,
index: u128,
) -> Result<(FeedbackEntry, bool)> {
let data = abi::encode_get_feedback(selectors::GET_FEEDBACK, subject_agent_id, index);
let ret = self
.transport
.eth_call(&self.addresses.reputation_registry, &data)
.await?;
let (rating, context_uri, exists) = abi::decode_get_feedback(&ret).ok_or_else(|| {
IdentityError::VerificationFailed(
"failed to decode ERC-8004 getFeedback return".into(),
)
})?;
Ok((
FeedbackEntry {
subject_agent_id,
rating,
context_uri,
},
exists,
))
}
pub async fn get_feedback_count(&self, subject_agent_id: u64) -> Result<u128> {
let data = abi::encode_get_feedback_count(selectors::GET_FEEDBACK_COUNT, subject_agent_id);
let ret = self
.transport
.eth_call(&self.addresses.reputation_registry, &data)
.await?;
abi::decode_get_feedback_count(&ret).ok_or_else(|| {
IdentityError::VerificationFailed(
"failed to decode ERC-8004 getFeedbackCount return".into(),
)
})
}
pub async fn get_validation(
&self,
request_id: &[u8; 32],
) -> Result<abi::DecodedValidation> {
let data = abi::encode_get_validation(selectors::GET_VALIDATION, request_id);
let ret = self
.transport
.eth_call(&self.addresses.validation_registry, &data)
.await?;
abi::decode_get_validation(&ret).ok_or_else(|| {
IdentityError::VerificationFailed(
"failed to decode ERC-8004 getValidation return".into(),
)
})
}
pub fn build_set_agent_uri_calldata(
&self,
agent_id: u64,
metadata_uri: &str,
) -> Vec<u8> {
abi::encode_set_agent_uri(selectors::SET_AGENT_URI, agent_id, metadata_uri)
}
pub fn build_set_agent_wallet_calldata(
&self,
agent_id: u64,
new_wallet: &EthAddress,
deadline: u128,
signature: &[u8],
) -> Vec<u8> {
abi::encode_set_agent_wallet(
selectors::SET_AGENT_WALLET,
agent_id,
new_wallet,
deadline,
signature,
)
}
pub fn build_set_metadata_calldata(
&self,
agent_id: u64,
metadata_key: &str,
metadata_value: &[u8],
) -> Vec<u8> {
abi::encode_set_metadata(
selectors::SET_METADATA,
agent_id,
metadata_key,
metadata_value,
)
}
pub async fn get_metadata(
&self,
agent_id: u64,
metadata_key: &str,
) -> Result<Vec<u8>> {
let data = abi::encode_get_metadata(selectors::GET_METADATA, agent_id, metadata_key);
let ret = self
.transport
.eth_call(&self.addresses.identity_registry, &data)
.await?;
abi::decode_get_metadata(&ret).ok_or_else(|| {
IdentityError::VerificationFailed(
"failed to decode ERC-8004 getMetadata return".into(),
)
})
}
pub async fn get_agent_uri(&self, agent_id: u64) -> Result<String> {
let data = abi::encode_get_agent_uri(selectors::GET_AGENT_URI, agent_id);
let ret = self
.transport
.eth_call(&self.addresses.identity_registry, &data)
.await?;
abi::decode_get_agent_uri(&ret).ok_or_else(|| {
IdentityError::VerificationFailed(
"failed to decode ERC-8004 getAgentURI return".into(),
)
})
}
pub async fn get_agent_wallet(&self, agent_id: u64) -> Result<EthAddress> {
let data = abi::encode_get_agent_wallet(selectors::GET_AGENT_WALLET, agent_id);
let ret = self
.transport
.eth_call(&self.addresses.identity_registry, &data)
.await?;
abi::decode_get_agent_wallet(&ret).ok_or_else(|| {
IdentityError::VerificationFailed(
"failed to decode ERC-8004 getAgentWallet return".into(),
)
})
}
pub async fn is_feedback_revoked(
&self,
agent_id: u64,
feedback_id: &[u8; 32],
) -> Result<bool> {
let data = abi::encode_is_feedback_revoked(
selectors::IS_FEEDBACK_REVOKED,
agent_id,
feedback_id,
);
let ret = self
.transport
.eth_call(&self.addresses.reputation_registry, &data)
.await?;
abi::decode_is_feedback_revoked(&ret).ok_or_else(|| {
IdentityError::VerificationFailed(
"failed to decode ERC-8004 isFeedbackRevoked return".into(),
)
})
}
pub async fn get_feedback_responses(
&self,
agent_id: u64,
feedback_id: &[u8; 32],
) -> Result<String> {
let data = abi::encode_get_feedback_responses(
selectors::GET_FEEDBACK_RESPONSES,
agent_id,
feedback_id,
);
let ret = self
.transport
.eth_call(&self.addresses.reputation_registry, &data)
.await?;
abi::decode_get_feedback_responses(&ret).ok_or_else(|| {
IdentityError::VerificationFailed(
"failed to decode ERC-8004 getFeedbackResponses return".into(),
)
})
}
pub async fn send_signed(&self, signed_tx: &[u8]) -> Result<String> {
self.transport.eth_send_raw(signed_tx).await
}
}
#[cfg(test)]
mod tests {
use super::*;
struct MockTransport {
agent_return: Vec<u8>,
}
#[async_trait]
impl Erc8004Transport for MockTransport {
async fn eth_call(&self, _to: &EthAddress, _data: &[u8]) -> Result<Vec<u8>> {
Ok(self.agent_return.clone())
}
async fn eth_send_raw(&self, _tx: &[u8]) -> Result<String> {
Ok("0xdeadbeef".into())
}
}
fn mk_addresses() -> Erc8004Addresses {
Erc8004Addresses {
identity_registry: [1u8; 20],
reputation_registry: [2u8; 20],
validation_registry: [3u8; 20],
}
}
#[test]
fn agent_id_uint256_be_roundtrip() {
for id in [0u64, 1, 42, 1_000_000, u64::MAX] {
let word = agent_id_to_uint256_be(id);
assert_eq!(&word[..24], &[0u8; 24]);
assert_eq!(&word[24..], &id.to_be_bytes());
assert_eq!(agent_id_from_uint256_be(&word), Some(id));
}
}
#[test]
fn agent_id_from_uint256_rejects_high_bits() {
let mut word = [0u8; 32];
word[0] = 1; assert_eq!(agent_id_from_uint256_be(&word), None);
}
#[test]
fn register_selectors_match_canonical_keccak() {
assert_eq!(selectors::REGISTER, [0x1a, 0xa3, 0xa0, 0x08]);
assert_eq!(selectors::REGISTER_WITH_URI, [0xf2, 0xc2, 0x98, 0xbe]);
assert_eq!(selectors::REGISTER_WITH_METADATA, [0x8e, 0xa4, 0x22, 0x86]);
assert_eq!(selectors::UNSET_AGENT_WALLET, [0x3f, 0xdd, 0xcf, 0x19]);
assert_eq!(selectors::GET_AGENT, [0x2d, 0xe5, 0xaa, 0xf7]);
assert_eq!(selectors::SUBMIT_FEEDBACK, [0xe5, 0x67, 0x9c, 0x29]);
assert_eq!(selectors::GET_FEEDBACK, [0x2d, 0x15, 0x04, 0x57]);
assert_eq!(selectors::GET_FEEDBACK_COUNT, [0x45, 0x37, 0xb7, 0x64]);
assert_eq!(selectors::GET_VALIDATION, [0xa8, 0x09, 0x1f, 0xc3]);
}
#[test]
fn encode_register_no_args() {
let data = abi::encode_register(selectors::REGISTER);
assert_eq!(data, selectors::REGISTER.to_vec());
}
#[test]
fn encode_register_with_uri_layout() {
let uri = "ipfs://card";
let data = abi::encode_register_with_uri(selectors::REGISTER_WITH_URI, uri);
assert_eq!(&data[0..4], &selectors::REGISTER_WITH_URI);
assert_eq!(data[35], 32);
assert_eq!(data[67] as usize, uri.len());
assert_eq!(&data[68..68 + uri.len()], uri.as_bytes());
}
#[test]
fn encode_register_with_metadata_packs_uri_then_array() {
let uri = "ipfs://card";
let entries = vec![
MetadataEntry {
metadata_key: "skills".into(),
metadata_value: b"forecast".to_vec(),
},
MetadataEntry {
metadata_key: "model".into(),
metadata_value: b"chronos".to_vec(),
},
];
let data =
abi::encode_register_with_metadata(selectors::REGISTER_WITH_METADATA, uri, &entries);
assert_eq!(&data[0..4], &selectors::REGISTER_WITH_METADATA);
assert_eq!(data[35], 64);
assert_eq!(data[67], 128);
assert_eq!(data[4 + 64 + 31] as usize, uri.len());
assert_eq!(&data[4 + 96..4 + 96 + uri.len()], uri.as_bytes());
assert_eq!(data[4 + 128 + 31], 2);
}
#[test]
fn encode_register_with_metadata_empty_array() {
let uri = "x";
let data =
abi::encode_register_with_metadata(selectors::REGISTER_WITH_METADATA, uri, &[]);
assert_eq!(data.len(), 4 + 160);
assert_eq!(&data[4 + 128..4 + 160], &[0u8; 32]);
}
#[test]
fn encode_unset_agent_wallet_layout() {
let data = abi::encode_unset_agent_wallet(selectors::UNSET_AGENT_WALLET, 42);
assert_eq!(data.len(), 4 + 32);
assert_eq!(&data[0..4], &selectors::UNSET_AGENT_WALLET);
assert_eq!(data[35], 42);
}
#[test]
fn encode_submit_feedback_uses_uint256_subject() {
let data = abi::encode_submit_feedback(selectors::SUBMIT_FEEDBACK, 7, -50, "ctx");
assert_eq!(&data[0..4], &selectors::SUBMIT_FEEDBACK);
assert_eq!(data[35], 7);
assert_eq!(data[36..67], [0xff; 31]);
assert_eq!(data[67], (-50i8) as u8);
}
#[test]
fn encode_get_feedback_uses_uint256_subject_and_index() {
let data = abi::encode_get_feedback(selectors::GET_FEEDBACK, 9, 42);
assert_eq!(data.len(), 4 + 64);
assert_eq!(&data[0..4], &selectors::GET_FEEDBACK);
assert_eq!(data[35], 9);
assert_eq!(data[67], 42);
}
#[test]
fn encode_get_feedback_count_uses_uint256_subject() {
let data = abi::encode_get_feedback_count(selectors::GET_FEEDBACK_COUNT, 11);
assert_eq!(data.len(), 4 + 32);
assert_eq!(&data[0..4], &selectors::GET_FEEDBACK_COUNT);
assert_eq!(data[35], 11);
}
#[test]
fn decode_get_agent_roundtrip() {
let mut ret = Vec::new();
ret.extend_from_slice(&[0u8; 12]);
ret.extend_from_slice(&[0xab; 20]);
let mut offset = [0u8; 32];
offset[31] = 64;
ret.extend_from_slice(&offset);
let mut len = [0u8; 32];
len[31] = 10;
ret.extend_from_slice(&len);
ret.extend_from_slice(b"ipfs://abc");
ret.extend_from_slice(&[0u8; 22]);
let (addr, uri) = abi::decode_get_agent(&ret).unwrap();
assert_eq!(addr, [0xab; 20]);
assert_eq!(uri, "ipfs://abc");
}
#[tokio::test]
async fn adapter_get_agent_via_transport() {
let mut ret = Vec::new();
ret.extend_from_slice(&[0u8; 12]);
ret.extend_from_slice(&[0xab; 20]);
let mut offset = [0u8; 32];
offset[31] = 64;
ret.extend_from_slice(&offset);
let mut len = [0u8; 32];
len[31] = 5;
ret.extend_from_slice(&len);
ret.extend_from_slice(b"hello");
ret.extend_from_slice(&[0u8; 27]);
let adapter = Erc8004Adapter::new(MockTransport { agent_return: ret }, mk_addresses());
let record = adapter.get_agent(123).await.unwrap();
assert_eq!(record.agent_address, [0xab; 20]);
assert_eq!(record.metadata_uri, "hello");
assert_eq!(record.agent_id, 123);
}
#[test]
fn read_selectors_are_distinct_from_writes() {
let all = [
selectors::REGISTER,
selectors::REGISTER_WITH_URI,
selectors::REGISTER_WITH_METADATA,
selectors::GET_AGENT,
selectors::SET_AGENT_URI,
selectors::SET_AGENT_WALLET,
selectors::UNSET_AGENT_WALLET,
selectors::SET_METADATA,
selectors::GET_METADATA,
selectors::GET_AGENT_URI,
selectors::GET_AGENT_WALLET,
selectors::SUBMIT_FEEDBACK,
selectors::GET_FEEDBACK,
selectors::GET_FEEDBACK_COUNT,
selectors::REVOKE_FEEDBACK,
selectors::APPEND_RESPONSE,
selectors::IS_FEEDBACK_REVOKED,
selectors::GET_FEEDBACK_RESPONSES,
selectors::VALIDATION_REQUEST,
selectors::VALIDATION_RESPONSE,
selectors::GET_VALIDATION,
];
for i in 0..all.len() {
for j in (i + 1)..all.len() {
assert_ne!(all[i], all[j], "selector collision at indices {i},{j}");
}
}
}
#[test]
fn decode_get_feedback_round_trips_precompile_shape() {
let rating: i8 = -25;
let context = "ipfs://feedback-cid";
let mut ret = Vec::new();
let mut rating_word = [0xffu8; 32];
rating_word[31] = rating as u8;
ret.extend_from_slice(&rating_word);
let mut offset = [0u8; 32];
offset[31] = 96;
ret.extend_from_slice(&offset);
let mut exists = [0u8; 32];
exists[31] = 1;
ret.extend_from_slice(&exists);
let mut len_word = [0u8; 32];
len_word[31] = context.len() as u8;
ret.extend_from_slice(&len_word);
ret.extend_from_slice(context.as_bytes());
let pad = (32 - (context.len() % 32)) % 32;
ret.extend(std::iter::repeat_n(0u8, pad));
let (decoded_rating, decoded_uri, decoded_exists) =
abi::decode_get_feedback(&ret).expect("must decode");
assert_eq!(decoded_rating, rating);
assert_eq!(decoded_uri, context);
assert!(decoded_exists);
}
#[test]
fn decode_get_feedback_count_extracts_low_uint128() {
let mut ret = vec![0u8; 32];
ret[16..32].copy_from_slice(&1_234_567u128.to_be_bytes());
let count = abi::decode_get_feedback_count(&ret).unwrap();
assert_eq!(count, 1_234_567);
}
#[test]
fn decode_get_validation_narrows_agent_id_to_u64() {
let validator = [0x99u8; 20];
let agent_id_u64: u64 = 0x4242_4242_4242_4242;
let request_uri = "ipfs://task";
let response_uri = "ipfs://proof-doc";
let tag = "valid";
let response: u8 = 87;
let request_padded = request_uri.len().div_ceil(32) * 32;
let request_block = 32 + request_padded;
let response_uri_offset = 224 + request_block;
let response_padded = response_uri.len().div_ceil(32) * 32;
let response_block = 32 + response_padded;
let tag_offset = response_uri_offset + response_block;
let tag_padded = tag.len().div_ceil(32) * 32;
let total_len = tag_offset + 32 + tag_padded;
let mut ret = vec![0u8; total_len];
ret[12..32].copy_from_slice(&validator);
ret[56..64].copy_from_slice(&agent_id_u64.to_be_bytes());
ret[95] = response;
ret[96 + 24..128].copy_from_slice(&224u64.to_be_bytes());
ret[128 + 24..160].copy_from_slice(&(response_uri_offset as u64).to_be_bytes());
ret[160 + 24..192].copy_from_slice(&(tag_offset as u64).to_be_bytes());
ret[223] = 1;
ret[224 + 24..256].copy_from_slice(&(request_uri.len() as u64).to_be_bytes());
ret[256..256 + request_uri.len()].copy_from_slice(request_uri.as_bytes());
ret[response_uri_offset + 24..response_uri_offset + 32]
.copy_from_slice(&(response_uri.len() as u64).to_be_bytes());
ret[response_uri_offset + 32..response_uri_offset + 32 + response_uri.len()]
.copy_from_slice(response_uri.as_bytes());
ret[tag_offset + 24..tag_offset + 32]
.copy_from_slice(&(tag.len() as u64).to_be_bytes());
ret[tag_offset + 32..tag_offset + 32 + tag.len()].copy_from_slice(tag.as_bytes());
let decoded = abi::decode_get_validation(&ret).expect("must decode");
assert_eq!(decoded.validator, validator);
assert_eq!(decoded.agent_id, Some(agent_id_u64));
assert_eq!(decoded.response, response);
assert_eq!(decoded.request_uri, request_uri);
assert_eq!(decoded.response_uri, response_uri);
assert_eq!(decoded.tag, tag);
assert!(decoded.exists);
}
#[test]
fn encode_set_agent_uri_layout() {
let uri = "ipfs://updated-cid";
let data = abi::encode_set_agent_uri(selectors::SET_AGENT_URI, 0x42, uri);
assert_eq!(&data[0..4], &selectors::SET_AGENT_URI);
assert_eq!(data[35], 0x42);
assert_eq!(data[67], 64);
assert_eq!(data[4 + 64 + 31] as usize, uri.len());
assert_eq!(&data[4 + 96..4 + 96 + uri.len()], uri.as_bytes());
}
#[test]
fn encode_set_agent_wallet_layout() {
let new_wallet = [0x22u8; 20];
let signature = vec![0xaa; 65];
let data = abi::encode_set_agent_wallet(
selectors::SET_AGENT_WALLET,
0x11,
&new_wallet,
42,
&signature,
);
assert_eq!(&data[0..4], &selectors::SET_AGENT_WALLET);
assert_eq!(data[35], 0x11);
assert_eq!(&data[4 + 32 + 12..4 + 32 + 32], &new_wallet);
assert_eq!(data[4 + 64 + 31], 42);
assert_eq!(data[4 + 96 + 31], 128);
assert_eq!(data[4 + 128 + 31] as usize, signature.len());
assert_eq!(
&data[4 + 160..4 + 160 + signature.len()],
signature.as_slice(),
);
}
#[test]
fn encode_set_metadata_packs_key_then_value() {
let key = "skills";
let value: &[u8] = b"forecast,vision";
let data = abi::encode_set_metadata(selectors::SET_METADATA, 0x33, key, value);
assert_eq!(&data[0..4], &selectors::SET_METADATA);
assert_eq!(data[35], 0x33);
assert_eq!(data[4 + 32 + 31], 96);
assert_eq!(data[4 + 64 + 31], 160);
assert_eq!(data[4 + 96 + 31] as usize, key.len());
assert_eq!(&data[4 + 128..4 + 128 + key.len()], key.as_bytes());
assert_eq!(data[4 + 160 + 31] as usize, value.len());
assert_eq!(&data[4 + 192..4 + 192 + value.len()], value);
}
#[test]
fn encode_set_metadata_with_empty_value() {
let key = "drop";
let data = abi::encode_set_metadata(selectors::SET_METADATA, 0, key, &[]);
assert_eq!(data[4 + 32 + 31], 96);
assert_eq!(data[4 + 64 + 31], 160);
assert_eq!(data[4 + 160 + 31], 0);
}
#[test]
fn encode_get_metadata_layout() {
let key = "skills";
let data = abi::encode_get_metadata(selectors::GET_METADATA, 0x44, key);
assert_eq!(&data[0..4], &selectors::GET_METADATA);
assert_eq!(data[35], 0x44);
assert_eq!(data[4 + 32 + 31], 64);
assert_eq!(data[4 + 64 + 31] as usize, key.len());
assert_eq!(&data[4 + 96..4 + 96 + key.len()], key.as_bytes());
}
#[test]
fn decode_get_metadata_round_trips_precompile_shape() {
let value: &[u8] = b"hello-world-bytes";
let mut ret = Vec::new();
let mut offset = [0u8; 32];
offset[31] = 32;
ret.extend_from_slice(&offset);
let mut len_word = [0u8; 32];
len_word[31] = value.len() as u8;
ret.extend_from_slice(&len_word);
ret.extend_from_slice(value);
let pad = (32 - (value.len() % 32)) % 32;
ret.extend(std::iter::repeat_n(0u8, pad));
let decoded = abi::decode_get_metadata(&ret).expect("must decode");
assert_eq!(decoded, value);
}
#[test]
fn decode_get_metadata_unset_returns_empty_vec() {
let mut ret = vec![0u8; 64];
ret[31] = 32;
let decoded = abi::decode_get_metadata(&ret).expect("must decode");
assert!(decoded.is_empty());
}
#[test]
fn encode_revoke_feedback_layout() {
let feedback_id = [0xb2u8; 32];
let data = abi::encode_revoke_feedback(selectors::REVOKE_FEEDBACK, 0xa1, &feedback_id);
assert_eq!(data.len(), 4 + 64);
assert_eq!(&data[0..4], &selectors::REVOKE_FEEDBACK);
assert_eq!(data[35], 0xa1);
assert_eq!(&data[36..68], &feedback_id);
}
#[test]
fn encode_append_response_layout() {
let feedback_id = [0xd4u8; 32];
let response = "ipfs://defense";
let data = abi::encode_append_response(
selectors::APPEND_RESPONSE,
0xc3,
&feedback_id,
response,
);
assert_eq!(&data[0..4], &selectors::APPEND_RESPONSE);
assert_eq!(data[35], 0xc3);
assert_eq!(&data[36..68], &feedback_id);
assert_eq!(data[99], 96);
assert_eq!(data[131], response.len() as u8);
assert_eq!(&data[132..132 + response.len()], response.as_bytes());
let pad = response.len().div_ceil(32) * 32;
assert_eq!(data.len(), 4 + 96 + 32 + pad);
}
#[test]
fn encode_append_response_with_empty_uri() {
let feedback_id = [0u8; 32];
let data =
abi::encode_append_response(selectors::APPEND_RESPONSE, 0, &feedback_id, "");
assert_eq!(data.len(), 4 + 96 + 32);
assert_eq!(&data[100..132], &[0u8; 32]);
}
#[test]
fn revoke_and_append_selectors_match_canonical_keccak() {
assert_eq!(selectors::REVOKE_FEEDBACK, [0xa2, 0x83, 0x34, 0xce]);
assert_eq!(selectors::APPEND_RESPONSE, [0x60, 0x1f, 0x56, 0x76]);
}
#[test]
fn v06_read_selectors_match_canonical_keccak() {
assert_eq!(selectors::GET_AGENT_URI, [0xce, 0x91, 0xae, 0xde]);
assert_eq!(selectors::GET_AGENT_WALLET, [0x00, 0x33, 0x95, 0x09]);
assert_eq!(selectors::IS_FEEDBACK_REVOKED, [0xb0, 0x17, 0xcb, 0x04]);
assert_eq!(selectors::GET_FEEDBACK_RESPONSES, [0xcc, 0x84, 0x63, 0x3b]);
}
#[test]
fn encode_get_agent_uri_layout() {
let data = abi::encode_get_agent_uri(selectors::GET_AGENT_URI, 0x55);
assert_eq!(data.len(), 4 + 32);
assert_eq!(&data[0..4], &selectors::GET_AGENT_URI);
assert_eq!(data[35], 0x55);
}
#[test]
fn encode_get_agent_wallet_layout() {
let data = abi::encode_get_agent_wallet(selectors::GET_AGENT_WALLET, 0x66);
assert_eq!(data.len(), 4 + 32);
assert_eq!(&data[0..4], &selectors::GET_AGENT_WALLET);
assert_eq!(data[35], 0x66);
}
#[test]
fn encode_is_feedback_revoked_layout() {
let feedback_id = [0x88u8; 32];
let data = abi::encode_is_feedback_revoked(
selectors::IS_FEEDBACK_REVOKED,
0x77,
&feedback_id,
);
assert_eq!(data.len(), 4 + 64);
assert_eq!(&data[0..4], &selectors::IS_FEEDBACK_REVOKED);
assert_eq!(data[35], 0x77);
assert_eq!(&data[36..68], &feedback_id);
}
#[test]
fn encode_get_feedback_responses_layout() {
let feedback_id = [0xaau8; 32];
let data = abi::encode_get_feedback_responses(
selectors::GET_FEEDBACK_RESPONSES,
0x99,
&feedback_id,
);
assert_eq!(data.len(), 4 + 64);
assert_eq!(&data[0..4], &selectors::GET_FEEDBACK_RESPONSES);
assert_eq!(data[35], 0x99);
assert_eq!(&data[36..68], &feedback_id);
}
#[test]
fn decode_get_agent_uri_round_trips_precompile_shape() {
let uri = "ipfs://agent-uri-cid";
let mut ret = Vec::new();
let mut offset = [0u8; 32];
offset[31] = 32;
ret.extend_from_slice(&offset);
let mut len_word = [0u8; 32];
len_word[31] = uri.len() as u8;
ret.extend_from_slice(&len_word);
ret.extend_from_slice(uri.as_bytes());
let pad = (32 - (uri.len() % 32)) % 32;
ret.extend(std::iter::repeat_n(0u8, pad));
let decoded = abi::decode_get_agent_uri(&ret).expect("must decode");
assert_eq!(decoded, uri);
}
#[test]
fn decode_get_agent_uri_unset_returns_empty_string() {
let mut ret = vec![0u8; 64];
ret[31] = 32;
let decoded = abi::decode_get_agent_uri(&ret).expect("must decode");
assert!(decoded.is_empty());
}
#[test]
fn decode_get_agent_wallet_round_trips_precompile_shape() {
let wallet = [0xefu8; 20];
let mut ret = vec![0u8; 32];
ret[12..32].copy_from_slice(&wallet);
let decoded = abi::decode_get_agent_wallet(&ret).expect("must decode");
assert_eq!(decoded, wallet);
}
#[test]
fn decode_get_agent_wallet_unset_returns_zero_address() {
let ret = vec![0u8; 32];
let decoded = abi::decode_get_agent_wallet(&ret).expect("must decode");
assert_eq!(decoded, [0u8; 20]);
}
#[test]
fn decode_is_feedback_revoked_round_trips() {
let mut ret_true = vec![0u8; 32];
ret_true[31] = 1;
assert!(abi::decode_is_feedback_revoked(&ret_true).unwrap());
let ret_false = vec![0u8; 32];
assert!(!abi::decode_is_feedback_revoked(&ret_false).unwrap());
}
#[test]
fn decode_get_feedback_responses_round_trips_precompile_shape() {
let response = "ipfs://reply-cid";
let mut ret = Vec::new();
let mut offset = [0u8; 32];
offset[31] = 32;
ret.extend_from_slice(&offset);
let mut len_word = [0u8; 32];
len_word[31] = response.len() as u8;
ret.extend_from_slice(&len_word);
ret.extend_from_slice(response.as_bytes());
let pad = (32 - (response.len() % 32)) % 32;
ret.extend(std::iter::repeat_n(0u8, pad));
let decoded = abi::decode_get_feedback_responses(&ret).expect("must decode");
assert_eq!(decoded, response);
}
#[test]
fn decode_get_feedback_responses_unset_returns_empty_string() {
let mut ret = vec![0u8; 64];
ret[31] = 32;
let decoded = abi::decode_get_feedback_responses(&ret).expect("must decode");
assert!(decoded.is_empty());
}
}