simperby_settlement/
execution.rs

1use super::*;
2use simperby_core::*;
3
4#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
5pub struct Execution {
6    /// The target settlement chain which this message will be delivered to.
7    pub target_chain: String,
8    /// An increasing sequence for the target contract to prevent replay attack.
9    pub contract_sequence: u128,
10    /// The actual content to deliver.
11    pub message: ExecutionMessage,
12}
13
14#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
15pub enum ExecutionMessage {
16    /// Does nothing but make the treasury contract verify the commitment anyway.
17    Dummy { msg: String },
18    /// Transfers a fungible token from the treasury contract.
19    TransferFungibleToken(TransferFungibleToken),
20    /// Transfers an NFT from the treasury contract.
21    TransferNonFungibleToken(TransferNonFungibleToken),
22}
23
24#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
25pub struct TransferFungibleToken {
26    pub token_address: HexSerializedVec,
27    pub amount: Decimal,
28    pub receiver_address: HexSerializedVec,
29}
30
31#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
32pub struct TransferNonFungibleToken {
33    pub collection_address: HexSerializedVec,
34    pub token_index: HexSerializedVec,
35    pub receiver_address: HexSerializedVec,
36}
37
38/// Creates an execution transaction that will be delivered to the target chain once finalized.
39pub fn create_execution_transaction(
40    execution: &Execution,
41    author: MemberName,
42    timestamp: Timestamp,
43) -> Result<Transaction, String> {
44    let head = match &execution.message {
45        ExecutionMessage::Dummy { .. } => format!("ex-dummy: {}", execution.target_chain),
46        ExecutionMessage::TransferFungibleToken(_) => {
47            format!("ex-transfer-ft: {}", execution.target_chain)
48        }
49        ExecutionMessage::TransferNonFungibleToken(_) => {
50            format!("ex-transfer-nft: {}", execution.target_chain)
51        }
52    };
53    let body = format!(
54        "{}\n---\n{}",
55        serde_spb::to_string(&execution).unwrap(),
56        Hash256::hash(serde_spb::to_vec(&execution).unwrap())
57    );
58    Ok(Transaction {
59        author,
60        timestamp,
61        head,
62        body,
63        diff: Diff::None,
64    })
65}
66
67/// Reads an execution transaction and tries to extract an execution message.
68pub fn convert_transaction_to_execution(transaction: &Transaction) -> Result<Execution, String> {
69    let segments = transaction.body.split("\n---\n").collect::<Vec<_>>();
70    if segments.len() != 2 {
71        return Err(format!(
72            "Invalid body: expected 2 segments, got {}",
73            segments.len()
74        ));
75    }
76    let execution: Execution = serde_spb::from_str(segments[0]).map_err(|e| e.to_string())?;
77    let hash = Hash256::hash(serde_spb::to_vec(&execution).unwrap());
78    if format!("{hash}") != segments[1] {
79        return Err(format!(
80            "Invalid body: expected hash {hash}, got {}",
81            segments[1]
82        ));
83    }
84
85    if !transaction.head.starts_with("ex-") {
86        return Err("Invalid head".to_string());
87    }
88    let execution_message =
89        transaction.head.split(": ").next().ok_or("Invalid head")?[3..].to_owned();
90    let target_chain = transaction.head.split(": ").nth(1).ok_or("Invalid head")?;
91    if execution.target_chain != target_chain {
92        return Err("Invalid target chain".to_string());
93    }
94    match execution_message.as_str() {
95        "dummy" => {
96            if !matches!(execution.message, ExecutionMessage::Dummy { .. }) {
97                return Err("Invalid message".to_string());
98            }
99        }
100        "transfer-ft" => {
101            if !matches!(
102                execution.message,
103                ExecutionMessage::TransferFungibleToken { .. }
104            ) {
105                return Err("Invalid message".to_string());
106            }
107        }
108        "transfer-nft" => {
109            if !matches!(
110                execution.message,
111                ExecutionMessage::TransferNonFungibleToken { .. }
112            ) {
113                return Err("Invalid message".to_string());
114            }
115        }
116        _ => return Err("Invalid message".to_string()),
117    }
118    Ok(execution)
119}