1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
/*
    Copyright © 2023, ParallelChain Lab 
    Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
*/

//! The data structures relevant to Transactions.

use std::ops::Deref;

use ed25519_dalek::{PublicKey, Signature, Signer, Verifier};
use crate::{crypto, Serializable, Deserializable, CryptographicallyIncorrectTransactionError, AsKeyPair, PublicAddress, ExitStatus};

/// Transactions are digitally signed instructions that tell 
/// the Mainnet state machine to execute a sequence of ‘commands’. 
#[derive(Debug, Clone, PartialEq, Eq, borsh::BorshSerialize, borsh::BorshDeserialize)]
pub struct Transaction {
    /// The public address of the external account which signed this transaction
    pub signer: crypto::PublicAddress,
    /// The number of transactions signed by the signer that have been included on 
    /// the blockchain before this transaction. This ensures that all of the signer’s transactions are
    /// included in the blockchain in an expected order, and prevents the same transaction from
    /// being included in multiple blocks.
    pub nonce: u64,
    /// A list of execution commands that triggers a sequence of state transitions
    pub commands: Vec<Command>,
    /// The maximum number of gas units (ref.) that should be used in executing this transaction
    pub gas_limit: u64,
    /// The maximum number of grays that the signer is willing to burn for a gas unit used in this transaction
    pub max_base_fee_per_gas: u64,
    /// the number of grays that the signer is willing to pay the block proposer for including this transaction in a block
    pub priority_fee_per_gas: u64,
    /// the signature formed by signing over content of this transaction by using the signer’s private key
    pub signature: crypto::Signature,
    /// The cryptographic hash of signature
    pub hash: crypto::Sha256Hash,
}

impl Serializable for Transaction {}
impl Deserializable for Transaction {}

impl Transaction {
    /// `to_signed` hashes and signs transactions and creates SignedTx which is a data structure suitable for submitting transactions 
    ///  to the ParallelChain ecosystem.
    /// ### Use of this method
    /// 1. If signature is all zeros, it computes a new one with the existing hash in transaction
    /// 2. If hash is all zeros, it computes a new one over the signature (after step 1).
    pub fn to_signed(mut self, keypair: &[u8]) -> Result<SignedTx, CryptographicallyIncorrectTransactionError> {

        let keypair = keypair.as_keypair()
            .map_err(|_|{CryptographicallyIncorrectTransactionError::InvalidKeypair})?;
        
        let serialized_transaction =  Transaction::serialize(&self);
        
        if self.signature == [0u8; 64] {
            self.signature = keypair.sign(serialized_transaction.as_slice()).to_bytes();
        }

        if self.hash == [0u8; 32] {
            self.hash = crypto::sha256(&self.signature);
        }

        Ok(SignedTx(self))
    }

    /// validated return transaction itself after validation of hash and signature
    /// ### Example
    /// ```no_run
    /// let tx = Transaction::deserialize(&value).unwrap();
    /// /// validated the transaction first before converting it to signed transaction
    /// let signed_tx = tx.validated().unwrap().to_signed(keypair);
    /// /// or converting it to "signed" transaction first. It is now optional to validate it.
    /// let signed_tx = tx.to_signed(keypair).validated().unwrap();
    /// ```
    pub fn validated(self) -> Result<Self, CryptographicallyIncorrectTransactionError> {
        self.is_cryptographically_correct()?;
        Ok(self)
    }

    /// Check whether the Transaction is hashed and signed correctly.
    pub fn is_cryptographically_correct(&self) -> Result<(), CryptographicallyIncorrectTransactionError> {
        // Verify the signature using the from_address (public key).
        let signed_msg = {
            let intermediate_txn = Transaction {
                hash: [0; 32],
                signature: [0; 64],
                ..self.to_owned()
            };

            Transaction::serialize(&intermediate_txn)
        };
        let public_key = PublicKey::from_bytes(&self.signer)
            .map_err(|_| CryptographicallyIncorrectTransactionError::InvalidFromAddress)?;
        let signature = Signature::from_bytes(&self.signature)
            .map_err(|_| CryptographicallyIncorrectTransactionError::InvalidSignature)?;
        public_key.verify(&signed_msg, &signature).map_err(|_| CryptographicallyIncorrectTransactionError::WrongSignature)?;

        // Verify the hash over the signature.
        if self.hash != crypto::sha256(signature.to_bytes().as_slice()) {
            return Err(CryptographicallyIncorrectTransactionError::WrongHash)
        }
        
        Ok(())
    }
}

