diff --git a/Cargo.lock b/Cargo.lock index 38e46c3378..bf780df19e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2522,12 +2522,12 @@ checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "gmp-mpfr-sys" -version = "1.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66d61197a68f6323b9afa616cf83d55d69191e1bf364d4eb7d35ae18defe776" +version = "1.6.8" +source = "git+ssh://git@github.com/Entropy-Foundation/bicycl-rs?rev=05f9c22eef4c30f55e9633640269e7c9296510f0#05f9c22eef4c30f55e9633640269e7c9296510f0" dependencies = [ + "cc", "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 7354ecfad1..ce1f9a28d8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -144,6 +144,7 @@ serde_derive = "1.0" thiserror = "2.0" triehash = "0.8" walkdir = "2.5" +gmp-mpfr-sys = "1.6.8" [workspace.package] license = "MIT" @@ -164,6 +165,10 @@ rust.unreachable_pub = "warn" rust.unused_must_use = "deny" rustdoc.all = "warn" +[patch.crates-io] +gmp-mpfr-sys = { git = "ssh://git@github.com/Entropy-Foundation/gmp-mpfr-sys", branch="master" } + + [workspace.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] diff --git a/crates/supra-extension/src/errors.rs b/crates/supra-extension/src/errors.rs index 96ad0216f3..5e37ad12f3 100644 --- a/crates/supra-extension/src/errors.rs +++ b/crates/supra-extension/src/errors.rs @@ -14,9 +14,13 @@ pub enum SupraExtensionError { PayloadDecode(#[from] alloy_sol_types::Error), /// Reported on failure of task state conversion to counterpart in native layer. - #[error("Invalid automation task state value: {0}, expected [0, 1, 2]")] + #[error("Invalid automation task state value: {0}, expected [0(PENDING), 1(ACTIVE), 2(CANCELLED)]")] InvalidAutomationTaskStateValue(u8), + /// Reported on failure of task state conversion to counterpart in native layer. + #[error("Invalid automation task type value: {0}, expected [0(UST), 1(GST)]")] + InvalidAutomationTaskTypeValue(u8), + /// Reported when automated transaction builder is attempted to be built for inactive task. #[error("Attempt to create automated transaction builder for non-active task")] InvalidAutomationTaskStateForBuilder, diff --git a/crates/supra-extension/src/supra_contract_bindings/supra_contracts_bindings.rs b/crates/supra-extension/src/supra_contract_bindings/supra_contracts_bindings.rs index 8a17943a27..6d91a3276f 100644 --- a/crates/supra-extension/src/supra_contract_bindings/supra_contracts_bindings.rs +++ b/crates/supra-extension/src/supra_contract_bindings/supra_contracts_bindings.rs @@ -1421,6 +1421,8 @@ library CommonUtils { } interface SupraContractsBindings { + event AutomationCycleEvent(uint64 indexed index, CommonUtils.CycleState indexed state, uint64 startTime, uint64 durationSecs, CommonUtils.CycleState indexed oldState); + function blockPrologue() external; function getAllActiveTaskIds() external view returns (uint256[] memory); function getCycleStateDetails() external view returns (CommonUtils.CycleDetails memory details); @@ -1725,6 +1727,43 @@ interface SupraContractsBindings { ], "outputs": [], "stateMutability": "nonpayable" + }, + { + "type": "event", + "name": "AutomationCycleEvent", + "inputs": [ + { + "name": "index", + "type": "uint64", + "indexed": true, + "internalType": "uint64" + }, + { + "name": "state", + "type": "uint8", + "indexed": true, + "internalType": "enum CommonUtils.CycleState" + }, + { + "name": "startTime", + "type": "uint64", + "indexed": false, + "internalType": "uint64" + }, + { + "name": "durationSecs", + "type": "uint64", + "indexed": false, + "internalType": "uint64" + }, + { + "name": "oldState", + "type": "uint8", + "indexed": true, + "internalType": "enum CommonUtils.CycleState" + } + ], + "anonymous": false } ] ```*/ @@ -1760,6 +1799,150 @@ pub mod SupraContractsBindings { ); #[derive(serde::Serialize, serde::Deserialize)] #[derive(Default, Debug, PartialEq, Eq, Hash)] + /**Event with signature `AutomationCycleEvent(uint64,uint8,uint64,uint64,uint8)` and selector `0xe3a609ff9d35dde784f4ecc5c5988b3a4ad5ebeabb27e7ad22a76570128e51df`. +```solidity +event AutomationCycleEvent(uint64 indexed index, CommonUtils.CycleState indexed state, uint64 startTime, uint64 durationSecs, CommonUtils.CycleState indexed oldState); +```*/ + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + #[derive(Clone)] + pub struct AutomationCycleEvent { + #[allow(missing_docs)] + pub index: u64, + #[allow(missing_docs)] + pub state: ::RustType, + #[allow(missing_docs)] + pub startTime: u64, + #[allow(missing_docs)] + pub durationSecs: u64, + #[allow(missing_docs)] + pub oldState: ::RustType, + } + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + const _: () = { + use alloy::sol_types as alloy_sol_types; + #[automatically_derived] + impl alloy_sol_types::SolEvent for AutomationCycleEvent { + type DataTuple<'a> = ( + alloy::sol_types::sol_data::Uint<64>, + alloy::sol_types::sol_data::Uint<64>, + ); + type DataToken<'a> = as alloy_sol_types::SolType>::Token<'a>; + type TopicList = ( + alloy_sol_types::sol_data::FixedBytes<32>, + alloy::sol_types::sol_data::Uint<64>, + CommonUtils::CycleState, + CommonUtils::CycleState, + ); + const SIGNATURE: &'static str = "AutomationCycleEvent(uint64,uint8,uint64,uint64,uint8)"; + const SIGNATURE_HASH: alloy_sol_types::private::B256 = alloy_sol_types::private::B256::new([ + 227u8, 166u8, 9u8, 255u8, 157u8, 53u8, 221u8, 231u8, 132u8, 244u8, 236u8, + 197u8, 197u8, 152u8, 139u8, 58u8, 74u8, 213u8, 235u8, 234u8, 187u8, 39u8, + 231u8, 173u8, 34u8, 167u8, 101u8, 112u8, 18u8, 142u8, 81u8, 223u8, + ]); + const ANONYMOUS: bool = false; + #[allow(unused_variables)] + #[inline] + fn new( + topics: ::RustType, + data: as alloy_sol_types::SolType>::RustType, + ) -> Self { + Self { + index: topics.1, + state: topics.2, + startTime: data.0, + durationSecs: data.1, + oldState: topics.3, + } + } + #[inline] + fn check_signature( + topics: &::RustType, + ) -> alloy_sol_types::Result<()> { + if topics.0 != Self::SIGNATURE_HASH { + return Err( + alloy_sol_types::Error::invalid_event_signature_hash( + Self::SIGNATURE, + topics.0, + Self::SIGNATURE_HASH, + ), + ); + } + Ok(()) + } + #[inline] + fn tokenize_body(&self) -> Self::DataToken<'_> { + ( + as alloy_sol_types::SolType>::tokenize(&self.startTime), + as alloy_sol_types::SolType>::tokenize(&self.durationSecs), + ) + } + #[inline] + fn topics(&self) -> ::RustType { + ( + Self::SIGNATURE_HASH.into(), + self.index.clone(), + self.state.clone(), + self.oldState.clone(), + ) + } + #[inline] + fn encode_topics_raw( + &self, + out: &mut [alloy_sol_types::abi::token::WordToken], + ) -> alloy_sol_types::Result<()> { + if out.len() < ::COUNT { + return Err(alloy_sol_types::Error::Overrun); + } + out[0usize] = alloy_sol_types::abi::token::WordToken( + Self::SIGNATURE_HASH, + ); + out[1usize] = as alloy_sol_types::EventTopic>::encode_topic(&self.index); + out[2usize] = ::encode_topic( + &self.state, + ); + out[3usize] = ::encode_topic( + &self.oldState, + ); + Ok(()) + } + } + #[automatically_derived] + impl alloy_sol_types::private::IntoLogData for AutomationCycleEvent { + fn to_log_data(&self) -> alloy_sol_types::private::LogData { + From::from(self) + } + fn into_log_data(self) -> alloy_sol_types::private::LogData { + From::from(&self) + } + } + #[automatically_derived] + impl From<&AutomationCycleEvent> for alloy_sol_types::private::LogData { + #[inline] + fn from(this: &AutomationCycleEvent) -> alloy_sol_types::private::LogData { + alloy_sol_types::SolEvent::encode_log_data(this) + } + } + }; + #[derive(serde::Serialize, serde::Deserialize)] + #[derive(Default, Debug, PartialEq, Eq, Hash)] /**Function with signature `blockPrologue()` and selector `0x7ded091b`. ```solidity function blockPrologue() external; @@ -3430,6 +3613,106 @@ function processTasks(uint64 _cycleIndex, uint64[] memory _taskIndexes) external } } } + ///Container for all the [`SupraContractsBindings`](self) events. + #[derive(Clone)] + #[derive(serde::Serialize, serde::Deserialize)] + #[derive(Debug, PartialEq, Eq, Hash)] + pub enum SupraContractsBindingsEvents { + #[allow(missing_docs)] + AutomationCycleEvent(AutomationCycleEvent), + } + impl SupraContractsBindingsEvents { + /// All the selectors of this enum. + /// + /// Note that the selectors might not be in the same order as the variants. + /// No guarantees are made about the order of the selectors. + /// + /// Prefer using `SolInterface` methods instead. + pub const SELECTORS: &'static [[u8; 32usize]] = &[ + [ + 227u8, 166u8, 9u8, 255u8, 157u8, 53u8, 221u8, 231u8, 132u8, 244u8, 236u8, + 197u8, 197u8, 152u8, 139u8, 58u8, 74u8, 213u8, 235u8, 234u8, 187u8, 39u8, + 231u8, 173u8, 34u8, 167u8, 101u8, 112u8, 18u8, 142u8, 81u8, 223u8, + ], + ]; + /// The names of the variants in the same order as `SELECTORS`. + pub const VARIANT_NAMES: &'static [&'static str] = &[ + ::core::stringify!(AutomationCycleEvent), + ]; + /// The signatures in the same order as `SELECTORS`. + pub const SIGNATURES: &'static [&'static str] = &[ + ::SIGNATURE, + ]; + /// Returns the signature for the given selector, if known. + #[inline] + pub fn signature_by_selector( + selector: [u8; 32usize], + ) -> ::core::option::Option<&'static str> { + match Self::SELECTORS.binary_search(&selector) { + ::core::result::Result::Ok(idx) => { + ::core::option::Option::Some(Self::SIGNATURES[idx]) + } + ::core::result::Result::Err(_) => ::core::option::Option::None, + } + } + /// Returns the enum variant name for the given selector, if known. + #[inline] + pub fn name_by_selector( + selector: [u8; 32usize], + ) -> ::core::option::Option<&'static str> { + let sig = Self::signature_by_selector(selector)?; + sig.split_once('(').map(|(name, _)| name) + } + } + #[automatically_derived] + impl alloy_sol_types::SolEventInterface for SupraContractsBindingsEvents { + const NAME: &'static str = "SupraContractsBindingsEvents"; + const COUNT: usize = 1usize; + fn decode_raw_log( + topics: &[alloy_sol_types::Word], + data: &[u8], + ) -> alloy_sol_types::Result { + match topics.first().copied() { + Some( + ::SIGNATURE_HASH, + ) => { + ::decode_raw_log( + topics, + data, + ) + .map(Self::AutomationCycleEvent) + } + _ => { + alloy_sol_types::private::Err(alloy_sol_types::Error::InvalidLog { + name: ::NAME, + log: alloy_sol_types::private::Box::new( + alloy_sol_types::private::LogData::new_unchecked( + topics.to_vec(), + data.to_vec().into(), + ), + ), + }) + } + } + } + } + #[automatically_derived] + impl alloy_sol_types::private::IntoLogData for SupraContractsBindingsEvents { + fn to_log_data(&self) -> alloy_sol_types::private::LogData { + match self { + Self::AutomationCycleEvent(inner) => { + alloy_sol_types::private::IntoLogData::to_log_data(inner) + } + } + } + fn into_log_data(self) -> alloy_sol_types::private::LogData { + match self { + Self::AutomationCycleEvent(inner) => { + alloy_sol_types::private::IntoLogData::into_log_data(inner) + } + } + } + } use alloy::contract as alloy_contract; /**Creates a new wrapper around an on-chain [`SupraContractsBindings`](self) contract instance. @@ -3663,5 +3946,11 @@ the bytecode concatenated with the constructor's ABI-encoded arguments.*/ ) -> alloy_contract::Event<&P, E, N> { alloy_contract::Event::new_sol(&self.provider, &self.address) } + ///Creates a new event filter for the [`AutomationCycleEvent`] event. + pub fn AutomationCycleEvent_filter( + &self, + ) -> alloy_contract::Event<&P, AutomationCycleEvent, N> { + self.event_filter::() + } } } diff --git a/crates/supra-extension/src/transactions/automated_transaction.rs b/crates/supra-extension/src/transactions/automated_transaction.rs index 4b3934f7e8..578e1f188c 100644 --- a/crates/supra-extension/src/transactions/automated_transaction.rs +++ b/crates/supra-extension/src/transactions/automated_transaction.rs @@ -10,9 +10,12 @@ use alloy_eips::eip2718::Typed2718; use alloy_sol_types::SolType; use context::transaction::{AccessListItem, SignedAuthorization}; use context::TransactionType; +use derive_getters::{Dissolve, Getters}; use primitives::TxKind; +use serde::{Deserialize, Serialize}; +use std::cmp::Ordering; -#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] +#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] #[repr(u8)] @@ -20,11 +23,23 @@ use primitives::TxKind; pub enum AutomatedTransactionType { /// User submitted automation task based #[default] - UST, + UST = 0, /// Governance submitted/authorized automation task based. Will be gasless transaction GST, } +impl TryFrom for AutomatedTransactionType { + type Error = SupraExtensionError; + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(Self::UST), + 1 => Ok(Self::GST), + _ => Err(Self::Error::InvalidAutomationTaskTypeValue(value)), + } + } +} + #[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] @@ -82,11 +97,8 @@ pub struct AutomatedTransaction { serde(deserialize_with = "alloy_serde::null_as_default") )] pub access_list: AccessList, - /// Input has two uses depending if `to` field is Create or Call. - /// pub init: An unlimited size byte array specifying the - /// EVM-code for the account initialisation procedure CREATE, - /// data: An unlimited size byte array specifying the - /// input data of the message call, formally Td. + /// Input: An unlimited size byte array specifying the + /// input data of the message call. pub input: Bytes, } @@ -200,6 +212,23 @@ pub struct AutomatedTransactionDetails { pub priority: u64, } +impl Ord for AutomatedTransactionDetails { + fn cmp(&self, other: &Self) -> Ordering { + let left_type = &self.txn.txn_type; + let right_type = &other.txn.txn_type; + if left_type == right_type { + self.priority.cmp(&other.priority) + } else { + left_type.cmp(right_type) + } + } +} +impl PartialOrd for AutomatedTransactionDetails { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + type AccessListItemTy = ( alloy_sol_types::sol_data::Address, alloy_sol_types::sol_data::Array>, @@ -212,6 +241,36 @@ type ExpandedPayloadTy = ( AccessListTy, ); +/// Evm automation task execution payload. +#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize, Getters, Dissolve)] +pub struct TaskPayload { + to: Address, + value: U256, + input: Bytes, + access_list: AccessList, +} + +impl TryFrom<&[u8]> for TaskPayload { + type Error = SupraExtensionError; + + fn try_from(value: &[u8]) -> Result { + let (value, to, input, access_list) = ExpandedPayloadTy::abi_decode(value)?; + let access_items = access_list + .into_iter() + .map(|(address, storage_keys)| AccessListItem { + address, + storage_keys, + }) + .collect(); + Ok(Self { + to, + value, + access_list: AccessList(access_items), + input, + }) + } +} + /// Automation task state in native layer #[derive(Clone, Debug, PartialEq, Eq)] #[repr(u8)] @@ -240,10 +299,12 @@ pub enum BuildResult { Success(AutomatedTransactionDetails), /// Build failure due to gas-price limit surpass. GasPriceLimitExceeded { - /// Gas price specified for the transaction - gas_price: u128, + /// Task index for which error is observed. + task_index: u64, + /// Gas price specified for the transaction. + value: u128, /// Gas price threshold specified for the automation task during registration. - gas_price_cap: u128, + threshold: u128, }, } @@ -253,13 +314,13 @@ pub enum BuildResult { /// - priority - defaults to task-index /// - access_list - default to empty access-list /// - value - defaults to 0 -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Getters)] pub struct AutomatedTransactionBuilder { block_height: Option, chain_id: Option, gas_limit: Option, gas_price: Option, - gas_price_cap: u128, + gas_price_cap: Option, registration_hash: Option, task_index: Option, expiry_timestamp: Option, @@ -275,13 +336,13 @@ pub struct AutomatedTransactionBuilder { #[allow(missing_docs)] impl AutomatedTransactionBuilder { - pub fn new(gas_price_cap: u128) -> Self { + pub fn new() -> Self { Self { block_height: None, chain_id: None, gas_limit: None, gas_price: None, - gas_price_cap, + gas_price_cap: None, registration_hash: None, task_index: None, expiry_timestamp: None, @@ -295,65 +356,65 @@ impl AutomatedTransactionBuilder { } } - pub fn block_height(mut self, block_height: u64) -> Self { + pub fn with_block_height(mut self, block_height: u64) -> Self { self.block_height = Some(block_height); self } - pub fn chain_id(mut self, chain_id: ChainId) -> Self { + pub fn with_chain_id(mut self, chain_id: ChainId) -> Self { self.chain_id = Some(chain_id); self } - pub fn gas_limit(mut self, gas_limit: u64) -> Self { + pub fn with_gas_limit(mut self, gas_limit: u64) -> Self { self.gas_limit = Some(gas_limit); self } - pub fn gas_price(mut self, gas_price: u128) -> Self { + pub fn with_gas_price(mut self, gas_price: u128) -> Self { self.gas_price = Some(gas_price); self } - pub fn gas_price_cap(mut self, gas_price_cap: u128) -> Self { - self.gas_price_cap = gas_price_cap; + pub fn with_gas_price_cap(mut self, gas_price_cap: u128) -> Self { + self.gas_price_cap = Some(gas_price_cap); self } - pub fn registration_hash(mut self, registration_hash: B256) -> Self { + pub fn with_registration_hash(mut self, registration_hash: B256) -> Self { self.registration_hash = Some(registration_hash); self } - pub fn task_index(mut self, task_index: u64) -> Self { + pub fn with_task_index(mut self, task_index: u64) -> Self { self.task_index = Some(task_index); self } - pub fn expiry_timestamp(mut self, expiry_timestamp: u64) -> Self { + pub fn with_expiry_timestamp(mut self, expiry_timestamp: u64) -> Self { self.expiry_timestamp = Some(expiry_timestamp); self } - pub fn owner(mut self, owner: Address) -> Self { + pub fn with_owner(mut self, owner: Address) -> Self { self.owner = Some(owner); self } - pub fn tpy(mut self, tpy: AutomatedTransactionType) -> Self { + pub fn with_tpy(mut self, tpy: AutomatedTransactionType) -> Self { self.tpy = Some(tpy); self } - pub fn priority(mut self, priority: u64) -> Self { + pub fn with_priority(mut self, priority: u64) -> Self { self.priority = Some(priority); self } - pub fn to(mut self, to: Address) -> Self { + pub fn with_to(mut self, to: Address) -> Self { self.to = Some(to); self } - pub fn value(mut self, value: U256) -> Self { + pub fn with_value(mut self, value: U256) -> Self { self.value = Some(value); self } - pub fn access_list(mut self, access_list: AccessList) -> Self { + pub fn with_access_list(mut self, access_list: AccessList) -> Self { self.access_list = Some(access_list); self } - pub fn input(mut self, input: Bytes) -> Self { + pub fn with_input(mut self, input: Bytes) -> Self { self.input = Some(input); self } @@ -379,7 +440,9 @@ impl AutomatedTransactionBuilder { value_or_error!(AutomatedTransactionBuilder, "block_height", block_height); let chain_id = value_or_error!(AutomatedTransactionBuilder, "chain_id", chain_id); let gas_limit = value_or_error!(AutomatedTransactionBuilder, "gas_limit", gas_limit); - let gas_price = value_or_error!(AutomatedTransactionBuilder, "gasPrice", gas_price); + let gas_price_cap = + value_or_error!(AutomatedTransactionBuilder, "gas_price_cap", gas_price_cap); + let gas_price = value_or_error!(AutomatedTransactionBuilder, "gas_price", gas_price); let registration_hash = value_or_error!( AutomatedTransactionBuilder, "registration_hash", @@ -395,8 +458,9 @@ impl AutomatedTransactionBuilder { let input = value_or_error!(AutomatedTransactionBuilder, "input", input); if gas_price_cap < gas_price { return Ok(BuildResult::GasPriceLimitExceeded { - gas_price, - gas_price_cap, + task_index, + value: gas_price, + threshold: gas_price_cap, }); } let txn = AutomatedTransaction { @@ -418,15 +482,6 @@ impl AutomatedTransactionBuilder { priority, })) } - - /// Checks whether the task/transaction can be considered as expired compared to the input - /// timestamp threshold value - /// If no expiry timestamp is specified, the potential underlying task is not considered as expired. - pub fn is_expired(&self, threshold: u64) -> bool { - self.expiry_timestamp - .map(|t| t < threshold) - .unwrap_or(false) - } } /// Constructs [`AutomatedTransactionBuilder`] from automation task details loaded from chain state. @@ -457,26 +512,23 @@ impl TryFrom for AutomatedTransactionBuilder { if AutomationTaskState::try_from(state)? == AutomationTaskState::Pending { return Err(SupraExtensionError::InvalidAutomationTaskStateForBuilder); } - - let (value, to, input, access_list) = ExpandedPayloadTy::abi_decode(payloadTx.as_ref())?; - let access_items = access_list - .into_iter() - .map(|(address, storage_keys)| AccessListItem { - address, - storage_keys, - }) - .collect(); - let builder = Self::new(gasPriceCap) - .gas_limit(maxGasAmount as u64) - .gas_price_cap(gasPriceCap) - .registration_hash(txHash) - .task_index(taskIndex) - .expiry_timestamp(expiryTime) - .owner(owner) - .to(to) - .value(value) - .input(input) - .access_list(AccessList(access_items)); + let typ = AutomatedTransactionType::try_from(taskType)?; + + let (to, value, input, access_list) = TaskPayload::try_from(payloadTx.as_ref())?.dissolve(); + let builder = Self::new() + .with_gas_price_cap(gasPriceCap) + .with_gas_limit(maxGasAmount as u64) + .with_gas_price_cap(gasPriceCap) + .with_registration_hash(txHash) + .with_task_index(taskIndex) + .with_expiry_timestamp(expiryTime) + .with_owner(owner) + .with_to(to) + .with_value(value) + .with_input(input) + .with_access_list(access_list) + .with_tpy(typ) + .with_priority(priority); Ok(builder) } } @@ -487,7 +539,7 @@ mod test { use alloy::hex; use alloy_sol_types::SolType; #[test] - fn check_decode() { + fn check_payload_decode() { let encoded = hex!("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000006b182f1488e8efeb2eb298155ed5bd7ff8a14042000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000242e1a7d4d0000000000000000000000000000000000000000000000000000000000000064000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000001111000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000022220000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001"); let (value, to, input, access_list) = ExpandedPayloadTy::abi_decode(&encoded).unwrap(); println!("to: {:?}", to); diff --git a/crates/supra-extension/src/transactions/automation_record.rs b/crates/supra-extension/src/transactions/automation_record.rs index e0d693584a..f2fdf03c74 100644 --- a/crates/supra-extension/src/transactions/automation_record.rs +++ b/crates/supra-extension/src/transactions/automation_record.rs @@ -166,30 +166,31 @@ impl AutomationRecordBuilder { cycle_index: None, } } - pub fn block_height(mut self, block_height: u64) -> Self { + pub fn with_block_height(mut self, block_height: u64) -> Self { self.block_height = Some(block_height); self } - pub fn nonce(mut self, nonce: u64) -> Self { + + pub fn with_nonce(mut self, nonce: u64) -> Self { self.nonce = Some(nonce); self } - pub fn gas_limit(mut self, gas_limit: u64) -> Self { + pub fn with_gas_limit(mut self, gas_limit: u64) -> Self { self.gas_limit = Some(gas_limit); self } - pub fn task_indexes(mut self, task_indexes: Vec) -> Self { + pub fn with_task_indexes(mut self, task_indexes: Vec) -> Self { self.task_indexes = Some(task_indexes); self } - pub fn cycle_index(mut self, cycle_index: u64) -> Self { + pub fn with_cycle_index(mut self, cycle_index: u64) -> Self { self.cycle_index = Some(cycle_index); self } - pub fn chain_id(mut self, chain_id: ChainId) -> Self { + pub fn with_chain_id(mut self, chain_id: ChainId) -> Self { self.chain_id = Some(chain_id); self } @@ -229,4 +230,13 @@ impl AutomationRecordBuilder { }; Bytes::from(process_task_call.abi_encode()) } + + pub fn task_count(&self) -> usize { + self.task_indexes.as_ref().map(|idx| idx.len()).unwrap_or(0) + } + + pub fn into_task_indexes(self) -> Vec { + self.task_indexes.unwrap_or_default() + + } } diff --git a/crates/supra-extension/src/transactions/block_metadata.rs b/crates/supra-extension/src/transactions/block_metadata.rs index c9223d1297..77b7f7fa53 100644 --- a/crates/supra-extension/src/transactions/block_metadata.rs +++ b/crates/supra-extension/src/transactions/block_metadata.rs @@ -134,7 +134,7 @@ impl Typed2718 for BlockMetadata { /// Builder for [`BlockMetadata`] transaction. /// All properties are mandatory. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] pub struct BlockMetadataBuilder { to: Address, height: Option, diff --git a/solidity/supra_contracts/script/GovActions.s.sol b/solidity/supra_contracts/script/GovActions.s.sol new file mode 100644 index 0000000000..d658eff4b1 --- /dev/null +++ b/solidity/supra_contracts/script/GovActions.s.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import {Script, console} from "forge-std/Script.sol"; +import {MultiSignatureWallet} from "../src/MultiSignatureWallet.sol"; +import {BlockMeta} from "../src/BlockMeta.sol"; + +contract InitializeCycleMonitoring is Script { + address payable multisigWalletAddr; + address blockMetadata; + address automationController; + bytes4 selector; + uint64 timeout; + + function setUp() public { + multisigWalletAddr = payable(vm.envAddress("MULTISIG_WALLET_ADDRESS")); + blockMetadata = vm.envAddress("BLOCK_METADATA_ADDRESS"); + automationController = vm.envAddress("AUTOMATION_CONTROLLER"); + selector = bytes4(keccak256("monitorCycleEnd()")); + timeout = uint64(vm.envUint("TIMEOUT")); + } + + function run() public { + vm.startBroadcast(); + + // Initialize MultiSignatureWallet and get nextTxnIndex + MultiSignatureWallet wallet = MultiSignatureWallet(multisigWalletAddr); + uint256 nextTxnIndex = wallet.getNextTransactionIndex(); + console.log("TxnIndex: ", nextTxnIndex); + + // Submit a foundation/gov action to register automationController::monitor_cycle_event + // to be executed for each block + bytes memory data = abi.encodeCall(BlockMeta.register, (automationController, selector)); + wallet.submitTransaction(blockMetadata, 0, timeout, data); + + vm.stopBroadcast(); + } +} + +contract VoteForTxn is Script { + address payable multisigWalletAddr; + uint256 txn_index; + + + function setUp() public { + multisigWalletAddr = payable(vm.envAddress("MULTISIG_WALLET_ADDRESS")); + txn_index = uint256(vm.envUint("GOV_TXN_INDEX")); + } + + function run() public { + vm.startBroadcast(); + MultiSignatureWallet wallet = MultiSignatureWallet(multisigWalletAddr); + console.log("Txn count", wallet.txCount()); + wallet.confirmTransaction(txn_index); + vm.stopBroadcast(); + } +} + +contract ExecuteTxn is Script { + address payable multisigWalletAddr; + uint256 txn_index; + + + function setUp() public { + multisigWalletAddr = payable(vm.envAddress("MULTISIG_WALLET_ADDRESS")); + txn_index = uint256(vm.envUint("GOV_TXN_INDEX")); + } + + function run() public { + vm.startBroadcast(); + MultiSignatureWallet wallet = MultiSignatureWallet(multisigWalletAddr); + wallet.executeTransaction(txn_index); + vm.stopBroadcast(); + } +} diff --git a/solidity/supra_contracts/script/RegisterAutomationTask.s.sol b/solidity/supra_contracts/script/RegisterAutomationTask.s.sol index 4d6c076183..8f98193f4f 100644 --- a/solidity/supra_contracts/script/RegisterAutomationTask.s.sol +++ b/solidity/supra_contracts/script/RegisterAutomationTask.s.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.27; import {Script, console} from "forge-std/Script.sol"; import {IAutomationRegistry} from "../src/IAutomationRegistry.sol"; +import {AutomationRegistry} from "../src/AutomationRegistry.sol"; import {CommonUtils} from "../src/CommonUtils.sol"; import {IERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; import {LibConfig} from "../src/LibConfig.sol"; @@ -65,3 +66,28 @@ contract RegisterAutomationTask is Script { } } + +contract CancelAutomationTask is Script { + address registry; + uint64 taskIndex; + + address public constant TX_HASH_PRECOMPILE = 0x0000000000000000000000000000000053555001; + // Config values loaded from .env file + function setUp() public { + registry = vm.envAddress("REGISTRY"); + taskIndex = uint64(vm.envUint("TASK_INDEX")); + + TxHashPrecompile deployed = new TxHashPrecompile(); + vm.etch(TX_HASH_PRECOMPILE, address(deployed).code); + } + + function run() public { + vm.startBroadcast(); + AutomationRegistry registryImpl = AutomationRegistry(registry); + + registryImpl.cancelTask( taskIndex); + + vm.stopBroadcast(); + } + +} diff --git a/solidity/supra_contracts/src/MultiSignatureWallet.sol b/solidity/supra_contracts/src/MultiSignatureWallet.sol index 45688a645a..8e6d08fc2e 100644 --- a/solidity/supra_contracts/src/MultiSignatureWallet.sol +++ b/solidity/supra_contracts/src/MultiSignatureWallet.sol @@ -443,6 +443,14 @@ contract MultiSignatureWallet is Initializable { return owners.values(); } + /** + * @dev Function to retrieve the potential index of the next transaction. + * @return Index of the next transaction of uint256 type. + */ + function getNextTransactionIndex() public view returns (uint256) { + return txIndex; + } + /** * @dev Checks if a transaction is confirmed by an owner. * @param _txIndex Index of the transaction to check for. diff --git a/solidity/supra_contracts/src/SupraContractsBindings.sol b/solidity/supra_contracts/src/SupraContractsBindings.sol index 8b667b648a..0127eaf994 100644 --- a/solidity/supra_contracts/src/SupraContractsBindings.sol +++ b/solidity/supra_contracts/src/SupraContractsBindings.sol @@ -20,4 +20,13 @@ interface SupraContractsBindings { // Entry function of the BlockMeta for block metadata transaction function blockPrologue() external; + + // Emitted when the cycle state transitions. + event AutomationCycleEvent( + uint64 indexed index, + CommonUtils.CycleState indexed state, + uint64 startTime, + uint64 durationSecs, + CommonUtils.CycleState indexed oldState + ); } diff --git a/solidity/supra_contracts/submit_governance_action.sh b/solidity/supra_contracts/submit_governance_action.sh new file mode 100644 index 0000000000..12c2976aa4 --- /dev/null +++ b/solidity/supra_contracts/submit_governance_action.sh @@ -0,0 +1,51 @@ +#!/bin/bash -x + +# Script targeting localnet to initialize cycle monitoring for each block +# by registering AutomationController::monitor_cycle_end entry in block-metadata contract +# Steps: +# - For localnet run: +# - Start a supra localnet chain +# - cp Logs/owners/evm* into env_setup directory created next to this script +# +# - prepare .env file next to script with the following content +# +# MULTISIG_WALLET_ADDRESS=0x0a3fa0df1f4e8777ea4a752a5a06681af6acba49 +# BLOCK_METADATA_ADDRESS=0x2cd6f3c0f0ca46ea1616adf9e396ee99c24559df +# AUTOMATION_CONTROLLER=0x31fb454ab230303b7095064d385cae8d4da4651b +# TIMEOUT=360 +# +# - export PASSWORD variable, otherwise password will be requested during run +# - with value of the CLI_PROFILE_PASSWORD of the local nodes, which is currently "Blue!Tiger99@Moon.PROFILE" +# +# - run this script +# + +password="" +if [ -n ${PASSWORD} ]; then + password="--password ${PASSWORD}" +fi + +script_path=$(dirname $(realpath ${0})) +foundation_owners=( $(ls ${script_path}/env_setup/evm*) ) +foundation_owners_addresses=() +for owner in ${foundation_owners[*]} +do + foundation_owners_addresses+=( $(basename ${owner} | cut -d "_" -f2) ) +done + +echo ${foundation_owners[*]} ${foundation_owners_addresses[*]} + +result=$(forge script ${script_path}/script/GovActions.s.sol:InitializeCycleMonitoring --keystore ${foundation_owners[0]} --sender ${foundation_owners_addresses[0]} --broadcast ${password}) +export GOV_TXN_INDEX=$(echo ${result} | grep -o "TxnIndex: [0-9]* "| cut -d ":" -f2 | tr -d " ") + +echo "Voting for: ${GOV_TXN_INDEX}" +length=${#foundation_owners[@]} +for (( i = 1; i < length; i++ )); do + keystore=${foundation_owners[$i]} + address=${foundation_owners_addresses[$i]} + echo ${keystore} ${address} + forge script ${script_path}/script/GovActions.s.sol:VoteForTxn --keystore ${keystore} --sender ${address} --broadcast ${password} +done + +echo "Executing Txn with index: ${GOV_TXN_INDEX}" +forge script ${script_path}/script/GovActions.s.sol:ExecuteTxn --keystore ${foundation_owners[0]} --sender ${foundation_owners_addresses[0]} --broadcast ${password}