use k256::ecdsa::SigningKey;
use super::*;
pub(crate) fn encode_attest(subject_token_id: u64, rating: u8, work_ref: &[u8; 32]) -> Vec<u8> {
let mut out = Vec::with_capacity(4 + 3 * 32);
out.extend_from_slice(&selector("attest(uint256,uint8,bytes32)"));
out.extend_from_slice(&u256_be(subject_token_id as u128)); out.extend_from_slice(&u256_be(rating as u128)); out.extend_from_slice(work_ref); out
}
pub async fn attest_sponsored(
attester_signer: &SigningKey,
fee_payer: &SigningKey,
subject_token_id: u64,
rating: u8,
work_ref: [u8; 32],
fee_token: &str,
) -> Result<String, String> {
sponsored_diamond_call(
attester_signer,
fee_payer,
encode_attest(subject_token_id, rating, &work_ref),
fee_token,
2_000_000,
)
.await
}
pub async fn reputation_of(token_id: u64) -> Result<(u64, u64), String> {
let result = read_view(
selector("reputationOf(uint256)"),
&[u256_be(token_id as u128)],
)
.await?;
let bytes = hex_to_bytes(&result)?;
if bytes.len() < 64 {
return Ok((0, 0));
}
let count = u64_low(&bytes[0..32]);
let sum = u64_low(&bytes[32..64]);
Ok((count, sum))
}
pub async fn attestations_of(
token_id: u64,
start: u64,
limit: u64,
) -> Result<Vec<(String, u8, String)>, String> {
let result = read_view(
selector("attestationsOf(uint256,uint256,uint256)"),
&[
u256_be(token_id as u128),
u256_be(start as u128),
u256_be(limit as u128),
],
)
.await?;
let bytes = hex_to_bytes(&result)?;
Ok(decode_attestations(&bytes))
}
pub(crate) fn decode_attestations(bytes: &[u8]) -> Vec<(String, u8, String)> {
let read_usize = |off: usize| -> Option<usize> {
let end = off.checked_add(32)?;
let w = bytes.get(off..end)?;
Some(u64::from_be_bytes(w[24..32].try_into().ok()?) as usize)
};
let array_body = |head_word: usize| -> Option<(usize, usize)> {
let off = read_usize(head_word * 32)?;
let len = read_usize(off)?;
let body = off.checked_add(32)?; Some((len, body))
};
let (Some((a_len, a_body)), Some((r_len, r_body)), Some((w_len, w_body))) =
(array_body(0), array_body(1), array_body(2))
else {
return Vec::new();
};
let n = a_len.min(r_len).min(w_len);
let word_at = |base: usize, i: usize| -> Option<&[u8]> {
let start = i.checked_mul(32).and_then(|o| base.checked_add(o))?;
let end = start.checked_add(32)?;
bytes.get(start..end)
};
let mut out = Vec::new();
for i in 0..n {
let (Some(aw), Some(rw), Some(ww)) =
(word_at(a_body, i), word_at(r_body, i), word_at(w_body, i))
else {
break;
};
let attester = format!("0x{}", bytes_to_hex(&aw[12..32]));
let rating = rw[31];
let work_ref = format!("0x{}", bytes_to_hex(ww));
out.push((attester, rating, work_ref));
}
out
}
pub async fn has_attested(
attester_hex: &str,
subject: u64,
work_ref: [u8; 32],
) -> Result<bool, String> {
let attester = parse_eth_address(attester_hex)?;
let result = read_view(
selector("hasAttested(address,uint256,bytes32)"),
&[addr_word(&attester), u256_be(subject as u128), work_ref],
)
.await?;
let bytes = hex_to_bytes(&result)?;
Ok(bytes.last().is_some_and(|&b| b != 0))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn attest_calldata_layout() {
let mut work_ref = [0u8; 32];
work_ref[0] = 0xAB;
work_ref[24..32].copy_from_slice(&7u64.to_be_bytes());
let cd = encode_attest(42, 5, &work_ref);
assert_eq!(cd.len(), 4 + 3 * 32);
assert_eq!(&cd[..4], &selector("attest(uint256,uint8,bytes32)"));
assert_eq!(&cd[4..36], &u256_be(42)[..]);
assert_eq!(&cd[28..36], &42u64.to_be_bytes());
assert!(cd[36..67].iter().all(|&b| b == 0), "rating word must be zero-padded");
assert_eq!(cd[67], 5, "rating must occupy the low byte of word 1");
assert_eq!(&cd[68..100], &work_ref[..]);
assert_eq!(cd[68], 0xAB);
}
#[test]
fn decode_attestations_zips_three_parallel_arrays() {
let word = |hex: &str| -> String {
assert!(hex.len() <= 64);
format!("{:0>64}", hex)
};
let mut hex = String::from("0x");
hex.push_str(&word("80")); hex.push_str(&word("c0")); hex.push_str(&word("100")); hex.push_str(&word("5")); hex.push_str(&word("1"));
hex.push_str(&word("1111111111111111111111111111111111111111"));
hex.push_str(&word("1"));
hex.push_str(&word("3"));
hex.push_str(&word("1"));
hex.push_str("cd000000000000000000000000000000000000000000000000000000000000" );
hex.push_str("09");
let bytes = hex_to_bytes(&hex).unwrap();
let rows = decode_attestations(&bytes);
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].0, "0x1111111111111111111111111111111111111111");
assert_eq!(rows[0].1, 3);
assert!(rows[0].2.starts_with("0xcd"));
assert!(rows[0].2.ends_with("09"));
assert!(decode_attestations(&[]).is_empty());
assert!(decode_attestations(&[0u8; 32]).is_empty());
}
}