Skip to main content

chio_settle/evm/
types.rs

1//! EVM settlement wire and snapshot data types.
2
3use super::*;
4
5#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
6#[serde(deny_unknown_fields)]
7pub struct PreparedEvmCall {
8    pub from_address: String,
9    pub to_address: String,
10    pub data: String,
11    #[serde(default, skip_serializing_if = "Option::is_none")]
12    pub gas_limit: Option<u64>,
13}
14
15#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
16#[serde(deny_unknown_fields)]
17pub struct PreparedErc20Approval {
18    pub owner_address: String,
19    pub token_address: String,
20    pub spender_address: String,
21    pub amount_minor_units: u128,
22    pub(super) call: PreparedEvmCall,
23}
24
25#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
26#[serde(deny_unknown_fields)]
27pub struct PreparedRootPublication {
28    pub(super) call: PreparedEvmCall,
29}
30
31#[derive(Debug, Clone, Serialize, Deserialize)]
32#[serde(deny_unknown_fields)]
33pub struct EscrowDispatchRequest {
34    pub dispatch_id: String,
35    pub issued_at: u64,
36    pub trust_profile_id: String,
37    pub contract_package_id: String,
38    pub capability_id: String,
39    pub depositor_address: String,
40    pub beneficiary_address: String,
41    pub capital_instruction: SignedCapitalExecutionInstruction,
42    pub settlement_path: Web3SettlementPath,
43    pub oracle_evidence_required_for_fx: bool,
44    pub note: Option<String>,
45}
46
47#[derive(Debug, Clone, Serialize)]
48#[serde(deny_unknown_fields)]
49pub struct PreparedEscrowCreate {
50    pub expected_escrow_id: String,
51    pub capability_commitment: String,
52    pub settlement_amount_minor_units: u128,
53    pub dispatch: Web3SettlementDispatchArtifact,
54    pub(super) call: PreparedEvmCall,
55}
56
57impl PreparedEscrowCreate {
58    #[must_use]
59    pub fn commitment(&self) -> SettlementCommitment {
60        SettlementCommitment {
61            chain_id: self.dispatch.chain_id.clone(),
62            lane_kind: match self.dispatch.settlement_path {
63                Web3SettlementPath::DualSignature => "evm_dual_signature",
64                Web3SettlementPath::MerkleProof => "evm_merkle_proof",
65            }
66            .to_string(),
67            capability_commitment: self.capability_commitment.clone(),
68            receipt_reference: self.dispatch.escrow_id.clone(),
69            operator_identity: self.dispatch.beneficiary_address.clone(),
70            settlement_amount: self.dispatch.settlement_amount.clone(),
71        }
72    }
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
76#[serde(rename_all = "snake_case")]
77pub enum EscrowExecutionAmount {
78    Full,
79    Partial(MonetaryAmount),
80}
81
82#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
83#[serde(deny_unknown_fields)]
84pub struct PreparedMerkleRelease {
85    pub escrow_id: String,
86    pub chain_id: String,
87    pub receipt_hash: String,
88    pub receipt_leaf_hash: String,
89    pub merkle_root: String,
90    pub partial: bool,
91    pub settlement_amount_minor_units: u128,
92    pub observed_amount: MonetaryAmount,
93    pub(super) call: PreparedEvmCall,
94}
95
96impl PreparedMerkleRelease {
97    #[must_use]
98    pub fn commitment(
99        &self,
100        capability_commitment: String,
101        operator_identity: String,
102    ) -> SettlementCommitment {
103        SettlementCommitment {
104            chain_id: self.chain_id.clone(),
105            lane_kind: "evm_merkle_proof".to_string(),
106            capability_commitment,
107            receipt_reference: self.receipt_hash.clone(),
108            operator_identity,
109            settlement_amount: self.observed_amount.clone(),
110        }
111    }
112}
113
114#[derive(Debug, Clone)]
115pub struct PreparedAuthorizedChannelMerkleReleaseV1 {
116    pub(super) release: PreparedMerkleRelease,
117    pub(super) authorization: crate::channel::ChannelReleaseAuthorizationBindingV1,
118    pub(super) authorization_digest: String,
119}
120
121impl PreparedAuthorizedChannelMerkleReleaseV1 {
122    #[must_use]
123    pub const fn authorization(&self) -> &crate::channel::ChannelReleaseAuthorizationBindingV1 {
124        &self.authorization
125    }
126
127    #[must_use]
128    pub fn authorization_digest(&self) -> &str {
129        &self.authorization_digest
130    }
131
132    #[must_use]
133    pub fn publication_root(&self) -> &str {
134        &self.release.merkle_root
135    }
136
137    pub fn canonical_call(&self) -> Result<Vec<u8>, SettlementError> {
138        canonical_json_bytes(&self.release.call)
139            .map_err(|error| SettlementError::Serialization(error.to_string()))
140    }
141}
142
143#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
144#[serde(deny_unknown_fields)]
145pub struct EvmSignature {
146    pub v: u8,
147    pub r: String,
148    pub s: String,
149}
150
151#[derive(Clone, PartialEq, Eq)]
152pub struct DualSignReleaseInput {
153    pub(super) operator_private_key_hex: zeroize::Zeroizing<String>,
154    pub observed_amount: MonetaryAmount,
155}
156
157impl DualSignReleaseInput {
158    #[must_use]
159    pub fn new(
160        operator_private_key_hex: impl Into<String>,
161        observed_amount: MonetaryAmount,
162    ) -> Self {
163        Self {
164            operator_private_key_hex: zeroize::Zeroizing::new(operator_private_key_hex.into()),
165            observed_amount,
166        }
167    }
168}
169
170impl std::fmt::Debug for DualSignReleaseInput {
171    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
172        formatter
173            .debug_struct("DualSignReleaseInput")
174            .field("operator_private_key_hex", &"[REDACTED]")
175            .field("observed_amount", &self.observed_amount)
176            .finish()
177    }
178}
179
180#[cfg(test)]
181mod dual_sign_release_input_tests {
182    use super::*;
183
184    #[test]
185    fn debug_redacts_operator_private_key() {
186        let input = DualSignReleaseInput::new(
187            "private-key-material",
188            MonetaryAmount {
189                units: 1,
190                currency: "USD".to_string(),
191            },
192        );
193
194        let debug = format!("{input:?}");
195        assert!(debug.contains("[REDACTED]"));
196        assert!(!debug.contains("private-key-material"));
197    }
198}
199
200#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
201#[serde(deny_unknown_fields)]
202pub struct DualSignRegistryEvidence {
203    pub chain_id: String,
204    pub identity_registry_contract: String,
205    pub operator_address: String,
206    pub block_number: u64,
207    pub block_hash: String,
208    pub observed_at: u64,
209    pub operator_key_hash: String,
210    pub settlement_key: String,
211    pub registered_at: u64,
212    pub operator_epoch: u64,
213    pub active: bool,
214}
215
216impl From<DualSignRegistryEvidence>
217    for chio_core::web3::settlement::Web3SettlementIdentityRegistryEvidence
218{
219    fn from(evidence: DualSignRegistryEvidence) -> Self {
220        Self {
221            chain_id: evidence.chain_id,
222            identity_registry_contract: evidence.identity_registry_contract,
223            operator_address: evidence.operator_address,
224            block_number: evidence.block_number,
225            block_hash: evidence.block_hash,
226            observed_at: evidence.observed_at,
227            operator_key_hash: evidence.operator_key_hash,
228            settlement_key: evidence.settlement_key,
229            registered_at: evidence.registered_at,
230            operator_epoch: evidence.operator_epoch,
231            active: evidence.active,
232        }
233    }
234}
235
236#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
237#[serde(deny_unknown_fields)]
238pub struct DualSignRegistryEvidenceBinding {
239    pub identity_registry_contract: String,
240    pub operator_address: String,
241    pub settlement_key: String,
242}
243
244impl From<DualSignRegistryEvidenceBinding>
245    for chio_core::web3::settlement::Web3SettlementIdentityRegistryEvidenceBinding
246{
247    fn from(binding: DualSignRegistryEvidenceBinding) -> Self {
248        Self {
249            identity_registry_contract: binding.identity_registry_contract,
250            operator_address: binding.operator_address,
251            settlement_key: binding.settlement_key,
252        }
253    }
254}
255
256#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
257#[serde(deny_unknown_fields)]
258pub struct PreparedDualSignRelease {
259    pub escrow_id: String,
260    pub chain_id: String,
261    pub receipt_hash: String,
262    pub digest: String,
263    pub operator_epoch: u64,
264    pub identity_registry_evidence: DualSignRegistryEvidence,
265    pub identity_registry_evidence_binding: DualSignRegistryEvidenceBinding,
266    pub settlement_amount_minor_units: u128,
267    pub observed_amount: MonetaryAmount,
268    pub signature: EvmSignature,
269    pub(super) call: PreparedEvmCall,
270}
271
272impl PreparedDualSignRelease {
273    #[must_use]
274    pub fn commitment(
275        &self,
276        capability_commitment: String,
277        operator_identity: String,
278    ) -> SettlementCommitment {
279        SettlementCommitment {
280            chain_id: self.chain_id.clone(),
281            lane_kind: "evm_dual_signature".to_string(),
282            capability_commitment,
283            receipt_reference: self.receipt_hash.clone(),
284            operator_identity,
285            settlement_amount: self.observed_amount.clone(),
286        }
287    }
288}
289
290#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
291#[serde(deny_unknown_fields)]
292pub struct PreparedEscrowRefund {
293    pub escrow_id: String,
294    pub chain_id: String,
295    pub(super) call: PreparedEvmCall,
296}
297
298#[derive(Debug, Clone, Serialize, Deserialize)]
299#[serde(deny_unknown_fields)]
300pub struct BondLockRequest {
301    pub principal_address: String,
302    pub bond: SignedCreditBond,
303}
304
305#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
306#[serde(deny_unknown_fields)]
307pub struct PreparedBondLock {
308    pub vault_id: String,
309    pub bond_id_hash: String,
310    pub facility_id_hash: String,
311    pub operator_key_hash: String,
312    pub collateral_minor_units: u128,
313    pub reserve_requirement_minor_units: u128,
314    pub(super) call: PreparedEvmCall,
315}
316
317#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
318#[serde(deny_unknown_fields)]
319pub struct PreparedBondRelease {
320    pub vault_id: String,
321    pub chain_id: String,
322    pub operator_key_hash: String,
323    pub evidence_hash: String,
324    pub merkle_root: String,
325    pub(super) call: PreparedEvmCall,
326}
327
328#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
329#[serde(deny_unknown_fields)]
330pub struct PreparedBondImpair {
331    pub vault_id: String,
332    pub chain_id: String,
333    pub operator_key_hash: String,
334    pub evidence_hash: String,
335    pub merkle_root: String,
336    pub slash_amount_minor_units: u128,
337    pub(super) call: PreparedEvmCall,
338}
339
340#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
341#[serde(deny_unknown_fields)]
342pub struct PreparedBondExpiry {
343    pub vault_id: String,
344    pub chain_id: String,
345    pub(super) call: PreparedEvmCall,
346}
347
348mod sealed {
349    use super::PreparedEvmCall;
350
351    pub trait Sealed {
352        fn call(&self) -> &PreparedEvmCall;
353    }
354}
355
356pub trait PreparedEvmSubmission: sealed::Sealed {}
357
358pub(super) fn prepared_submission_call<T: PreparedEvmSubmission + ?Sized>(
359    prepared: &T,
360) -> &PreparedEvmCall {
361    sealed::Sealed::call(prepared)
362}
363
364macro_rules! prepared_evm_call_access {
365    ($($type:ty),+ $(,)?) => {
366        $(
367            impl $type {
368                #[must_use]
369                pub const fn call(&self) -> &PreparedEvmCall {
370                    &self.call
371                }
372            }
373
374        )+
375    };
376}
377
378macro_rules! prepared_evm_submission {
379    ($($type:ty),+ $(,)?) => {
380        $(
381
382            impl sealed::Sealed for $type {
383                fn call(&self) -> &PreparedEvmCall {
384                    &self.call
385                }
386            }
387
388            impl PreparedEvmSubmission for $type {}
389        )+
390    };
391}
392
393prepared_evm_call_access!(
394    PreparedErc20Approval,
395    PreparedRootPublication,
396    PreparedEscrowCreate,
397    PreparedMerkleRelease,
398    PreparedDualSignRelease,
399    PreparedEscrowRefund,
400    PreparedBondLock,
401    PreparedBondRelease,
402    PreparedBondImpair,
403    PreparedBondExpiry,
404);
405
406prepared_evm_submission!(
407    PreparedErc20Approval,
408    PreparedEscrowCreate,
409    PreparedBondLock,
410);
411
412#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
413#[serde(deny_unknown_fields)]
414pub struct EvmLogEntry {
415    pub address: String,
416    pub topics: Vec<String>,
417    pub data: String,
418    #[serde(default, skip_serializing_if = "Option::is_none")]
419    pub log_index: Option<u64>,
420}
421
422#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
423#[serde(deny_unknown_fields)]
424pub struct EvmTransactionReceipt {
425    pub tx_hash: String,
426    pub block_number: u64,
427    pub block_hash: String,
428    pub status: bool,
429    pub from_address: String,
430    pub to_address: String,
431    pub gas_used: u64,
432    pub observed_at: u64,
433    #[serde(default, skip_serializing_if = "Vec::is_empty")]
434    pub logs: Vec<EvmLogEntry>,
435}
436
437#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
438#[serde(deny_unknown_fields)]
439pub struct EscrowSnapshot {
440    pub escrow_id: String,
441    pub depositor_address: String,
442    pub beneficiary_address: String,
443    pub deadline: u64,
444    pub deposited_minor_units: u128,
445    pub released_minor_units: u128,
446    pub refunded: bool,
447    pub remaining_minor_units: u128,
448}
449
450#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
451#[serde(deny_unknown_fields)]
452pub struct EvmBondSnapshot {
453    pub vault_id: String,
454    pub principal_address: String,
455    pub operator_key_hash: String,
456    pub expires_at: u64,
457    pub observed_at: u64,
458    pub locked_minor_units: u128,
459    pub reserve_requirement_minor_units: u128,
460    pub reserve_requirement_ratio_bps: u16,
461    pub slashed_minor_units: u128,
462    pub released: bool,
463    pub expired: bool,
464}
465
466#[derive(Debug, Deserialize)]
467pub(super) struct JsonRpcEnvelope {
468    #[serde(rename = "jsonrpc")]
469    pub(super) _jsonrpc: String,
470    #[serde(rename = "id")]
471    pub(super) _id: u64,
472    pub(super) result: Option<Value>,
473    pub(super) error: Option<JsonRpcError>,
474}
475
476#[derive(Debug, Deserialize)]
477pub(super) struct JsonRpcError {
478    pub(super) code: i64,
479    pub(super) message: String,
480    pub(super) data: Option<Value>,
481}