use tiny_keccak::{Hasher, Keccak};
fn compute_keccak256(input: &[u8]) -> [u8; 32] {
let mut output = [0u8; 32];
let mut hasher = Keccak::v256();
hasher.update(input);
hasher.finalize(&mut output);
output
}
fn seal_used_signature() -> [u8; 32] {
compute_keccak256(b"SealUsed(bytes32,bytes32)")
}
pub struct CsvSealAbi;
impl CsvSealAbi {
pub fn seal_used_event_signature() -> [u8; 32] {
seal_used_signature()
}
pub fn encode_mark_seal_used(seal_id: [u8; 32], commitment: [u8; 32]) -> Vec<u8> {
let selector = compute_keccak256(b"markSealUsed(bytes32,bytes32)");
let mut calldata = Vec::with_capacity(4 + 32 + 32);
calldata.extend_from_slice(&selector[..4]);
calldata.extend_from_slice(&seal_id);
calldata.extend_from_slice(&commitment);
calldata
}
pub fn decode_seal_used_event(topics: &[[u8; 32]], data: &[u8]) -> Option<SealUsedEvent> {
if topics.is_empty() {
return None;
}
if topics[0] != Self::seal_used_event_signature() {
return None;
}
if data.len() < 64 {
return None;
}
let mut seal_id = [0u8; 32];
seal_id.copy_from_slice(&data[..32]);
let mut commitment = [0u8; 32];
commitment.copy_from_slice(&data[32..64]);
Some(SealUsedEvent {
seal_id,
commitment,
})
}
pub fn matches_seal_used_event(
address: &[u8; 20],
contract_address: &[u8; 20],
topics: &[[u8; 32]],
) -> bool {
if address != contract_address {
return false;
}
if topics.is_empty() {
return false;
}
topics[0] == Self::seal_used_event_signature()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SealUsedEvent {
pub seal_id: [u8; 32],
pub commitment: [u8; 32],
}
pub const CSV_SEAL_SOL: &str = r#"
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract CSVSeal {
mapping(bytes32 => bool) public usedSeals;
event SealUsed(bytes32 indexed sealId, bytes32 commitment);
function markSealUsed(bytes32 sealId, bytes32 commitment) external {
require(!usedSeals[sealId], "Seal already used");
usedSeals[sealId] = true;
emit SealUsed(sealId, commitment);
}
function isSealUsed(bytes32 sealId) external view returns (bool) {
return usedSeals[sealId];
}
}
"#;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_encode_mark_seal_used() {
let calldata = CsvSealAbi::encode_mark_seal_used([1u8; 32], [2u8; 32]);
assert_eq!(calldata.len(), 4 + 32 + 32);
}
#[test]
fn test_decode_seal_used_event() {
let seal_id = [1u8; 32];
let commitment = [2u8; 32];
let mut data = Vec::with_capacity(64);
data.extend_from_slice(&seal_id);
data.extend_from_slice(&commitment);
let topics = vec![CsvSealAbi::seal_used_event_signature()];
let event = CsvSealAbi::decode_seal_used_event(&topics, &data).unwrap();
assert_eq!(event.seal_id, seal_id);
assert_eq!(event.commitment, commitment);
}
#[test]
fn test_decode_seal_used_wrong_signature() {
let data = vec![0u8; 64];
let topics = vec![[0xFF; 32]]; assert!(CsvSealAbi::decode_seal_used_event(&topics, &data).is_none());
}
#[test]
fn test_decode_seal_used_short_data() {
let topics = vec![CsvSealAbi::seal_used_event_signature()];
assert!(CsvSealAbi::decode_seal_used_event(&topics, &[0u8; 32]).is_none());
}
#[test]
fn test_matches_seal_used_event() {
let address = [1u8; 20];
let contract = [1u8; 20];
let topics = vec![CsvSealAbi::seal_used_event_signature()];
assert!(CsvSealAbi::matches_seal_used_event(
&address, &contract, &topics
));
}
#[test]
fn test_matches_seal_used_wrong_address() {
let address = [1u8; 20];
let contract = [2u8; 20];
let topics = vec![CsvSealAbi::seal_used_event_signature()];
assert!(!CsvSealAbi::matches_seal_used_event(
&address, &contract, &topics
));
}
#[test]
fn test_encode_mark_seal_used_selector() {
let calldata = CsvSealAbi::encode_mark_seal_used([1u8; 32], [2u8; 32]);
assert_eq!(calldata.len(), 4 + 32 + 32);
assert_ne!(&calldata[..4], &[0xDE, 0xAD, 0xBE, 0xEF]); }
#[test]
fn test_seal_used_signature_is_valid_keccak() {
let sig = CsvSealAbi::seal_used_event_signature();
assert!(sig.iter().any(|&b| b != 0));
assert_ne!(sig, [0u8; 32]);
}
}