Skip to main content

csv_adapter_sui/
rpc.rs

1//! Sui RPC trait and mock implementation
2
3#[cfg(debug_assertions)]
4use std::collections::HashMap;
5#[cfg(debug_assertions)]
6use std::sync::Mutex;
7
8/// Trait for Sui RPC operations
9pub trait SuiRpc: Send + Sync {
10    /// Get object by ID
11    fn get_object(
12        &self,
13        object_id: [u8; 32],
14    ) -> Result<Option<SuiObject>, Box<dyn std::error::Error + Send + Sync>>;
15
16    /// Get transaction block by digest
17    fn get_transaction_block(
18        &self,
19        digest: [u8; 32],
20    ) -> Result<Option<SuiTransactionBlock>, Box<dyn std::error::Error + Send + Sync>>;
21
22    /// Get transaction events by digest
23    fn get_transaction_events(
24        &self,
25        digest: [u8; 32],
26    ) -> Result<Vec<SuiEvent>, Box<dyn std::error::Error + Send + Sync>>;
27
28    /// Get checkpoint by sequence number
29    fn get_checkpoint(
30        &self,
31        sequence_number: u64,
32    ) -> Result<Option<SuiCheckpoint>, Box<dyn std::error::Error + Send + Sync>>;
33
34    /// Get latest checkpoint sequence number
35    fn get_latest_checkpoint_sequence_number(
36        &self,
37    ) -> Result<u64, Box<dyn std::error::Error + Send + Sync>>;
38
39    /// Get the sender's address
40    fn sender_address(&self) -> Result<[u8; 32], Box<dyn std::error::Error + Send + Sync>>;
41
42    /// Get gas objects owned by the sender
43    fn get_gas_objects(
44        &self,
45        owner: [u8; 32],
46    ) -> Result<Vec<SuiObject>, Box<dyn std::error::Error + Send + Sync>>;
47
48    /// Execute a signed MoveCall transaction and return the transaction digest
49    ///
50    /// # Arguments
51    /// * `tx_bytes` - BCS-serialized TransactionData
52    /// * `signature` - Ed25519 signature (64 bytes)
53    /// * `public_key` - Signer's public key (32 bytes)
54    fn execute_signed_transaction(
55        &self,
56        tx_bytes: Vec<u8>,
57        signature: Vec<u8>,
58        public_key: Vec<u8>,
59    ) -> Result<[u8; 32], Box<dyn std::error::Error + Send + Sync>>;
60
61    /// Wait for transaction confirmation
62    fn wait_for_transaction(
63        &self,
64        digest: [u8; 32],
65        timeout_ms: u64,
66    ) -> Result<Option<SuiTransactionBlock>, Box<dyn std::error::Error + Send + Sync>>;
67
68    /// Get ledger info
69    fn get_ledger_info(&self) -> Result<SuiLedgerInfo, Box<dyn std::error::Error + Send + Sync>>;
70
71    /// Downcast to Any for feature-gated real implementations
72    fn as_any(&self) -> &dyn std::any::Any {
73        unimplemented!("as_any() must be implemented by concrete types")
74    }
75}
76
77/// Sui object representation
78#[derive(Clone, Debug)]
79pub struct SuiObject {
80    pub object_id: [u8; 32],
81    pub version: u64,
82    pub owner: Vec<u8>,
83    pub object_type: String,
84    pub has_public_transfer: bool,
85}
86
87/// Sui object change type
88#[derive(Clone, Debug)]
89pub struct SuiObjectChange {
90    pub object_id: [u8; 32],
91    pub change_type: String,
92}
93
94/// Sui execution status
95#[derive(Clone, Debug, PartialEq)]
96pub enum SuiExecutionStatus {
97    Success,
98    Failure { error: String },
99}
100
101/// Sui transaction effects
102#[derive(Clone, Debug)]
103pub struct SuiTransactionEffects {
104    pub status: SuiExecutionStatus,
105    pub gas_used: u64,
106    pub modified_objects: Vec<SuiObjectChange>,
107}
108
109/// Sui transaction block
110#[derive(Clone, Debug)]
111pub struct SuiTransactionBlock {
112    pub digest: [u8; 32],
113    pub checkpoint: Option<u64>,
114    pub effects: SuiTransactionEffects,
115}
116
117/// Sui event
118#[derive(Clone, Debug)]
119pub struct SuiEvent {
120    pub id: String,
121    pub transaction_digest: [u8; 32],
122    pub event_sequence_number: u64,
123    pub type_field: String,
124    pub data: Vec<u8>,
125}
126
127/// Sui checkpoint
128#[derive(Clone, Debug)]
129pub struct SuiCheckpoint {
130    pub sequence_number: u64,
131    pub digest: [u8; 32],
132    pub epoch: u64,
133    pub network_total_transactions: u64,
134    pub certified: bool,
135}
136
137/// Sui ledger info
138#[derive(Clone, Debug)]
139pub struct SuiLedgerInfo {
140    pub latest_version: u64,
141    pub latest_epoch: u64,
142}
143
144/// Mock Sui RPC for testing
145///
146/// This implementation is only compiled in debug builds to prevent
147/// accidental use in production environments.
148#[cfg(debug_assertions)]
149pub struct MockSuiRpc {
150    objects: Mutex<HashMap<[u8; 32], SuiObject>>,
151    transactions: Mutex<HashMap<[u8; 32], SuiTransactionBlock>>,
152    checkpoints: Mutex<HashMap<u64, SuiCheckpoint>>,
153    latest_checkpoint: u64,
154    mock_address: [u8; 32],
155    tx_counter: std::sync::atomic::AtomicU64,
156}
157
158#[cfg(debug_assertions)]
159impl MockSuiRpc {
160    pub fn new(latest_checkpoint: u64) -> Self {
161        Self {
162            objects: Mutex::new(HashMap::new()),
163            transactions: Mutex::new(HashMap::new()),
164            checkpoints: Mutex::new(HashMap::new()),
165            latest_checkpoint,
166            mock_address: [0x42; 32],
167            tx_counter: std::sync::atomic::AtomicU64::new(0),
168        }
169    }
170
171    pub fn new_with_address(latest_checkpoint: u64, address: [u8; 32]) -> Self {
172        Self {
173            objects: Mutex::new(HashMap::new()),
174            transactions: Mutex::new(HashMap::new()),
175            checkpoints: Mutex::new(HashMap::new()),
176            latest_checkpoint,
177            mock_address: address,
178            tx_counter: std::sync::atomic::AtomicU64::new(0),
179        }
180    }
181
182    pub fn add_object(&self, object: SuiObject) {
183        self.objects
184            .lock()
185            .unwrap()
186            .insert(object.object_id, object);
187    }
188
189    pub fn add_transaction(&self, tx: SuiTransactionBlock) {
190        self.transactions.lock().unwrap().insert(tx.digest, tx);
191    }
192
193    pub fn add_checkpoint(&self, checkpoint: SuiCheckpoint) {
194        self.checkpoints
195            .lock()
196            .unwrap()
197            .insert(checkpoint.sequence_number, checkpoint);
198    }
199}
200
201#[cfg(debug_assertions)]
202impl SuiRpc for MockSuiRpc {
203    fn get_object(
204        &self,
205        object_id: [u8; 32],
206    ) -> Result<Option<SuiObject>, Box<dyn std::error::Error + Send + Sync>> {
207        Ok(self.objects.lock().unwrap().get(&object_id).cloned())
208    }
209
210    fn get_transaction_block(
211        &self,
212        digest: [u8; 32],
213    ) -> Result<Option<SuiTransactionBlock>, Box<dyn std::error::Error + Send + Sync>> {
214        Ok(self.transactions.lock().unwrap().get(&digest).cloned())
215    }
216
217    fn get_transaction_events(
218        &self,
219        _digest: [u8; 32],
220    ) -> Result<Vec<SuiEvent>, Box<dyn std::error::Error + Send + Sync>> {
221        Ok(Vec::new())
222    }
223
224    fn get_checkpoint(
225        &self,
226        sequence_number: u64,
227    ) -> Result<Option<SuiCheckpoint>, Box<dyn std::error::Error + Send + Sync>> {
228        Ok(self
229            .checkpoints
230            .lock()
231            .unwrap()
232            .get(&sequence_number)
233            .cloned())
234    }
235
236    fn get_latest_checkpoint_sequence_number(
237        &self,
238    ) -> Result<u64, Box<dyn std::error::Error + Send + Sync>> {
239        Ok(self.latest_checkpoint)
240    }
241
242    fn sender_address(&self) -> Result<[u8; 32], Box<dyn std::error::Error + Send + Sync>> {
243        Ok(self.mock_address)
244    }
245
246    fn get_gas_objects(
247        &self,
248        _owner: [u8; 32],
249    ) -> Result<Vec<SuiObject>, Box<dyn std::error::Error + Send + Sync>> {
250        // Return mock gas objects
251        Ok(vec![SuiObject {
252            object_id: [0x01; 32],
253            version: 1,
254            owner: self.mock_address.to_vec(),
255            object_type: "0x2::coin::Coin<0x2::sui::SUI>".to_string(),
256            has_public_transfer: true,
257        }])
258    }
259
260    fn execute_signed_transaction(
261        &self,
262        _tx_bytes: Vec<u8>,
263        _signature: Vec<u8>,
264        _public_key: Vec<u8>,
265    ) -> Result<[u8; 32], Box<dyn std::error::Error + Send + Sync>> {
266        // Mock: return a deterministic digest with incrementing counter
267        let counter = self
268            .tx_counter
269            .fetch_add(1, std::sync::atomic::Ordering::SeqCst);
270        let mut digest = [0u8; 32];
271        digest[..4].copy_from_slice(b"mock");
272        digest[4..12].copy_from_slice(&counter.to_le_bytes());
273        Ok(digest)
274    }
275
276    fn wait_for_transaction(
277        &self,
278        _digest: [u8; 32],
279        _timeout_ms: u64,
280    ) -> Result<Option<SuiTransactionBlock>, Box<dyn std::error::Error + Send + Sync>> {
281        Ok(None)
282    }
283
284    fn get_ledger_info(&self) -> Result<SuiLedgerInfo, Box<dyn std::error::Error + Send + Sync>> {
285        Ok(SuiLedgerInfo {
286            latest_version: self.latest_checkpoint,
287            latest_epoch: 1,
288        })
289    }
290
291    fn as_any(&self) -> &dyn std::any::Any {
292        self
293    }
294}
295
296#[cfg(test)]
297mod tests {
298    use super::*;
299
300    #[test]
301    fn test_mock_object() {
302        let rpc = MockSuiRpc::new(1000);
303        let obj = SuiObject {
304            object_id: [1u8; 32],
305            version: 1,
306            owner: vec![2, 3],
307            object_type: "CSV::Seal".to_string(),
308            has_public_transfer: false,
309        };
310        rpc.add_object(obj.clone());
311
312        let fetched = rpc.get_object([1u8; 32]).unwrap();
313        assert_eq!(fetched.unwrap().version, 1);
314    }
315
316    #[test]
317    fn test_mock_checkpoint() {
318        let rpc = MockSuiRpc::new(1000);
319        let cp = SuiCheckpoint {
320            sequence_number: 500,
321            digest: [1u8; 32],
322            epoch: 1,
323            network_total_transactions: 50000,
324            certified: true,
325        };
326        rpc.add_checkpoint(cp.clone());
327
328        let fetched = rpc.get_checkpoint(500).unwrap();
329        assert!(fetched.unwrap().certified);
330    }
331}