/// Command is the Transaction Kind that define how state mahcine transits.
#[derive(Debug, Clone, PartialEq, Eq, borsh::BorshSerialize, borsh::BorshDeserialize)]
pub enum Command {
    /// Transfer Balance from transaction signer to recipient.
    Transfer {
        /// Recipient of the transfer
        recipient: PublicAddress,
        /// The amount to transfer
        amount: u64
    },
    /// Deploy smart contract to the state of the blockchain.
    Deploy{
        /// Smart contract in format of WASM bytecode
        contract: Vec<u8>,
        /// Version of Contract Binary Interface
        cbi_version: u32
    },
    /// Trigger method call of a deployed smart contract.
    Call{
        /// The address of the target contract
        target: PublicAddress,
        /// The method to be invoked
        method: String,
        /// The arguments supplied to the invoked method. It is a list of serialized method arguments (see [Serializable])
        arguments: Option<Vec<Vec<u8>>>,
        /// The amount sent to the target contract. The invoked contract can check the received amount 
        /// by host function `amount()` according to the CBI.
        amount: Option<u64>
    },
    /// Instantiation of a Pool in state
    CreatePool {
        /// Commission rate (in unit of percentage) is the portion that 
        /// the owners of its delegated stakes should pay from the reward in an epoch transaction.
        commission_rate: u8
    },
    /// Update settings of an existing Pool.
    SetPoolSettings {
        /// Commission rate (in unit of percentage) is the portion that 
        /// the owners of its delegated stakes should pay from the reward in an epoch transaction.
        commission_rate: u8,
    },
    /// Delete an existing Pool in state.
    DeletePool,
    /// Instantiation of a Pool in state
    CreateDeposit {
        /// The address of operator of the target pool
        operator: PublicAddress,
        /// The deposit amount
        balance: u64,
        /// Flag to indicate whether the received reward in epoch transaction should be automatically
        /// staked to the pool
        auto_stake_rewards: bool,
    },
    /// Update settings of an existing Deposit.
    SetDepositSettings {
        /// The address of operator of the target pool
        operator: PublicAddress,
        /// Flag to indicate whether the received reward in epoch transaction should be automatically
        /// staked to the pool
        auto_stake_rewards: bool,
    },
    /// Increase balance of an existing Deposit.
    TopUpDeposit {
        /// The address of operator of the target pool
        operator: PublicAddress,
        /// The amount added to Deposit's Balance
        amount: u64,
    },
    /// Withdraw balance from an existing Deposit.
    WithdrawDeposit {
        /// The address of operator of the target pool
        operator: PublicAddress,
        /// The amount of deposits that the stake owner wants to withdraw. The prefix 'max'
        /// is denoted here because the actual withdrawal amount can be less than 
        /// the wanted amount.
        max_amount: u64,
    },
    /// Increase stakes to an existing Pool
    StakeDeposit {
        /// The address of operator of the target pool
        operator: PublicAddress,
        /// The amount of stakes that the stake owner wants to stake to the target pool. 
        /// The prefix 'max' is denoted here because the actual amount to be staked
        /// can be less than the wanted amount.
        max_amount: u64,
    },
    /// Remove stakes from an existing Pool.
    UnstakeDeposit {
        /// The address of operator of the target pool
        operator: PublicAddress,
        /// The amount of stakes that the stake owner wants to remove from the target pool. 
        /// The prefix 'max' is denoted here because the actual amount to be removed
        /// can be less than the wanted amount.
        max_amount: u64,
    },
    /// Administration Command: proceed to next epoch.
    NextEpoch,
}

impl Serializable for Command {}
impl Deserializable for Command {}

/// Log are messages produced by smart contract executions that are persisted on the blockchain
/// in a cryptographically-provable way. Log produced by transactions that call smart contracts
/// are stored in the `logs` field of a Block in the order in which they are emitted.
#[derive(Debug, Clone, PartialEq, Eq, borsh::BorshSerialize, borsh::BorshDeserialize)]
pub struct Log { 
    /// Key of this event. It is created from contract execution.
    pub topic: Vec<u8>,
    /// Value of this event. It is created from contract execution.
    pub value: Vec<u8>,
}

impl Serializable for Log {}
impl Deserializable for Log {}

/// Receipt defines the result of transaction execution.
pub type Receipt = Vec<CommandReceipt>;

/// A CommandReceipt summarizes the result of execution of a [Command].
#[derive(Debug, Clone, PartialEq, Eq, borsh::BorshSerialize, borsh::BorshDeserialize)]
pub struct CommandReceipt {
    /// Exit status tells whether the corresponding command in the sequence
    /// succeeded in doing its operation, and, if it failed, whether the 
    /// failure is because of gas exhaustion or some other reason.
    pub exit_status: ExitStatus,
    /// How much gas was used in the execution of the transaction. 
    /// This will at most be the transaction’s gas limit.
    pub gas_used: u64,
    /// The return value of the corresponding command.
    pub return_values: Vec<u8>,
    /// The logs emitted during the corresponding call command.
    pub logs: Vec<Log>,
}

impl Serializable for CommandReceipt {}
impl Deserializable for CommandReceipt {}

/// SignedTx is a data structure utlized in generating 
/// signed [Transaction] for submission to ParallelChain.
#[derive(Debug, Clone, PartialEq, Eq, borsh::BorshSerialize, borsh::BorshDeserialize)]
pub struct SignedTx(Transaction);

impl Serializable for SignedTx {}
impl Deserializable for SignedTx {}

impl Deref for SignedTx {
    type Target = Transaction;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl From<SignedTx> for Transaction {
    fn from(signed_tx: SignedTx) -> Self {
        signed_tx.0
    }
}