use crate::injected::SignedInjectedTransaction;
use alloc::vec::Vec;
use derive_more::{Deref, DerefMut, IntoIterator};
use gprimitives::H256;
use parity_scale_codec::{Decode, Encode};
use scale_info::TypeInfo;
#[cfg(feature = "std")]
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, Eq, TypeInfo, derive_more::IsVariant)]
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
#[repr(u32)]
pub enum Operation {
AdvanceTillEthereumBlock { block_hash: H256 } = 0,
ProgressTasks = 1,
ProcessQueues { gas_allowance: u64 } = 2,
Injected(SignedInjectedTransaction) = 3,
ProcessQueuesV2 { gas_allowance: u64 } = 4,
ProcessQueuesV3 { gas_allowance: u64 } = 5,
}
impl Operation {
pub fn tag(&self) -> u32 {
match self {
Self::AdvanceTillEthereumBlock { .. } => 0,
Self::ProgressTasks => 1,
Self::ProcessQueues { .. } => 2,
Self::Injected(_) => 3,
Self::ProcessQueuesV2 { .. } => 4,
Self::ProcessQueuesV3 { .. } => 5,
}
}
}
impl Decode for Operation {
fn decode<I: parity_scale_codec::Input>(
input: &mut I,
) -> core::result::Result<Self, parity_scale_codec::Error> {
let tag = u32::decode(input)?;
match tag {
0 => Ok(Operation::AdvanceTillEthereumBlock {
block_hash: H256::decode(input)?,
}),
1 => Ok(Operation::ProgressTasks),
2 => Ok(Operation::ProcessQueues {
gas_allowance: u64::decode(input)?,
}),
3 => Ok(Operation::Injected(SignedInjectedTransaction::decode(
input,
)?)),
4 => Ok(Operation::ProcessQueuesV2 {
gas_allowance: u64::decode(input)?,
}),
5 => Ok(Operation::ProcessQueuesV3 {
gas_allowance: u64::decode(input)?,
}),
_ => Err(parity_scale_codec::Error::from("invalid operation tag")),
}
}
}
impl Encode for Operation {
fn encode_to<T: parity_scale_codec::Output + ?Sized>(&self, dest: &mut T) {
self.tag().encode_to(dest);
match self {
Operation::AdvanceTillEthereumBlock { block_hash } => block_hash.encode_to(dest),
Operation::ProgressTasks => {}
Operation::ProcessQueues { gas_allowance } => gas_allowance.encode_to(dest),
Operation::Injected(signed_tx) => signed_tx.encode_to(dest),
Operation::ProcessQueuesV2 { gas_allowance } => gas_allowance.encode_to(dest),
Operation::ProcessQueuesV3 { gas_allowance } => gas_allowance.encode_to(dest),
}
}
}
#[derive(
Clone, Debug, Default, PartialEq, Eq, Encode, Decode, TypeInfo, Deref, DerefMut, IntoIterator,
)]
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
pub struct Operations(pub Vec<Operation>);
impl Operations {
pub fn new(operations: Vec<Operation>) -> Self {
Self(operations)
}
pub fn hash(&self) -> H256 {
gear_core::utils::hash(&self.encode()).into()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn empty_txs() -> Operations {
Operations::new(alloc::vec![
Operation::ProgressTasks,
Operation::ProcessQueuesV3 {
gas_allowance: 1234,
},
])
}
#[test]
fn hash_is_deterministic_for_same_content() {
let a = empty_txs();
let b = empty_txs();
assert_eq!(a.hash(), b.hash());
}
#[test]
fn hash_changes_when_operations_change() {
let mut a = empty_txs();
let b = empty_txs();
a.push(Operation::AdvanceTillEthereumBlock {
block_hash: H256::from_low_u64_be(0xEB),
});
assert_ne!(a.hash(), b.hash());
}
#[test]
fn operation_tag_distinguishes_variants() {
let advance = Operation::AdvanceTillEthereumBlock {
block_hash: H256::zero(),
};
let progress = Operation::ProgressTasks;
let queues = Operation::ProcessQueuesV3 {
gas_allowance: 1234,
};
assert!(advance.is_advance_till_ethereum_block());
assert!(progress.is_progress_tasks());
assert!(queues.is_process_queues_v_3());
}
#[test]
fn operation_encoding_is_frozen() {
assert_eq!(
Operation::AdvanceTillEthereumBlock {
block_hash: H256::zero()
}
.tag(),
0
);
assert_eq!(Operation::ProgressTasks.tag(), 1);
assert_eq!(Operation::ProcessQueues { gas_allowance: 0 }.tag(), 2);
assert_eq!(Operation::ProcessQueuesV2 { gas_allowance: 0 }.tag(), 4);
assert_eq!(Operation::ProcessQueuesV3 { gas_allowance: 0 }.tag(), 5);
assert_eq!(
&Operation::AdvanceTillEthereumBlock {
block_hash: H256::zero()
}
.encode()[..4],
&[0, 0, 0, 0],
);
assert_eq!(Operation::ProgressTasks.encode(), [1, 0, 0, 0]);
assert_eq!(
&Operation::ProcessQueues { gas_allowance: 0 }.encode()[..4],
&[2, 0, 0, 0],
);
assert_eq!(
&Operation::ProcessQueuesV2 { gas_allowance: 0 }.encode()[..4],
&[4, 0, 0, 0],
);
assert_eq!(
&Operation::ProcessQueuesV3 { gas_allowance: 0 }.encode()[..4],
&[5, 0, 0, 0],
);
use parity_scale_codec::DecodeAll;
assert!(Operation::decode_all(&mut [6u8, 0, 0, 0].as_slice()).is_err());
}
#[test]
fn scale_round_trip_preserves_hash() {
use parity_scale_codec::Decode;
let original = Operations::new(alloc::vec![Operation::AdvanceTillEthereumBlock {
block_hash: H256::from_low_u64_be(0xEB)
}]);
let encoded = original.encode();
let decoded = Operations::decode(&mut encoded.as_slice()).expect("decode");
assert_eq!(original, decoded);
assert_eq!(original.hash(), decoded.hash());
}
}