#[cfg(debug_assertions)]
use std::collections::HashMap;
#[cfg(debug_assertions)]
use std::sync::Mutex;
pub trait SuiRpc: Send + Sync {
fn get_object(
&self,
object_id: [u8; 32],
) -> Result<Option<SuiObject>, Box<dyn std::error::Error + Send + Sync>>;
fn get_transaction_block(
&self,
digest: [u8; 32],
) -> Result<Option<SuiTransactionBlock>, Box<dyn std::error::Error + Send + Sync>>;
fn get_transaction_events(
&self,
digest: [u8; 32],
) -> Result<Vec<SuiEvent>, Box<dyn std::error::Error + Send + Sync>>;
fn get_checkpoint(
&self,
sequence_number: u64,
) -> Result<Option<SuiCheckpoint>, Box<dyn std::error::Error + Send + Sync>>;
fn get_latest_checkpoint_sequence_number(
&self,
) -> Result<u64, Box<dyn std::error::Error + Send + Sync>>;
fn sender_address(&self) -> Result<[u8; 32], Box<dyn std::error::Error + Send + Sync>>;
fn get_gas_objects(
&self,
owner: [u8; 32],
) -> Result<Vec<SuiObject>, Box<dyn std::error::Error + Send + Sync>>;
fn execute_signed_transaction(
&self,
tx_bytes: Vec<u8>,
signature: Vec<u8>,
public_key: Vec<u8>,
) -> Result<[u8; 32], Box<dyn std::error::Error + Send + Sync>>;
fn wait_for_transaction(
&self,
digest: [u8; 32],
timeout_ms: u64,
) -> Result<Option<SuiTransactionBlock>, Box<dyn std::error::Error + Send + Sync>>;
fn get_ledger_info(&self) -> Result<SuiLedgerInfo, Box<dyn std::error::Error + Send + Sync>>;
fn as_any(&self) -> &dyn std::any::Any {
unimplemented!("as_any() must be implemented by concrete types")
}
}
#[derive(Clone, Debug)]
pub struct SuiObject {
pub object_id: [u8; 32],
pub version: u64,
pub owner: Vec<u8>,
pub object_type: String,
pub has_public_transfer: bool,
}
#[derive(Clone, Debug)]
pub struct SuiObjectChange {
pub object_id: [u8; 32],
pub change_type: String,
}
#[derive(Clone, Debug, PartialEq)]
pub enum SuiExecutionStatus {
Success,
Failure { error: String },
}
#[derive(Clone, Debug)]
pub struct SuiTransactionEffects {
pub status: SuiExecutionStatus,
pub gas_used: u64,
pub modified_objects: Vec<SuiObjectChange>,
}
#[derive(Clone, Debug)]
pub struct SuiTransactionBlock {
pub digest: [u8; 32],
pub checkpoint: Option<u64>,
pub effects: SuiTransactionEffects,
}
#[derive(Clone, Debug)]
pub struct SuiEvent {
pub id: String,
pub transaction_digest: [u8; 32],
pub event_sequence_number: u64,
pub type_field: String,
pub data: Vec<u8>,
}
#[derive(Clone, Debug)]
pub struct SuiCheckpoint {
pub sequence_number: u64,
pub digest: [u8; 32],
pub epoch: u64,
pub network_total_transactions: u64,
pub certified: bool,
}
#[derive(Clone, Debug)]
pub struct SuiLedgerInfo {
pub latest_version: u64,
pub latest_epoch: u64,
}
#[cfg(debug_assertions)]
pub struct MockSuiRpc {
objects: Mutex<HashMap<[u8; 32], SuiObject>>,
transactions: Mutex<HashMap<[u8; 32], SuiTransactionBlock>>,
checkpoints: Mutex<HashMap<u64, SuiCheckpoint>>,
latest_checkpoint: u64,
mock_address: [u8; 32],
tx_counter: std::sync::atomic::AtomicU64,
}
#[cfg(debug_assertions)]
impl MockSuiRpc {
pub fn new(latest_checkpoint: u64) -> Self {
Self {
objects: Mutex::new(HashMap::new()),
transactions: Mutex::new(HashMap::new()),
checkpoints: Mutex::new(HashMap::new()),
latest_checkpoint,
mock_address: [0x42; 32],
tx_counter: std::sync::atomic::AtomicU64::new(0),
}
}
pub fn new_with_address(latest_checkpoint: u64, address: [u8; 32]) -> Self {
Self {
objects: Mutex::new(HashMap::new()),
transactions: Mutex::new(HashMap::new()),
checkpoints: Mutex::new(HashMap::new()),
latest_checkpoint,
mock_address: address,
tx_counter: std::sync::atomic::AtomicU64::new(0),
}
}
pub fn add_object(&self, object: SuiObject) {
self.objects
.lock()
.unwrap()
.insert(object.object_id, object);
}
pub fn add_transaction(&self, tx: SuiTransactionBlock) {
self.transactions.lock().unwrap().insert(tx.digest, tx);
}
pub fn add_checkpoint(&self, checkpoint: SuiCheckpoint) {
self.checkpoints
.lock()
.unwrap()
.insert(checkpoint.sequence_number, checkpoint);
}
}
#[cfg(debug_assertions)]
impl SuiRpc for MockSuiRpc {
fn get_object(
&self,
object_id: [u8; 32],
) -> Result<Option<SuiObject>, Box<dyn std::error::Error + Send + Sync>> {
Ok(self.objects.lock().unwrap().get(&object_id).cloned())
}
fn get_transaction_block(
&self,
digest: [u8; 32],
) -> Result<Option<SuiTransactionBlock>, Box<dyn std::error::Error + Send + Sync>> {
Ok(self.transactions.lock().unwrap().get(&digest).cloned())
}
fn get_transaction_events(
&self,
_digest: [u8; 32],
) -> Result<Vec<SuiEvent>, Box<dyn std::error::Error + Send + Sync>> {
Ok(Vec::new())
}
fn get_checkpoint(
&self,
sequence_number: u64,
) -> Result<Option<SuiCheckpoint>, Box<dyn std::error::Error + Send + Sync>> {
Ok(self
.checkpoints
.lock()
.unwrap()
.get(&sequence_number)
.cloned())
}
fn get_latest_checkpoint_sequence_number(
&self,
) -> Result<u64, Box<dyn std::error::Error + Send + Sync>> {
Ok(self.latest_checkpoint)
}
fn sender_address(&self) -> Result<[u8; 32], Box<dyn std::error::Error + Send + Sync>> {
Ok(self.mock_address)
}
fn get_gas_objects(
&self,
_owner: [u8; 32],
) -> Result<Vec<SuiObject>, Box<dyn std::error::Error + Send + Sync>> {
Ok(vec![SuiObject {
object_id: [0x01; 32],
version: 1,
owner: self.mock_address.to_vec(),
object_type: "0x2::coin::Coin<0x2::sui::SUI>".to_string(),
has_public_transfer: true,
}])
}
fn execute_signed_transaction(
&self,
_tx_bytes: Vec<u8>,
_signature: Vec<u8>,
_public_key: Vec<u8>,
) -> Result<[u8; 32], Box<dyn std::error::Error + Send + Sync>> {
let counter = self
.tx_counter
.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
let mut digest = [0u8; 32];
digest[..4].copy_from_slice(b"mock");
digest[4..12].copy_from_slice(&counter.to_le_bytes());
Ok(digest)
}
fn wait_for_transaction(
&self,
_digest: [u8; 32],
_timeout_ms: u64,
) -> Result<Option<SuiTransactionBlock>, Box<dyn std::error::Error + Send + Sync>> {
Ok(None)
}
fn get_ledger_info(&self) -> Result<SuiLedgerInfo, Box<dyn std::error::Error + Send + Sync>> {
Ok(SuiLedgerInfo {
latest_version: self.latest_checkpoint,
latest_epoch: 1,
})
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mock_object() {
let rpc = MockSuiRpc::new(1000);
let obj = SuiObject {
object_id: [1u8; 32],
version: 1,
owner: vec![2, 3],
object_type: "CSV::Seal".to_string(),
has_public_transfer: false,
};
rpc.add_object(obj.clone());
let fetched = rpc.get_object([1u8; 32]).unwrap();
assert_eq!(fetched.unwrap().version, 1);
}
#[test]
fn test_mock_checkpoint() {
let rpc = MockSuiRpc::new(1000);
let cp = SuiCheckpoint {
sequence_number: 500,
digest: [1u8; 32],
epoch: 1,
network_total_transactions: 50000,
certified: true,
};
rpc.add_checkpoint(cp.clone());
let fetched = rpc.get_checkpoint(500).unwrap();
assert!(fetched.unwrap().certified);
}
}