Skip to main content

ethexe_common/
gear.rs

1// Copyright (C) Gear Technologies Inc.
2// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
3
4//! This is supposed to be an exact copy of Gear.sol library.
5
6use crate::{Address, Digest, ToDigest, ValidatorsVec};
7use alloc::vec::Vec;
8use alloy_primitives::U256 as AlloyU256;
9use gear_core::message::{ReplyCode, ReplyDetails, StoredMessage, SuccessReplyReason};
10use gprimitives::{ActorId, CodeId, H256, MessageId, U256};
11use parity_scale_codec::{Decode, Encode};
12use scale_info::TypeInfo;
13use sha3::Digest as _;
14
15// TODO: support query from router.
16pub const COMPUTATION_THRESHOLD: u64 = 2_500_000_000;
17pub const WVARA_PER_SECOND: u128 = 10_000_000_000_000;
18
19/// Gas limit for chunk processing.
20pub const CHUNK_PROCESSING_GAS_LIMIT: u64 = 1_000_000_000_000;
21/// Gas charge threshold for panicked injected messages.
22pub const INJECTED_MESSAGE_PANIC_GAS_CHARGE_THRESHOLD: u64 = 1_000_000_000;
23
24/// Max block gas limit for the node.
25pub const MAX_BLOCK_GAS_LIMIT: u64 = 9_000_000_000_000;
26
27/// [`CANONICAL_QUARANTINE`] defines the period of blocks to wait before applying canonical events.
28pub const CANONICAL_QUARANTINE: u8 = 16;
29
30#[derive(Clone, Debug, Default, Encode, Decode, PartialEq, Eq)]
31pub struct AggregatedPublicKey {
32    pub x: U256,
33    pub y: U256,
34}
35
36#[derive(Clone, Debug, Encode, Decode, PartialEq, Eq)]
37#[repr(u8)]
38pub enum SignatureType {
39    FROST,
40    ECDSA,
41}
42
43#[derive(Clone, Debug, Default, Encode, Decode, PartialEq, Eq)]
44pub struct AddressBook {
45    pub mirror: ActorId,
46    pub mirror_proxy: ActorId,
47    pub wrapped_vara: ActorId,
48}
49
50/// Squashed chain commitment with state transitions, MB head, and the latest
51/// advanced Ethereum block hash, zero if no ethereum block has been advanced.
52#[derive(Clone, Debug, Encode, Decode, PartialEq, Eq)]
53pub struct ChainCommitment {
54    pub transitions: Vec<StateTransition>,
55    pub head: H256,
56    pub last_advanced_eth_block: H256,
57}
58
59impl ToDigest for ChainCommitment {
60    fn update_hasher(&self, hasher: &mut sha3::Keccak256) {
61        let ChainCommitment {
62            transitions,
63            head,
64            last_advanced_eth_block,
65        } = self;
66
67        hasher.update(transitions.to_digest());
68        hasher.update(head.0);
69        hasher.update(last_advanced_eth_block.0);
70    }
71}
72
73#[derive(Clone, Debug, Encode, Decode, PartialEq, Eq)]
74pub struct CodeCommitment {
75    pub id: CodeId,
76    pub valid: bool,
77}
78
79impl ToDigest for CodeCommitment {
80    fn update_hasher(&self, hasher: &mut sha3::Keccak256) {
81        // To avoid missing incorrect hashing while developing.
82        let Self { id, valid } = self;
83
84        hasher.update(id.into_bytes());
85        hasher.update([*valid as u8]);
86    }
87}
88
89#[derive(Clone, Debug, Default, Encode, Decode, PartialEq, Eq)]
90pub struct OperatorRewardsCommitment {
91    pub amount: U256,
92    pub root: H256,
93}
94
95impl ToDigest for OperatorRewardsCommitment {
96    fn update_hasher(&self, hasher: &mut sha3::Keccak256) {
97        let OperatorRewardsCommitment { amount, root } = self;
98
99        hasher.update(<[u8; 32]>::from(*amount));
100        hasher.update(root);
101    }
102}
103
104#[derive(Clone, Debug, Default, Encode, Decode, PartialEq, Eq)]
105pub struct StakerRewards {
106    pub vault: Address,
107    pub amount: U256,
108}
109
110#[derive(Clone, Debug, Default, Encode, Decode, PartialEq, Eq)]
111pub struct StakerRewardsCommitment {
112    pub distribution: Vec<StakerRewards>,
113    pub total_amount: U256,
114    pub token: Address,
115}
116
117impl ToDigest for StakerRewardsCommitment {
118    fn update_hasher(&self, hasher: &mut sha3::Keccak256) {
119        let StakerRewardsCommitment {
120            distribution,
121            total_amount,
122            token,
123        } = &self;
124
125        distribution
126            .iter()
127            .for_each(|StakerRewards { vault, amount }| {
128                hasher.update(vault);
129                hasher.update(<[u8; 32]>::from(*amount));
130            });
131
132        hasher.update(<[u8; 32]>::from(*total_amount));
133        hasher.update(token);
134    }
135}
136
137#[derive(Clone, Debug, Default, Encode, Decode, PartialEq, Eq)]
138pub struct RewardsCommitment {
139    pub operators: OperatorRewardsCommitment,
140    pub stakers: StakerRewardsCommitment,
141    /// Rewards for timestamp. Represented as u48 in router contract.
142    pub timestamp: u64,
143}
144
145impl ToDigest for RewardsCommitment {
146    fn update_hasher(&self, hasher: &mut sha3::Keccak256) {
147        let RewardsCommitment {
148            operators,
149            stakers,
150            timestamp,
151        } = self;
152
153        hasher.update(operators.to_digest());
154        hasher.update(stakers.to_digest());
155        hasher.update(crate::u64_into_uint48_be_bytes_lossy(*timestamp));
156    }
157}
158
159/// Batch of different commitments that are created for a specific ethereum block.
160#[derive(Clone, Debug, Default, Encode, Decode, PartialEq, Eq)]
161pub struct BatchCommitment {
162    // Hash of ethereum block for which this batch has been created
163    // This is used to identify whether router have to apply this batch,
164    // it can be a batch from another branch and after reorg it's not actual anymore (currently we have predecessorBlock for this)
165    pub block_hash: H256,
166
167    /// Timestamp of ethereum block for which this batch has been created
168    /// This timestamp is used to identify validator set to verify commitment (current or previous era)
169    pub timestamp: u64,
170
171    /// Digest of the previous committed batch.
172    /// This is used to verify that the batch is committed in the correct order.
173    pub previous_batch: Digest,
174
175    /// How long the batch is valid (in blocks since `block_hash`).
176    /// if 1 - then valid only in child block
177    /// if 2 - then valid in child and grandchild blocks
178    /// ... etc.
179    pub expiry: u8,
180
181    pub chain_commitment: Option<ChainCommitment>,
182    pub code_commitments: Vec<CodeCommitment>,
183    pub validators_commitment: Option<ValidatorsCommitment>,
184    pub rewards_commitment: Option<RewardsCommitment>,
185}
186
187impl ToDigest for BatchCommitment {
188    fn update_hasher(&self, hasher: &mut sha3::Keccak256) {
189        // To avoid missing incorrect hashing while developing.
190        let Self {
191            block_hash,
192            timestamp,
193            previous_batch,
194            expiry,
195            chain_commitment,
196            code_commitments,
197            validators_commitment,
198            rewards_commitment,
199        } = self;
200
201        hasher.update(block_hash);
202        hasher.update(crate::u64_into_uint48_be_bytes_lossy(*timestamp));
203        hasher.update(previous_batch);
204        hasher.update(expiry.to_be_bytes());
205        hasher.update(chain_commitment.to_digest());
206        hasher.update(code_commitments.to_digest());
207        hasher.update(rewards_commitment.to_digest());
208        hasher.update(validators_commitment.to_digest());
209    }
210}
211
212#[derive(Clone, Debug, Default, Encode, Decode, PartialEq, Eq)]
213pub struct Timelines {
214    pub era: u64,
215    pub election: u64,
216    pub validation_delay: u64,
217}
218
219#[derive(Clone, Debug, Encode, Decode, PartialEq, Eq)]
220pub struct ValidatorsCommitment {
221    /// Does the batch have aggregated public key in validators commitment.
222    pub has_aggregated_public_key: bool,
223    pub aggregated_public_key: AggregatedPublicKey,
224    pub verifiable_secret_sharing_commitment: Vec<u8>,
225    pub validators: ValidatorsVec,
226    pub era_index: u64,
227}
228
229impl ToDigest for ValidatorsCommitment {
230    fn update_hasher(&self, hasher: &mut sha3::Keccak256) {
231        let ValidatorsCommitment {
232            has_aggregated_public_key,
233            aggregated_public_key,
234            verifiable_secret_sharing_commitment: _, // TODO: add to digest
235            validators,
236            era_index,
237        } = self;
238
239        hasher.update([*has_aggregated_public_key as u8]);
240        hasher.update(<[u8; 32]>::from(aggregated_public_key.x));
241        hasher.update(<[u8; 32]>::from(aggregated_public_key.y));
242        hasher.update(
243            validators
244                .iter()
245                .flat_map(|v| {
246                    // Adjust to 32 bytes, because of `encodePacked` in Gear.validatorCommitmentHash
247                    let mut bytes = [0u8; 32];
248                    bytes[12..32].copy_from_slice(&v.0);
249                    bytes.into_iter()
250                })
251                .collect::<Vec<u8>>(),
252        );
253
254        let bytes = AlloyU256::from(*era_index).to_be_bytes::<32>();
255        hasher.update(bytes);
256    }
257}
258
259#[derive(Clone, Copy, Debug, Default, Encode, Decode, PartialEq, Eq)]
260pub enum CodeState {
261    #[default]
262    Unknown,
263    ValidationRequested,
264    Validated,
265}
266
267impl From<u8> for CodeState {
268    fn from(value: u8) -> Self {
269        match value {
270            0 => Self::Unknown,
271            1 => Self::ValidationRequested,
272            2 => Self::Validated,
273            // NOTE: newly added variants should be updated accordingly
274            _ => Self::Unknown,
275        }
276    }
277}
278
279#[derive(Clone, Debug, Default, Encode, Decode, PartialEq, Eq)]
280pub struct CommittedBlockInfo {
281    pub hash: H256,
282    /// represented as u48 in router contract.
283    pub timestamp: u64,
284}
285
286#[derive(Clone, Debug, Default, Encode, Decode, PartialEq, Eq)]
287pub struct ComputationSettings {
288    pub threshold: u64,
289    pub wvara_per_second: u128,
290}
291
292#[derive(Clone, Debug, Default, Encode, Decode, TypeInfo, PartialEq, Eq, Hash)]
293#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
294pub struct Message {
295    pub id: MessageId,
296    pub destination: ActorId,
297    pub payload: Vec<u8>,
298    pub value: u128,
299    pub reply_details: Option<ReplyDetails>,
300    pub call: bool,
301}
302
303impl ToDigest for Message {
304    fn update_hasher(&self, hasher: &mut sha3::Keccak256) {
305        // To avoid missing incorrect hashing while developing.
306        let Self {
307            id,
308            destination,
309            payload,
310            value,
311            reply_details,
312            call,
313        } = self;
314
315        let (reply_details_to, reply_details_code) =
316            reply_details.map(|d| d.into_parts()).unwrap_or((
317                MessageId::default(),
318                ReplyCode::Success(SuccessReplyReason::Auto),
319            ));
320
321        hasher.update(id);
322        hasher.update(destination.to_address_lossy());
323        hasher.update(payload);
324        hasher.update(value.to_be_bytes());
325        hasher.update(reply_details_to);
326        hasher.update(reply_details_code.to_bytes());
327        hasher.update([*call as u8]);
328    }
329}
330
331impl Message {
332    pub fn from_stored(value: StoredMessage, call: bool) -> Self {
333        let (id, _source, destination, payload, value, details) = value.into_parts();
334        Self {
335            id,
336            destination,
337            payload: payload.into_vec(),
338            value,
339            reply_details: details.and_then(|v| v.to_reply_details()),
340            call,
341        }
342    }
343}
344
345#[derive(Clone, Debug, Default, Encode, Decode, PartialEq, Eq)]
346pub struct ProtocolData {
347    // flatten mapping of codes CodeId => CodeState
348    // flatten mapping of program to codes ActorId => CodeId
349    pub programs_count: U256,
350    pub validated_codes_count: U256,
351}
352
353#[derive(Clone, Debug, Default, Encode, Decode, TypeInfo, PartialEq, Eq, Hash)]
354#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
355pub struct StateTransition {
356    pub actor_id: ActorId,
357    pub new_state_hash: H256,
358    pub exited: bool,
359    pub inheritor: ActorId,
360    /// We represent `value_to_receive` as `u128` and `bool` because each non-zero byte costs 16 gas,
361    /// and each zero byte costs 4 gas (see <https://evm.codes/about#gascosts>).
362    ///
363    /// Negative numbers will be stored like this:
364    /// ```bash
365    /// $ cast
366    /// > -1 ether
367    /// Type: int256
368    /// Hex: 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffff21f494c589c0000
369    /// ```
370    ///
371    /// This is optimization on EVM side to reduce gas costs for storing and processing values.
372    pub value_to_receive: u128,
373    pub value_to_receive_negative_sign: bool,
374    pub value_claims: Vec<ValueClaim>,
375    pub messages: Vec<Message>,
376}
377
378impl ToDigest for StateTransition {
379    fn update_hasher(&self, hasher: &mut sha3::Keccak256) {
380        // To avoid missing incorrect hashing while developing.
381        let Self {
382            actor_id,
383            new_state_hash,
384            exited,
385            inheritor,
386            value_to_receive,
387            value_to_receive_negative_sign,
388            value_claims,
389            messages,
390        } = self;
391
392        hasher.update(actor_id.to_address_lossy());
393        hasher.update(new_state_hash);
394        hasher.update([*exited as u8]);
395        hasher.update(inheritor.to_address_lossy());
396        hasher.update(value_to_receive.to_be_bytes());
397        hasher.update([*value_to_receive_negative_sign as u8]);
398        hasher.update(value_claims.to_digest());
399        hasher.update(messages.to_digest());
400    }
401}
402
403#[derive(Clone, Debug, Default, Encode, Decode, TypeInfo, PartialEq, Eq, Hash)]
404#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
405pub struct ValueClaim {
406    pub message_id: MessageId,
407    pub destination: ActorId,
408    pub value: u128,
409}
410
411impl ToDigest for ValueClaim {
412    fn update_hasher(&self, hasher: &mut sha3::Keccak256) {
413        let ValueClaim {
414            message_id,
415            destination,
416            value,
417        } = self;
418
419        hasher.update(message_id);
420        hasher.update(destination.to_address_lossy());
421        hasher.update(value.to_be_bytes());
422    }
423}
424
425#[derive(
426    Clone,
427    Copy,
428    Debug,
429    Encode,
430    Decode,
431    PartialEq,
432    Eq,
433    Default,
434    PartialOrd,
435    Ord,
436    Hash,
437    derive_more::IsVariant,
438)]
439#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
440pub enum MessageType {
441    #[default]
442    Canonical,
443    Injected,
444}
445
446#[derive(Debug)]
447pub struct GenesisBlockInfo {
448    pub hash: H256,
449    pub number: u32,
450    pub timestamp: u64,
451}
452
453#[cfg(test)]
454mod tests {
455    use super::*;
456
457    #[test]
458    fn validators_commitment_accepts_raw_vss_commitment_bytes() {
459        let commitment = ValidatorsCommitment {
460            has_aggregated_public_key: false,
461            aggregated_public_key: AggregatedPublicKey::default(),
462            verifiable_secret_sharing_commitment: vec![],
463            validators: nonempty::nonempty![crate::Address::default()].into(),
464            era_index: 0,
465        };
466
467        assert!(commitment.verifiable_secret_sharing_commitment.is_empty());
468    }
469}