diff --git a/aptos-move/aptos-gas-schedule/src/gas_schedule/aptos_framework.rs b/aptos-move/aptos-gas-schedule/src/gas_schedule/aptos_framework.rs index 7d3c92bea7c49..3151e27ab4000 100644 --- a/aptos-move/aptos-gas-schedule/src/gas_schedule/aptos_framework.rs +++ b/aptos-move/aptos-gas-schedule/src/gas_schedule/aptos_framework.rs @@ -270,7 +270,6 @@ crate::gas_schedule::macros::define_gas_parameters!( [transaction_context_get_txn_hash_base: InternalGas, { 10.. => "transaction_context.get_txn_hash.base" }, 735], [transaction_context_get_script_hash_base: InternalGas, "transaction_context.get_script_hash.base", 735], - [transaction_context_get_txn_app_hash_base: InternalGas, "transaction_context.get_txn_app_hash.base", 735], // Based on SHA3-256's cost [transaction_context_generate_unique_address_base: InternalGas, { 10.. => "transaction_context.generate_unique_address.base" }, 14704], [transaction_context_sender_base: InternalGas, {RELEASE_V1_12.. => "transaction_context.sender.base"}, 735], diff --git a/aptos-move/aptos-vm/src/aptos_vm.rs b/aptos-move/aptos-vm/src/aptos_vm.rs index 43dc2ff475c5f..d6b36c884f794 100644 --- a/aptos-move/aptos-vm/src/aptos_vm.rs +++ b/aptos-move/aptos-vm/src/aptos_vm.rs @@ -921,6 +921,7 @@ impl AptosVM { traversal_context, txn_data.sender(), registration_params, + txn_data ) })?; @@ -958,6 +959,7 @@ impl AptosVM { traversal_context: &mut TraversalContext, sender: AccountAddress, registration_params: &RegistrationParams, + txn_metadata: &TransactionMetadata, ) -> Result<(), VMStatus> { // Note: Feature gating is needed here because the traversal of the dependencies could // result in shallow-loading of the modules and therefore subtle changes in @@ -972,7 +974,8 @@ impl AptosVM { [(module_id.address(), module_id.name())], )?; } - let args = registration_params.serialized_args_with_sender(sender); + let args = registration_params + .serialized_args_with_sender_and_parent_hash(sender, txn_metadata.txn_app_hash.clone()); session.execute_function_bypass_visibility( registration_params.module_id(), diff --git a/aptos-move/aptos-vm/src/transaction_metadata.rs b/aptos-move/aptos-vm/src/transaction_metadata.rs index e2fb65941cebd..9d1767044fa8f 100644 --- a/aptos-move/aptos-vm/src/transaction_metadata.rs +++ b/aptos-move/aptos-vm/src/transaction_metadata.rs @@ -173,7 +173,6 @@ impl TransactionMetadata { self.gas_unit_price.into(), self.chain_id.id(), payload_type_reference, - self.txn_app_hash.clone(), ) } } diff --git a/aptos-move/e2e-testsuite/src/tests/automation_registration.rs b/aptos-move/e2e-testsuite/src/tests/automation_registration.rs index ac94f4223e941..fdb84a222ba55 100644 --- a/aptos-move/e2e-testsuite/src/tests/automation_registration.rs +++ b/aptos-move/e2e-testsuite/src/tests/automation_registration.rs @@ -130,7 +130,7 @@ fn check_successful_registration() { let result = view_output.values.expect("Valid result"); assert_eq!(result.len(), 1); let next_task_id = bcs::from_bytes::(&result[0]).unwrap(); - assert_eq!(next_task_id, 2); + assert_eq!(next_task_id, 1); } #[test] diff --git a/aptos-move/framework/cached-packages/src/aptos_framework_sdk_builder.rs b/aptos-move/framework/cached-packages/src/aptos_framework_sdk_builder.rs index cec67b6f50aba..6aaa54ef3f477 100644 --- a/aptos-move/framework/cached-packages/src/aptos_framework_sdk_builder.rs +++ b/aptos-move/framework/cached-packages/src/aptos_framework_sdk_builder.rs @@ -147,12 +147,19 @@ pub enum EntryFunctionCall { cap_update_table: Vec, }, - /// Remove Automatioon task entry. - AutomationRegistryRemoveTask { - registry_id: u64, + /// Cancel Automation task with specified id. + /// Only existing task, which is PENDING or ACTIVE, can be cancled and only by task onwer. + /// If the task is + /// - active, its state is updated to be CANCELLED. + /// - pending, it is removed form the list. + /// - cancelled, an error is reported + /// Committed gas-limit is updated by reducing it with the max-gas-amount of the cancelled task. + AutomationRegistryCancelTask { + id: u64, }, - /// Update Automation gas limit + /// Update Automation gas limit. + /// If the committed gas amount for the next epoch is greater then the new gas limit, then error is reported. AutomationRegistryUpdateAutomationGasLimit { automation_gas_limit: u64, }, @@ -162,12 +169,6 @@ pub enum EntryFunctionCall { duration_upper_limit: u64, }, - /// Withdraw accumulated automation task fees from the resource account - access by admin - AutomationRegistryWithdrawAutomationTaskFees { - to: AccountAddress, - amount: u64, - }, - /// Same as `publish_package` but as an entry function which can be called as a transaction. Because /// of current restrictions for txn parameters, the metadata needs to be passed in serialized form. CodePublishPackageTxn { @@ -1202,18 +1203,13 @@ impl EntryFunctionCall { new_public_key_bytes, cap_update_table, ), - AutomationRegistryRemoveTask { registry_id } => { - automation_registry_remove_task(registry_id) - }, + AutomationRegistryCancelTask { id } => automation_registry_cancel_task(id), AutomationRegistryUpdateAutomationGasLimit { automation_gas_limit, } => automation_registry_update_automation_gas_limit(automation_gas_limit), AutomationRegistryUpdateDurationUpperLimit { duration_upper_limit, } => automation_registry_update_duration_upper_limit(duration_upper_limit), - AutomationRegistryWithdrawAutomationTaskFees { to, amount } => { - automation_registry_withdraw_automation_task_fees(to, amount) - }, CodePublishPackageTxn { metadata_serialized, code, @@ -2150,8 +2146,14 @@ pub fn account_rotate_authentication_key_with_rotation_capability( )) } -/// Remove Automatioon task entry. -pub fn automation_registry_remove_task(registry_id: u64) -> TransactionPayload { +/// Cancel Automation task with specified id. +/// Only existing task, which is PENDING or ACTIVE, can be cancled and only by task onwer. +/// If the task is +/// - active, its state is updated to be CANCELLED. +/// - pending, it is removed form the list. +/// - cancelled, an error is reported +/// Committed gas-limit is updated by reducing it with the max-gas-amount of the cancelled task. +pub fn automation_registry_cancel_task(id: u64) -> TransactionPayload { TransactionPayload::EntryFunction(EntryFunction::new( ModuleId::new( AccountAddress::new([ @@ -2160,13 +2162,14 @@ pub fn automation_registry_remove_task(registry_id: u64) -> TransactionPayload { ]), ident_str!("automation_registry").to_owned(), ), - ident_str!("remove_task").to_owned(), + ident_str!("cancel_task").to_owned(), vec![], - vec![bcs::to_bytes(®istry_id).unwrap()], + vec![bcs::to_bytes(&id).unwrap()], )) } -/// Update Automation gas limit +/// Update Automation gas limit. +/// If the committed gas amount for the next epoch is greater then the new gas limit, then error is reported. pub fn automation_registry_update_automation_gas_limit( automation_gas_limit: u64, ) -> TransactionPayload { @@ -2202,25 +2205,6 @@ pub fn automation_registry_update_duration_upper_limit( )) } -/// Withdraw accumulated automation task fees from the resource account - access by admin -pub fn automation_registry_withdraw_automation_task_fees( - to: AccountAddress, - amount: u64, -) -> TransactionPayload { - TransactionPayload::EntryFunction(EntryFunction::new( - ModuleId::new( - AccountAddress::new([ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 1, - ]), - ident_str!("automation_registry").to_owned(), - ), - ident_str!("withdraw_automation_task_fees").to_owned(), - vec![], - vec![bcs::to_bytes(&to).unwrap(), bcs::to_bytes(&amount).unwrap()], - )) -} - /// Same as `publish_package` but as an entry function which can be called as a transaction. Because /// of current restrictions for txn parameters, the metadata needs to be passed in serialized form. pub fn code_publish_package_txn( @@ -5381,12 +5365,12 @@ mod decoder { } } - pub fn automation_registry_remove_task( + pub fn automation_registry_cancel_task( payload: &TransactionPayload, ) -> Option { if let TransactionPayload::EntryFunction(script) = payload { - Some(EntryFunctionCall::AutomationRegistryRemoveTask { - registry_id: bcs::from_bytes(script.args().get(0)?).ok()?, + Some(EntryFunctionCall::AutomationRegistryCancelTask { + id: bcs::from_bytes(script.args().get(0)?).ok()?, }) } else { None @@ -5421,21 +5405,6 @@ mod decoder { } } - pub fn automation_registry_withdraw_automation_task_fees( - payload: &TransactionPayload, - ) -> Option { - if let TransactionPayload::EntryFunction(script) = payload { - Some( - EntryFunctionCall::AutomationRegistryWithdrawAutomationTaskFees { - to: bcs::from_bytes(script.args().get(0)?).ok()?, - amount: bcs::from_bytes(script.args().get(1)?).ok()?, - }, - ) - } else { - None - } - } - pub fn code_publish_package_txn(payload: &TransactionPayload) -> Option { if let TransactionPayload::EntryFunction(script) = payload { Some(EntryFunctionCall::CodePublishPackageTxn { @@ -7290,8 +7259,8 @@ static SCRIPT_FUNCTION_DECODER_MAP: once_cell::sync::Lazy, - _args: VecDeque, -) -> SafeNativeResult> { - context.charge(TRANSACTION_CONTEXT_GET_TXN_APP_HASH_BASE)?; - - let user_transaction_context_opt = get_user_transaction_context_opt_from_context(context); - if let Some(transaction_context) = user_transaction_context_opt { - Ok(smallvec![Value::vector_u8( - transaction_context.txn_app_hash() - )]) - } else { - Err(SafeNativeError::Abort { - abort_code: error::invalid_state(abort_codes::ETRANSACTION_CONTEXT_NOT_AVAILABLE), - }) - } -} - fn create_option_some_value(value: Value) -> Value { Value::struct_(Struct::pack(vec![create_singleton_vector(value)])) } @@ -431,7 +406,6 @@ pub fn make_all( "multisig_payload_internal", native_multisig_payload_internal, ), - ("txn_app_hash_internal", native_txn_app_hash_internal), ]; builder.make_named_natives(natives) diff --git a/aptos-move/framework/supra-framework/doc/automation_registry.md b/aptos-move/framework/supra-framework/doc/automation_registry.md index 3c1c2b1f37f51..cb200a29a35c1 100644 --- a/aptos-move/framework/supra-framework/doc/automation_registry.md +++ b/aptos-move/framework/supra-framework/doc/automation_registry.md @@ -9,15 +9,11 @@ This contract is part of the Supra Framework and is designed to manage automated - [Resource `AutomationRegistry`](#0x1_automation_registry_AutomationRegistry) -- [Struct `AutomationTaskMetaData`](#0x1_automation_registry_AutomationTaskMetaData) - [Struct `FeeWithdrawnAdmin`](#0x1_automation_registry_FeeWithdrawnAdmin) - [Struct `RefundFeeUser`](#0x1_automation_registry_RefundFeeUser) -- [Struct `UpdateAutomationGasLimit`](#0x1_automation_registry_UpdateAutomationGasLimit) - [Struct `UpdateDurationUpperLimit`](#0x1_automation_registry_UpdateDurationUpperLimit) -- [Struct `RemoveAutomationTask`](#0x1_automation_registry_RemoveAutomationTask) - [Constants](#@Constants_0) - [Function `initialize`](#0x1_automation_registry_initialize) -- [Function `on_new_epoch`](#0x1_automation_registry_on_new_epoch) - [Function `withdraw_automation_task_fees`](#0x1_automation_registry_withdraw_automation_task_fees) - [Function `transfer_fee_to_account_internal`](#0x1_automation_registry_transfer_fee_to_account_internal) - [Function `update_automation_gas_limit`](#0x1_automation_registry_update_automation_gas_limit) @@ -25,26 +21,25 @@ This contract is part of the Supra Framework and is designed to manage automated - [Function `charge_automation_fee_from_user`](#0x1_automation_registry_charge_automation_fee_from_user) - [Function `get_last_epoch_time_second`](#0x1_automation_registry_get_last_epoch_time_second) - [Function `register`](#0x1_automation_registry_register) -- [Function `remove_task`](#0x1_automation_registry_remove_task) +- [Function `cancel_task`](#0x1_automation_registry_cancel_task) - [Function `refund_automation_task_fee`](#0x1_automation_registry_refund_automation_task_fee) - [Function `get_next_task_index`](#0x1_automation_registry_get_next_task_index) - [Function `get_active_task_ids`](#0x1_automation_registry_get_active_task_ids) - [Function `get_task_details`](#0x1_automation_registry_get_task_details) +- [Function `has_active_task_with_id`](#0x1_automation_registry_has_active_task_with_id) - [Function `get_registry_fee_address`](#0x1_automation_registry_get_registry_fee_address) - [Function `get_gas_committed_for_next_epoch`](#0x1_automation_registry_get_gas_committed_for_next_epoch)
use 0x1::account;
+use 0x1::automation_registry_state;
 use 0x1::block;
-use 0x1::enumerable_map;
 use 0x1::event;
 use 0x1::reconfiguration;
 use 0x1::signer;
 use 0x1::supra_account;
 use 0x1::system_addresses;
 use 0x1::timestamp;
-use 0x1::transaction_context;
-use 0x1::vector;
 
@@ -67,18 +62,6 @@ It tracks entries both pending and completed, organized by unique indices.
-current_index: u64 -
-
- The current unique index counter for registered tasks. This value increments as new tasks are added. -
-
-automation_gas_limit: u64 -
-
- Automation task gas limit. -
-
duration_upper_limit: u64
@@ -108,95 +91,6 @@ It tracks entries both pending and completed, organized by unique indices.
Resource account signature capability
-
-tasks: enumerable_map::EnumerableMap<u64, automation_registry::AutomationTaskMetaData> -
-
- A collection of automation task entries that are active state. -
-
- - - - - - -## Struct `AutomationTaskMetaData` - -AutomationTaskMetaData represents a single automation task item, containing metadata. - - -
#[event]
-struct AutomationTaskMetaData has copy, drop, store
-
- - - -
-Fields - - -
-
-id: u64 -
-
- Automation task index in registry -
-
-owner: address -
-
- The address of the task owner. -
-
-payload_tx: vector<u8> -
-
- The function signature associated with the registry entry. -
-
-expiry_time: u64 -
-
- Expiry of the task, represented in a timestamp in second. -
-
-tx_hash: vector<u8> -
-
- The transaction hash of the request transaction. -
-
-max_gas_amount: u64 -
-
- Max gas amount of automation task -
-
-gas_price_cap: u64 -
-
- Maximum gas price cap for the task -
-
-registration_epoch: u64 -
-
- Registration epoch number -
-
-registration_time: u64 -
-
- Registration epoch time -
-
-is_active: bool -
-
- Flag indicating whether the task is active. -
@@ -270,35 +164,6 @@ Withdraw user's registration fee event -
- - - -## Struct `UpdateAutomationGasLimit` - -Update automation gas limit event - - -
#[event]
-struct UpdateAutomationGasLimit has drop, store
-
- - - -
-Fields - - -
-
-automation_gas_limit: u64 -
-
- -
-
- -
@@ -330,38 +195,19 @@ Update duration upper limit event - - -## Struct `RemoveAutomationTask` - -Remove automation task registry event - - -
#[event]
-struct RemoveAutomationTask has drop, store
-
- - - -
-Fields + +## Constants -
-
-id: u64 -
-
-
-
+ +Invalid expiry time: it cannot be earlier than the current time -
- +
const EINVALID_EXPIRY_TIME: u64 = 1;
+
-## Constants @@ -394,22 +240,12 @@ The default upper limit duration for automation task, specified in seconds (30 d - - -Automation task not found - - -
const EAUTOMATION_TASK_NOT_EXIST: u64 = 7;
-
- - - Expiry time must be after the start of the next epoch -
const EEXPIRY_BEFORE_NEXT_EPOCH: u64 = 4;
+
const EEXPIRY_BEFORE_NEXT_EPOCH: u64 = 3;
 
@@ -419,57 +255,7 @@ Expiry time must be after the start of the next epoch Expiry time does not go beyond upper cap duration -
const EEXPIRY_TIME_UPPER: u64 = 3;
-
- - - - - -Gas amount does not go beyond upper cap limit - - -
const EGAS_AMOUNT_UPPER: u64 = 5;
-
- - - - - -Invalid expiry time: it cannot be earlier than the current time - - -
const EINVALID_EXPIRY_TIME: u64 = 2;
-
- - - - - -Invalid gas price: it cannot be zero - - -
const EINVALID_GAS_PRICE: u64 = 6;
-
- - - - - -Registry Id not found - - -
const EREGITRY_NOT_FOUND: u64 = 1;
-
- - - - - -Unauthorized access: the caller is not the owner of the task - - -
const EUNAUTHORIZED_TASK_OWNER: u64 = 8;
+
const EEXPIRY_TIME_UPPER: u64 = 2;
 
@@ -511,6 +297,7 @@ Registry resource creation seed
public fun initialize(supra_framework: &signer) {
     system_addresses::assert_supra_framework(supra_framework);
+    automation_registry_state::initialize(supra_framework, DEFAULT_AUTOMATION_GAS_LIMIT);
 
     let (registry_fee_resource_signer, registry_fee_address_signer_cap) = account::create_resource_account(
         supra_framework,
@@ -518,70 +305,17 @@ Registry resource creation seed
     );
 
     move_to(supra_framework, AutomationRegistry {
-        current_index: 0,
-        automation_gas_limit: DEFAULT_AUTOMATION_GAS_LIMIT,
         duration_upper_limit: DEFAULT_DURATION_UPPER_LIMIT,
         gas_committed_for_next_epoch: 0,
         automation_unit_price: DEFAULT_AUTOMATION_UNIT_PRICE,
         registry_fee_address: signer::address_of(®istry_fee_resource_signer),
         registry_fee_address_signer_cap,
-        tasks: enumerable_map::new_map(),
     })
 }
 
- - - - -## Function `on_new_epoch` - - - -
public(friend) fun on_new_epoch(supra_framework: &signer)
-
- - - -
-Implementation - - -
public(friend) fun on_new_epoch(supra_framework: &signer) acquires AutomationRegistry {
-    system_addresses::assert_supra_framework(supra_framework);
-
-    let automation_registry = borrow_global_mut<AutomationRegistry>(@supra_framework);
-    let ids = enumerable_map::get_map_list(&automation_registry.tasks);
-
-    let current_time = timestamp::now_seconds();
-    let epoch_interval = block::get_epoch_interval_secs();
-    let expired_task_gas = 0;
-
-    // Perform clean up and updation of state
-    vector::for_each(ids, |id| {
-        let task = enumerable_map::get_value_mut(&mut automation_registry.tasks, id);
-
-        // Tasks that are active during this new epoch but will be already expired for the next epoch
-        if (task.expiry_time < (current_time + epoch_interval)) {
-            expired_task_gas = expired_task_gas + task.max_gas_amount;
-        };
-
-        if (task.expiry_time <= current_time) {
-            enumerable_map::remove_value(&mut automation_registry.tasks, id);
-        } else if (!task.is_active && task.expiry_time > current_time) {
-            task.is_active = true;
-        }
-    });
-
-    // Adjust the gas committed for the next epoch by subtracting the gas amount of the expired task
-    automation_registry.gas_committed_for_next_epoch = automation_registry.gas_committed_for_next_epoch - expired_task_gas;
-}
-
- - -
@@ -591,7 +325,7 @@ Registry resource creation seed Withdraw accumulated automation task fees from the resource account - access by admin -
entry fun withdraw_automation_task_fees(supra_framework: &signer, to: address, amount: u64)
+
public fun withdraw_automation_task_fees(supra_framework: &signer, to: address, amount: u64)
 
@@ -600,7 +334,7 @@ Withdraw accumulated automation task fees from the resource account - access by Implementation -
entry fun withdraw_automation_task_fees(
+
public fun withdraw_automation_task_fees(
     supra_framework: &signer,
     to: address,
     amount: u64
@@ -648,7 +382,8 @@ Transfers the specified fee amount from the resource account to the target accou
 
 ## Function `update_automation_gas_limit`
 
-Update Automation gas limit
+Update Automation gas limit.
+If the committed gas amount for the next epoch is greater then the new gas limit, then error is reported.
 
 
 
public entry fun update_automation_gas_limit(supra_framework: &signer, automation_gas_limit: u64)
@@ -663,13 +398,9 @@ Update Automation gas limit
 
public entry fun update_automation_gas_limit(
     supra_framework: &signer,
     automation_gas_limit: u64
-) acquires AutomationRegistry {
+) {
     system_addresses::assert_supra_framework(supra_framework);
-
-    let automation_registry = borrow_global_mut<AutomationRegistry>(@supra_framework);
-    automation_registry.automation_gas_limit = automation_gas_limit;
-
-    event::emit(UpdateAutomationGasLimit { automation_gas_limit });
+    automation_registry_state::update_automation_gas_limit(supra_framework, automation_gas_limit)
 }
 
@@ -717,7 +448,7 @@ Update duration upper limit Deducts the automation fee from the user's account based on the selected expiry time. -
fun charge_automation_fee_from_user(owner: &signer, fee: u64)
+
fun charge_automation_fee_from_user(owner: &signer, automation_unit_price: u64, task_duration: u64, registry_fee_address: address)
 
@@ -726,10 +457,10 @@ Deducts the automation fee from the user's account based on the selected expiry Implementation -
fun charge_automation_fee_from_user(owner: &signer, fee: u64) {
+
fun charge_automation_fee_from_user(owner: &signer, automation_unit_price: u64, task_duration: u64, registry_fee_address: address) {
+    let automation_base_fee = task_duration * automation_unit_price;
     // todo : dynamic price calculation is pending
-    let registry_fee_address = get_registry_fee_address();
-    supra_account::transfer(owner, registry_fee_address, fee);
+    supra_account::transfer(owner, registry_fee_address, automation_base_fee);
 }
 
@@ -770,7 +501,7 @@ Get last epoch time in second Registers a new automation task entry. -
public fun register(owner: &signer, payload_tx: vector<u8>, expiry_time: u64, max_gas_amount: u64, gas_price_cap: u64)
+
public fun register(owner: &signer, payload_tx: vector<u8>, expiry_time: u64, max_gas_amount: u64, gas_price_cap: u64, parent_hash: vector<u8>)
 
@@ -784,48 +515,38 @@ Registers a new automation task entry. payload_tx: vector<u8>, expiry_time: u64, max_gas_amount: u64, - gas_price_cap: u64 + gas_price_cap: u64, + parent_hash: vector<u8> ) acquires AutomationRegistry { let registry_data = borrow_global_mut<AutomationRegistry>(@supra_framework); - // todo : well formedness check of payload_tx + //Well-formedness check of payload_tx is done in native layer beforehand. let current_time = timestamp::now_seconds(); assert!(expiry_time > current_time, EINVALID_EXPIRY_TIME); + let task_duration = expiry_time - current_time; + assert!(task_duration < registry_data.duration_upper_limit, EEXPIRY_TIME_UPPER); - let expiry_time_duration = expiry_time - current_time; - assert!(expiry_time_duration < registry_data.duration_upper_limit, EEXPIRY_TIME_UPPER); - + // Check that task is valid at least in the next epoch let epoch_interval = block::get_epoch_interval_secs(); let last_epoch_time = get_last_epoch_time_second(); assert!(expiry_time > (last_epoch_time + epoch_interval), EEXPIRY_BEFORE_NEXT_EPOCH); - registry_data.gas_committed_for_next_epoch = registry_data.gas_committed_for_next_epoch + max_gas_amount; - assert!(registry_data.gas_committed_for_next_epoch < registry_data.automation_gas_limit, EGAS_AMOUNT_UPPER); - - assert!(gas_price_cap > 0, EINVALID_GAS_PRICE); - - let fee = expiry_time_duration * registry_data.automation_unit_price; - charge_automation_fee_from_user(owner, fee); - - registry_data.current_index = registry_data.current_index + 1; - - let automation_task_metadata = AutomationTaskMetaData { - id: registry_data.current_index, - owner: signer::address_of(owner), + let epoch = reconfiguration::current_epoch(); + automation_registry_state::register( + owner, payload_tx, expiry_time, max_gas_amount, gas_price_cap, - is_active: false, - registration_epoch: reconfiguration::current_epoch(), - registration_time: timestamp::now_seconds(), - tx_hash: transaction_context::txn_app_hash() - }; - - enumerable_map::add_value(&mut registry_data.tasks, registry_data.current_index, automation_task_metadata); - - event::emit(automation_task_metadata); + epoch, + parent_hash); + + charge_automation_fee_from_user( + owner, + registry_data.automation_unit_price, + task_duration, + registry_data.registry_fee_address); }
@@ -833,14 +554,20 @@ Registers a new automation task entry. - + -## Function `remove_task` +## Function `cancel_task` -Remove Automatioon task entry. +Cancel Automation task with specified id. +Only existing task, which is PENDING or ACTIVE, can be cancled and only by task onwer. +If the task is +- active, its state is updated to be CANCELLED. +- pending, it is removed form the list. +- cancelled, an error is reported +Committed gas-limit is updated by reducing it with the max-gas-amount of the cancelled task. -
public entry fun remove_task(owner: &signer, registry_id: u64)
+
public entry fun cancel_task(owner: &signer, id: u64)
 
@@ -849,24 +576,10 @@ Remove Automatioon task entry. Implementation -
public entry fun remove_task(owner: &signer, registry_id: u64) acquires AutomationRegistry {
-    let user_addr = signer::address_of(owner);
-
+
public entry fun cancel_task(owner: &signer, id: u64) acquires AutomationRegistry {
+    let automation_task_metadata = automation_registry_state::cancel_task(owner, id);
     let automation_registry = borrow_global_mut<AutomationRegistry>(@supra_framework);
-    assert!(enumerable_map::contains(&automation_registry.tasks, registry_id), EAUTOMATION_TASK_NOT_EXIST);
-
-    let automation_task_metadata = enumerable_map::get_value(&automation_registry.tasks, registry_id);
-    assert!(automation_task_metadata.owner == user_addr, EUNAUTHORIZED_TASK_OWNER);
-
-    enumerable_map::remove_value(&mut automation_registry.tasks, registry_id);
-
-    // Adjust the gas committed for the next epoch by subtracting the gas amount of the expired task
-    automation_registry.gas_committed_for_next_epoch = automation_registry.gas_committed_for_next_epoch - automation_task_metadata.max_gas_amount;
-
-    // Calculate refund fee and transfer it to user
-    refund_automation_task_fee(user_addr, automation_task_metadata, automation_registry.automation_unit_price);
-
-    event::emit(RemoveAutomationTask { id: automation_task_metadata.id });
+    refund_automation_task_fee(signer::address_of(owner), automation_task_metadata, automation_registry.automation_unit_price);
 }
 
@@ -881,7 +594,7 @@ Remove Automatioon task entry. Refunds the automation task fee to the user who has removed their task registration from the list. -
fun refund_automation_task_fee(user: address, automation_task_metadata: automation_registry::AutomationTaskMetaData, automation_unit_price: u64)
+
fun refund_automation_task_fee(user: address, automation_task_metadata: automation_registry_state::AutomationTaskMetaData, automation_unit_price: u64)
 
@@ -892,11 +605,11 @@ Refunds the automation task fee to the user who has removed their task registrat
fun refund_automation_task_fee(
     user: address,
-    automation_task_metadata: AutomationTaskMetaData,
+    automation_task_metadata: AutomationTaskMetaData,
     automation_unit_price: u64,
 ) acquires AutomationRegistry {
     let current_time = timestamp::now_seconds();
-    let expiry_time_duration = automation_task_metadata.expiry_time - current_time;
+    let expiry_time_duration = automation_registry_state::task_expiry_time(&automation_task_metadata) - current_time;
 
     let refund_amount = expiry_time_duration * automation_unit_price;
     transfer_fee_to_account_internal(user, refund_amount);
@@ -925,9 +638,8 @@ Returns next task index in registry
 Implementation
 
 
-
public fun get_next_task_index(): u64 acquires AutomationRegistry {
-    let automation_registry = borrow_global<AutomationRegistry>(@supra_framework);
-    automation_registry.current_index + 1
+
public fun get_next_task_index(): u64 {
+    automation_registry_state::get_next_task_index()
 }
 
@@ -939,7 +651,9 @@ Returns next task index in registry ## Function `get_active_task_ids` -List all the automation task ids +List all active automation task ids for the current epoch. +Note that the tasks with CANCELLED state are still considered active for the current epoch, +as cancellation takes effect in the next epoch only.
#[view]
@@ -952,19 +666,8 @@ List all the automation task ids
 Implementation
 
 
-
public fun get_active_task_ids(): vector<u64> acquires AutomationRegistry {
-    let automation_registry = borrow_global<AutomationRegistry>(@supra_framework);
-
-    let active_task_ids = vector[];
-    let ids = enumerable_map::get_map_list(&automation_registry.tasks);
-
-    vector::for_each(ids, |id| {
-        let task = enumerable_map::get_value(&automation_registry.tasks, id);
-        if (task.is_active) {
-            vector::push_back(&mut active_task_ids, id);
-        };
-    });
-    return active_task_ids
+
public fun get_active_task_ids(): vector<u64> {
+    automation_registry_state::get_active_task_ids()
 }
 
@@ -977,12 +680,37 @@ List all the automation task ids ## Function `get_task_details` Retrieves the details of a automation task entry by its ID. -Returns a tuple where the first element indicates if the registry is completed/failed (true) or pending (false), -and the second element contains the AutomationTaskMetaData details. +Error will be returned if entry with specified ID does not exist. + + +
#[view]
+public fun get_task_details(id: u64): automation_registry_state::AutomationTaskMetaData
+
+ + + +
+Implementation + + +
public fun get_task_details(id: u64): AutomationTaskMetaData {
+    automation_registry_state::get_task_details(id)
+}
+
+ + + +
+ + + +## Function `has_active_task_with_id` + +Checks whether there is an active task in registry with specified input task id.
#[view]
-public fun get_task_details(id: u64): automation_registry::AutomationTaskMetaData
+public fun has_active_task_with_id(id: u64): bool
 
@@ -991,10 +719,8 @@ and the second element contains the get_task_details(id: u64): AutomationTaskMetaData acquires AutomationRegistry { - let automation_task_metadata = borrow_global<AutomationRegistry>(@supra_framework); - assert!(enumerable_map::contains(&automation_task_metadata.tasks, id), EREGITRY_NOT_FOUND); - enumerable_map::get_value(&automation_task_metadata.tasks, id) +
public fun has_active_task_with_id(id: u64): bool {
+    automation_registry_state::has_active_task_with_id(id)
 }
 
@@ -1032,7 +758,7 @@ Get registry fee resource account address ## Function `get_gas_committed_for_next_epoch` -Ge gas committed for next epoch +Get gas committed for next epoch
#[view]
@@ -1045,9 +771,8 @@ Ge gas committed for next epoch
 Implementation
 
 
-
public fun get_gas_committed_for_next_epoch(): u64 acquires AutomationRegistry {
-    let automation_task_metadata = borrow_global<AutomationRegistry>(@supra_framework);
-    automation_task_metadata.gas_committed_for_next_epoch
+
public fun get_gas_committed_for_next_epoch(): u64 {
+    automation_registry_state::get_gas_committed_for_next_epoch()
 }
 
diff --git a/aptos-move/framework/supra-framework/doc/automation_registry_state.md b/aptos-move/framework/supra-framework/doc/automation_registry_state.md new file mode 100644 index 0000000000000..c2ac0ed343077 --- /dev/null +++ b/aptos-move/framework/supra-framework/doc/automation_registry_state.md @@ -0,0 +1,779 @@ + + + +# Module `0x1::automation_registry_state` + +Supra Automation Registry State + +This contract is part of the Supra Framework and is designed to manage automated task entries + + +- [Resource `AutomationRegistryState`](#0x1_automation_registry_state_AutomationRegistryState) +- [Struct `AutomationTaskMetaData`](#0x1_automation_registry_state_AutomationTaskMetaData) +- [Struct `UpdateAutomationGasLimit`](#0x1_automation_registry_state_UpdateAutomationGasLimit) +- [Struct `CancelledAutomationTask`](#0x1_automation_registry_state_CancelledAutomationTask) +- [Constants](#@Constants_0) +- [Function `task_expiry_time`](#0x1_automation_registry_state_task_expiry_time) +- [Function `initialize`](#0x1_automation_registry_state_initialize) +- [Function `on_new_epoch`](#0x1_automation_registry_state_on_new_epoch) +- [Function `register`](#0x1_automation_registry_state_register) +- [Function `cancel_task`](#0x1_automation_registry_state_cancel_task) +- [Function `update_automation_gas_limit`](#0x1_automation_registry_state_update_automation_gas_limit) +- [Function `get_active_task_ids`](#0x1_automation_registry_state_get_active_task_ids) +- [Function `get_task_details`](#0x1_automation_registry_state_get_task_details) +- [Function `has_active_task_with_id`](#0x1_automation_registry_state_has_active_task_with_id) +- [Function `get_next_task_index`](#0x1_automation_registry_state_get_next_task_index) +- [Function `get_gas_committed_for_next_epoch`](#0x1_automation_registry_state_get_gas_committed_for_next_epoch) + + +
use 0x1::enumerable_map;
+use 0x1::event;
+use 0x1::signer;
+use 0x1::system_addresses;
+use 0x1::timestamp;
+use 0x1::vector;
+
+ + + + + +## Resource `AutomationRegistryState` + +It tracks entries both pending and completed, organized by unique indices. + + +
struct AutomationRegistryState has store, key
+
+ + + +
+Fields + + +
+
+tasks: enumerable_map::EnumerableMap<u64, automation_registry_state::AutomationTaskMetaData> +
+
+ A collection of automation task entries that are active state. +
+
+current_index: u64 +
+
+ +
+
+gas_committed_for_next_epoch: u64 +
+
+ +
+
+automation_gas_limit: u64 +
+
+ +
+
+ + +
+ + + +## Struct `AutomationTaskMetaData` + +AutomationTaskMetaData represents a single automation task item, containing metadata. + + +
#[event]
+struct AutomationTaskMetaData has copy, drop, store
+
+ + + +
+Fields + + +
+
+id: u64 +
+
+ Automation task index in registry +
+
+owner: address +
+
+ The address of the task owner. +
+
+payload_tx: vector<u8> +
+
+ The function signature associated with the registry entry. +
+
+expiry_time: u64 +
+
+ Expiry of the task, represented in a timestamp in second. +
+
+tx_hash: vector<u8> +
+
+ The transaction hash of the request transaction. +
+
+max_gas_amount: u64 +
+
+ Max gas amount of automation task +
+
+gas_price_cap: u64 +
+
+ Maximum gas price cap for the task +
+
+registration_epoch: u64 +
+
+ Registration epoch number +
+
+registration_time: u64 +
+
+ Registration epoch time +
+
+state: u8 +
+
+ Flag indicating whether the task is active, canclled or pending. +
+
+ + +
+ + + +## Struct `UpdateAutomationGasLimit` + +Update automation gas limit event + + +
#[event]
+struct UpdateAutomationGasLimit has drop, store
+
+ + + +
+Fields + + +
+
+automation_gas_limit: u64 +
+
+ +
+
+ + +
+ + + +## Struct `CancelledAutomationTask` + +Cancelled automation task registry event + + +
#[event]
+struct CancelledAutomationTask has drop, store
+
+ + + +
+Fields + + +
+
+id: u64 +
+
+ +
+
+ + +
+ + + +## Constants + + + + + + +
const CANCELLED: u8 = 2;
+
+ + + + + + + +
const ACTIVE: u8 = 1;
+
+ + + + + +Task is already cancelled. + + +
const EALREADY_CANCELLED: u64 = 10;
+
+ + + + + +Task with provided Id not found + + +
const EAUTOMATION_TASK_NOT_FOUND: u64 = 4;
+
+ + + + + +Gas amount does not go beyond upper cap limit + + +
const EGAS_AMOUNT_UPPER: u64 = 5;
+
+ + + + + +Upon new epoch entry failed to propertly calculated committed gas for the next epoch. +It is greater than current epoch committed gas. + + +
const EINVALID_COMMITTED_GAS_CALCULATION: u64 = 7;
+
+ + + + + +Invalid expiry time: it cannot be earlier than the current time + + +
const EINVALID_EXPIRY_TIME: u64 = 1;
+
+ + + + + +Invalid gas price: it cannot be zero + + +
const EINVALID_GAS_PRICE: u64 = 2;
+
+ + + + + +Invalid max gas amount for automated task: it cannot be zero + + +
const EINVALID_MAX_GAS_AMOUNT: u64 = 3;
+
+ + + + + +Transactoin hash that registring current task is invalid. Lenght should be 32. + + +
const EINVALID_TXN_HASH: u64 = 8;
+
+ + + + + +Current committed gas amount is greater than the automation gas limit. + + +
const EUNACCEPTABLE_AUTOMATION_GAS_LIMIT: u64 = 9;
+
+ + + + + +Unauthorized access: the caller is not the owner of the task + + +
const EUNAUTHORIZED_TASK_OWNER: u64 = 6;
+
+ + + + + +Conversion factor between microseconds and second + + +
const MICROSECS_CONVERSION_FACTOR: u64 = 1000000;
+
+ + + + + +Constants describing task state. + + +
const PENDING: u8 = 0;
+
+ + + + + +The lenght of the transaction hash. + + +
const TXN_HASH_LENGTH: u64 = 32;
+
+ + + + + +## Function `task_expiry_time` + + + +
public(friend) fun task_expiry_time(task: &automation_registry_state::AutomationTaskMetaData): u64
+
+ + + +
+Implementation + + +
public(friend) fun task_expiry_time(task: &AutomationTaskMetaData): u64 {
+    task.expiry_time
+}
+
+ + + +
+ + + +## Function `initialize` + + + +
public(friend) fun initialize(supra_framework: &signer, automation_gas_limit: u64)
+
+ + + +
+Implementation + + +
public(friend) fun initialize(supra_framework: &signer, automation_gas_limit: u64) {
+    system_addresses::assert_supra_framework(supra_framework);
+
+    move_to(supra_framework, AutomationRegistryState {
+        tasks: enumerable_map::new_map(),
+        current_index: 0,
+        gas_committed_for_next_epoch: 0,
+        automation_gas_limit
+    })
+}
+
+ + + +
+ + + +## Function `on_new_epoch` + + + +
public(friend) fun on_new_epoch(epoch_interval_micro: u64)
+
+ + + +
+Implementation + + +
public(friend) fun on_new_epoch(epoch_interval_micro: u64) acquires AutomationRegistryState {
+    let state = borrow_global_mut<AutomationRegistryState>(@supra_framework);
+    let ids = enumerable_map::get_map_list(&state.tasks);
+
+    let epoch_interval_secs = epoch_interval_micro / MICROSECS_CONVERSION_FACTOR;
+    let current_time = timestamp::now_seconds();
+    let gas_committed_for_next_epoch = 0;
+
+    // Perform clean up and updation of state
+    vector::for_each(ids, |id| {
+        let task = enumerable_map::get_value_mut(&mut state.tasks, id);
+
+        // Tasks that are active during next epoch and are not cancled
+        // current_time shows the start time of the current new epoch.
+        if (task.state != CANCELLED && task.expiry_time > (current_time + epoch_interval_secs) ) {
+            gas_committed_for_next_epoch = gas_committed_for_next_epoch + task.max_gas_amount;
+        };
+
+        // Drop or activate task for this current epoch.
+        if (task.expiry_time <= current_time || task.state == CANCELLED) {
+            enumerable_map::remove_value(&mut state.tasks, id);
+        } else {
+            task.state = ACTIVE;
+        }
+    });
+
+    state.gas_committed_for_next_epoch = gas_committed_for_next_epoch;
+}
+
+ + + +
+ + + +## Function `register` + +Registers a new automation task entry. + + +
public(friend) fun register(owner: &signer, payload_tx: vector<u8>, expiry_time: u64, max_gas_amount: u64, gas_price_cap: u64, registration_epoch: u64, tx_hash: vector<u8>)
+
+ + + +
+Implementation + + +
public(friend) fun register(
+    owner: &signer,
+    payload_tx: vector<u8>,
+    expiry_time: u64,
+    max_gas_amount: u64,
+    gas_price_cap: u64,
+    registration_epoch: u64,
+    tx_hash: vector<u8>,
+) acquires AutomationRegistryState {
+    let registry_data = borrow_global_mut<AutomationRegistryState>(@supra_framework);
+    let registration_time = timestamp::now_seconds();
+
+    assert!(expiry_time > registration_time, EINVALID_EXPIRY_TIME);
+    assert!(gas_price_cap > 0, EINVALID_GAS_PRICE);
+    assert!(max_gas_amount > 0, EINVALID_MAX_GAS_AMOUNT);
+    assert!(vector::length(&tx_hash) == TXN_HASH_LENGTH, EINVALID_TXN_HASH);
+
+    let committed_gas = registry_data.gas_committed_for_next_epoch + max_gas_amount;
+    assert!(committed_gas < registry_data.automation_gas_limit, EGAS_AMOUNT_UPPER);
+    registry_data.gas_committed_for_next_epoch = committed_gas;
+    let task_index = registry_data.current_index;
+
+    let automation_task_metadata = AutomationTaskMetaData {
+        id: task_index,
+        owner: signer::address_of(owner),
+        payload_tx,
+        expiry_time,
+        max_gas_amount,
+        gas_price_cap,
+        state: PENDING,
+        registration_epoch,
+        registration_time,
+        tx_hash,
+    };
+
+    enumerable_map::add_value(&mut registry_data.tasks, task_index, automation_task_metadata);
+    registry_data.current_index = registry_data.current_index + 1;
+    event::emit(automation_task_metadata);
+}
+
+ + + +
+ + + +## Function `cancel_task` + +Cancel Automation task with specified id. +Only existing task, which is PENDING or ACTIVE, can be cancled and only by task onwer. +If the task is +- active, its state is updated to be CANCELLED. +- pending, it is removed form the list. +- cancelled, an error is reported +Committed gas-limit is updated by reducing it with the max-gas-amount of the cancelled task. + + +
public(friend) fun cancel_task(owner: &signer, id: u64): automation_registry_state::AutomationTaskMetaData
+
+ + + +
+Implementation + + +
public (friend) fun cancel_task(owner: &signer, id: u64): AutomationTaskMetaData acquires AutomationRegistryState {
+    let state = borrow_global_mut<AutomationRegistryState>(@supra_framework);
+    assert!(enumerable_map::contains(&state.tasks, id), EAUTOMATION_TASK_NOT_FOUND);
+
+    let automation_task_metadata = enumerable_map::get_value(&state.tasks, id);
+    assert!(automation_task_metadata.owner == signer::address_of(owner), EUNAUTHORIZED_TASK_OWNER);
+    assert!(automation_task_metadata.state != CANCELLED, EALREADY_CANCELLED);
+    if (automation_task_metadata.state == PENDING) {
+        enumerable_map::remove_value(&mut state.tasks, id);
+    } else if (automation_task_metadata.state == ACTIVE) {
+        automation_task_metadata.state = CANCELLED;
+        enumerable_map::update_value(&mut state.tasks, id, automation_task_metadata);
+    };
+
+    // Adjust the gas committed for the next epoch by subtracting the gas amount of the cancelled task
+    state.gas_committed_for_next_epoch = state.gas_committed_for_next_epoch - automation_task_metadata.max_gas_amount;
+
+    event::emit(CancelledAutomationTask { id: automation_task_metadata.id });
+    automation_task_metadata
+}
+
+ + + +
+ + + +## Function `update_automation_gas_limit` + +Update Automation gas limit. +If the committed gas amount for the next epoch is greater then the new gas limit, then error is reported. + + +
public(friend) fun update_automation_gas_limit(supra_framework: &signer, automation_gas_limit: u64)
+
+ + + +
+Implementation + + +
public (friend) fun update_automation_gas_limit(
+    supra_framework: &signer,
+    automation_gas_limit: u64
+) acquires AutomationRegistryState {
+    system_addresses::assert_supra_framework(supra_framework);
+
+    let state = borrow_global_mut<AutomationRegistryState>(@supra_framework);
+    assert!(state.gas_committed_for_next_epoch < automation_gas_limit, EUNACCEPTABLE_AUTOMATION_GAS_LIMIT);
+
+    state.automation_gas_limit = automation_gas_limit;
+
+    event::emit(UpdateAutomationGasLimit { automation_gas_limit });
+}
+
+ + + +
+ + + +## Function `get_active_task_ids` + +List all active automation task ids for the current epoch. +Note that the tasks with CANCELLED state are still considered active for the current epoch, +as cancellation takes effect in the next epoch only. + + +
public(friend) fun get_active_task_ids(): vector<u64>
+
+ + + +
+Implementation + + +
public(friend) fun get_active_task_ids(): vector<u64> acquires AutomationRegistryState {
+    let state = borrow_global<AutomationRegistryState>(@supra_framework);
+
+    let active_task_ids = vector[];
+    let ids = enumerable_map::get_map_list(&state.tasks);
+
+    vector::for_each(ids, |id| {
+        let task = enumerable_map::get_value_ref(&state.tasks, id);
+        if (task.state != PENDING) {
+            vector::push_back(&mut active_task_ids, id);
+        };
+    });
+    return active_task_ids
+}
+
+ + + +
+ + + +## Function `get_task_details` + +Retrieves the details of a automation task entry by its ID. +Error will be returned if entry with specified ID does not exist. + + +
public(friend) fun get_task_details(id: u64): automation_registry_state::AutomationTaskMetaData
+
+ + + +
+Implementation + + +
public (friend) fun get_task_details(id: u64): AutomationTaskMetaData acquires AutomationRegistryState {
+    let automation_task_metadata = borrow_global<AutomationRegistryState>(@supra_framework);
+    assert!(enumerable_map::contains(&automation_task_metadata.tasks, id), EAUTOMATION_TASK_NOT_FOUND);
+    enumerable_map::get_value(&automation_task_metadata.tasks, id)
+}
+
+ + + +
+ + + +## Function `has_active_task_with_id` + +Checks whether there is an active task in registry with specified input task id. + + +
public(friend) fun has_active_task_with_id(id: u64): bool
+
+ + + +
+Implementation + + +
public(friend) fun has_active_task_with_id(id: u64): bool acquires AutomationRegistryState {
+    let automation_task_metadata = borrow_global<AutomationRegistryState>(@supra_framework);
+    if (enumerable_map::contains(&automation_task_metadata.tasks, id)) {
+        let value = enumerable_map::get_value_ref(&automation_task_metadata.tasks, id);
+        value.state != PENDING
+    } else  {
+        false
+    }
+}
+
+ + + +
+ + + +## Function `get_next_task_index` + +Returns next task index in registry + + +
public(friend) fun get_next_task_index(): u64
+
+ + + +
+Implementation + + +
public (friend) fun get_next_task_index(): u64 acquires AutomationRegistryState {
+    let state = borrow_global<AutomationRegistryState>(@supra_framework);
+    state.current_index
+}
+
+ + + +
+ + + +## Function `get_gas_committed_for_next_epoch` + +Ge gas committed for next epoch + + +
public(friend) fun get_gas_committed_for_next_epoch(): u64
+
+ + + +
+Implementation + + +
public(friend) fun get_gas_committed_for_next_epoch(): u64 acquires AutomationRegistryState {
+    let state = borrow_global<AutomationRegistryState>(@supra_framework);
+    state.gas_committed_for_next_epoch
+}
+
+ + + +
+ + +[move-book]: https://aptos.dev/move/book/SUMMARY diff --git a/aptos-move/framework/supra-framework/doc/block.md b/aptos-move/framework/supra-framework/doc/block.md index 51c418a64bd73..34d260c61a51c 100644 --- a/aptos-move/framework/supra-framework/doc/block.md +++ b/aptos-move/framework/supra-framework/doc/block.md @@ -42,6 +42,7 @@ This module defines a struct storing the metadata of the block and new block eve
use 0x1::account;
+use 0x1::automation_registry_state;
 use 0x1::error;
 use 0x1::event;
 use 0x1::features;
@@ -669,6 +670,7 @@ The runtime always runs this before executing the transactions in a block.
     randomness::on_new_block(&vm, epoch, round, option::none());
     if (timestamp - reconfiguration::last_reconfiguration_time() >= epoch_interval) {
         reconfiguration::reconfigure();
+        automation_registry_state::on_new_epoch(epoch_interval);
     };
 }
 
@@ -718,6 +720,7 @@ The runtime always runs this before executing the transactions in a block. if (timestamp - reconfiguration::last_reconfiguration_time() >= epoch_interval) { reconfiguration_with_dkg::try_start(); + automation_registry_state::on_new_epoch(epoch_interval); }; }
@@ -1088,6 +1091,70 @@ The number of new events created does not exceed MAX_U64. + + + + +
schema BlockRequirement {
+    vm: signer;
+    hash: address;
+    epoch: u64;
+    round: u64;
+    proposer: address;
+    failed_proposer_indices: vector<u64>;
+    previous_block_votes_bitvec: vector<u8>;
+    timestamp: u64;
+    requires chain_status::is_operating();
+    requires system_addresses::is_vm(vm);
+    // This enforces high-level requirement 4:
+    requires proposer == @vm_reserved || stake::spec_is_current_epoch_validator(proposer);
+    requires (proposer == @vm_reserved) ==> (timestamp::spec_now_microseconds() == timestamp);
+    requires (proposer != @vm_reserved) ==> (timestamp::spec_now_microseconds() < timestamp);
+    requires exists<stake::ValidatorFees>(@supra_framework);
+    requires exists<CoinInfo<SupraCoin>>(@supra_framework);
+    include transaction_fee::RequiresCollectedFeesPerValueLeqBlockAptosSupply;
+    include staking_config::StakingRewardsConfigRequirement;
+}
+
+ + + + + + + +
schema Initialize {
+    supra_framework: signer;
+    epoch_interval_microsecs: u64;
+    let addr = signer::address_of(supra_framework);
+    // This enforces high-level requirement 2:
+    aborts_if addr != @supra_framework;
+    aborts_if epoch_interval_microsecs == 0;
+    aborts_if exists<BlockResource>(addr);
+    aborts_if exists<CommitHistory>(addr);
+    ensures exists<BlockResource>(addr);
+    ensures exists<CommitHistory>(addr);
+    ensures global<BlockResource>(addr).height == 0;
+}
+
+ + + + + + + +
schema NewEventHandle {
+    supra_framework: signer;
+    let addr = signer::address_of(supra_framework);
+    let account = global<account::Account>(addr);
+    aborts_if !exists<account::Account>(addr);
+    aborts_if account.guid_creation_num + 2 > MAX_U64;
+}
+
+ + + ### Function `update_epoch_interval_microsecs` diff --git a/aptos-move/framework/supra-framework/doc/overview.md b/aptos-move/framework/supra-framework/doc/overview.md index 88bd03b29e70f..ea2b6b6a186a9 100644 --- a/aptos-move/framework/supra-framework/doc/overview.md +++ b/aptos-move/framework/supra-framework/doc/overview.md @@ -17,6 +17,7 @@ This is the reference documentation of the Supra framework. - [`0x1::aggregator_factory`](aggregator_factory.md#0x1_aggregator_factory) - [`0x1::aggregator_v2`](aggregator_v2.md#0x1_aggregator_v2) - [`0x1::automation_registry`](automation_registry.md#0x1_automation_registry) +- [`0x1::automation_registry_state`](automation_registry_state.md#0x1_automation_registry_state) - [`0x1::block`](block.md#0x1_block) - [`0x1::chain_id`](chain_id.md#0x1_chain_id) - [`0x1::chain_status`](chain_status.md#0x1_chain_status) diff --git a/aptos-move/framework/supra-framework/doc/pbo_delegation_pool.md b/aptos-move/framework/supra-framework/doc/pbo_delegation_pool.md index 5ca21e1694513..8ab60e9eb3dba 100644 --- a/aptos-move/framework/supra-framework/doc/pbo_delegation_pool.md +++ b/aptos-move/framework/supra-framework/doc/pbo_delegation_pool.md @@ -157,6 +157,7 @@ transferred to A - [Function `calculate_and_update_delegator_voter`](#0x1_pbo_delegation_pool_calculate_and_update_delegator_voter) - [Function `get_expected_stake_pool_address`](#0x1_pbo_delegation_pool_get_expected_stake_pool_address) - [Function `min_remaining_secs_for_commission_change`](#0x1_pbo_delegation_pool_min_remaining_secs_for_commission_change) +- [Function `initialize_delegation_pool_with_amount`](#0x1_pbo_delegation_pool_initialize_delegation_pool_with_amount) - [Function `initialize_delegation_pool`](#0x1_pbo_delegation_pool_initialize_delegation_pool) - [Function `fund_delegators_with_locked_stake`](#0x1_pbo_delegation_pool_fund_delegators_with_locked_stake) - [Function `fund_delegators_with_stake`](#0x1_pbo_delegation_pool_fund_delegators_with_stake) @@ -1367,6 +1368,16 @@ Requested amount too high, the balance would fall below principle stake after un + + +Balance is not enough. + + +
const EBALANCE_NOT_SUFFICIENT: u64 = 38;
+
+ + + Coin value is not the same with principle stake. @@ -2294,6 +2305,61 @@ Return the minimum remaining time in seconds for commission change, which is one + + + + +## Function `initialize_delegation_pool_with_amount` + +Initialize a delegation pool without actual coin but withdraw from the owner's account. + + +
public fun initialize_delegation_pool_with_amount(owner: &signer, multisig_admin: option::Option<address>, amount: u64, operator_commission_percentage: u64, delegation_pool_creation_seed: vector<u8>, delegator_address: vector<address>, principle_stake: vector<u64>, unlock_numerators: vector<u64>, unlock_denominator: u64, unlock_start_time: u64, unlock_duration: u64)
+
+ + + +
+Implementation + + +
public fun initialize_delegation_pool_with_amount(
+    owner: &signer,
+    multisig_admin: option::Option<address>,
+    amount: u64,
+    operator_commission_percentage: u64,
+    delegation_pool_creation_seed: vector<u8>,
+    delegator_address: vector<address>,
+    principle_stake: vector<u64>,
+    unlock_numerators: vector<u64>,
+    unlock_denominator: u64,
+    unlock_start_time: u64,
+    unlock_duration: u64
+) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
+    assert!(
+        coin::balance<SupraCoin>(signer::address_of(owner)) >= amount,
+        error::invalid_argument(EBALANCE_NOT_SUFFICIENT)
+    );
+    let coin = coin::withdraw<SupraCoin>(owner, amount);
+
+    initialize_delegation_pool(
+        owner,
+        multisig_admin,
+        operator_commission_percentage,
+        delegation_pool_creation_seed,
+        delegator_address,
+        principle_stake,
+        coin,
+        unlock_numerators,
+        unlock_denominator,
+        unlock_start_time,
+        unlock_duration
+    )
+}
+
+ + +
diff --git a/aptos-move/framework/supra-framework/doc/transaction_context.md b/aptos-move/framework/supra-framework/doc/transaction_context.md index c2e2fbd781032..b930357c81eb3 100644 --- a/aptos-move/framework/supra-framework/doc/transaction_context.md +++ b/aptos-move/framework/supra-framework/doc/transaction_context.md @@ -30,8 +30,6 @@ - [Function `chain_id_internal`](#0x1_transaction_context_chain_id_internal) - [Function `entry_function_payload`](#0x1_transaction_context_entry_function_payload) - [Function `entry_function_payload_internal`](#0x1_transaction_context_entry_function_payload_internal) -- [Function `txn_app_hash`](#0x1_transaction_context_txn_app_hash) -- [Function `txn_app_hash_internal`](#0x1_transaction_context_txn_app_hash_internal) - [Function `account_address`](#0x1_transaction_context_account_address) - [Function `module_name`](#0x1_transaction_context_module_name) - [Function `function_name`](#0x1_transaction_context_function_name) @@ -734,55 +732,6 @@ This function aborts if called outside of the transaction prologue, execution, o - - - - -## Function `txn_app_hash` - -Returns the original transaction hash calculated on the raw-bytes. -This function aborts if called outside of the transaction prologue, execution, or epilogue phases. - - -
public fun txn_app_hash(): vector<u8>
-
- - - -
-Implementation - - -
public fun txn_app_hash(): vector<u8> {
-    assert!(features::transaction_context_extension_enabled(), error::invalid_state(ETRANSACTION_CONTEXT_EXTENSION_NOT_ENABLED));
-    txn_app_hash_internal()
-}
-
- - - -
- - - -## Function `txn_app_hash_internal` - - - -
fun txn_app_hash_internal(): vector<u8>
-
- - - -
-Implementation - - -
native fun txn_app_hash_internal(): vector<u8>;
-
- - -
diff --git a/aptos-move/framework/supra-framework/sources/automation_registry.move b/aptos-move/framework/supra-framework/sources/automation_registry.move index 99ee925fa988b..518133395ac23 100644 --- a/aptos-move/framework/supra-framework/sources/automation_registry.move +++ b/aptos-move/framework/supra-framework/sources/automation_registry.move @@ -4,18 +4,15 @@ module supra_framework::automation_registry { use std::signer; - use std::vector; - - use supra_std::enumerable_map::{Self, EnumerableMap}; + use supra_framework::event; + use supra_framework::automation_registry_state::{Self, AutomationTaskMetaData}; use supra_framework::account::{Self, SignerCapability}; use supra_framework::block; - use supra_framework::event; use supra_framework::reconfiguration; use supra_framework::supra_account; use supra_framework::system_addresses; use supra_framework::timestamp; - use supra_framework::transaction_context; #[test_only] use supra_framework::coin; @@ -24,22 +21,12 @@ module supra_framework::automation_registry { friend supra_framework::genesis; - /// Registry Id not found - const EREGITRY_NOT_FOUND: u64 = 1; /// Invalid expiry time: it cannot be earlier than the current time - const EINVALID_EXPIRY_TIME: u64 = 2; + const EINVALID_EXPIRY_TIME: u64 = 1; /// Expiry time does not go beyond upper cap duration - const EEXPIRY_TIME_UPPER: u64 = 3; + const EEXPIRY_TIME_UPPER: u64 = 2; /// Expiry time must be after the start of the next epoch - const EEXPIRY_BEFORE_NEXT_EPOCH: u64 = 4; - /// Gas amount does not go beyond upper cap limit - const EGAS_AMOUNT_UPPER: u64 = 5; - /// Invalid gas price: it cannot be zero - const EINVALID_GAS_PRICE: u64 = 6; - /// Automation task not found - const EAUTOMATION_TASK_NOT_EXIST: u64 = 7; - /// Unauthorized access: the caller is not the owner of the task - const EUNAUTHORIZED_TASK_OWNER: u64 = 8; + const EEXPIRY_BEFORE_NEXT_EPOCH: u64 = 3; /// The default automation task gas limit const DEFAULT_AUTOMATION_GAS_LIMIT: u64 = 100000000; @@ -54,10 +41,6 @@ module supra_framework::automation_registry { /// It tracks entries both pending and completed, organized by unique indices. struct AutomationRegistry has key, store { - /// The current unique index counter for registered tasks. This value increments as new tasks are added. - current_index: u64, - /// Automation task gas limit. - automation_gas_limit: u64, /// Automation task duration upper limit. duration_upper_limit: u64, /// Gas committed for next epoch @@ -68,34 +51,8 @@ module supra_framework::automation_registry { registry_fee_address: address, /// Resource account signature capability registry_fee_address_signer_cap: SignerCapability, - /// A collection of automation task entries that are active state. - tasks: EnumerableMap, } - #[event] - /// `AutomationTaskMetaData` represents a single automation task item, containing metadata. - struct AutomationTaskMetaData has copy, store, drop { - /// Automation task index in registry - id: u64, - /// The address of the task owner. - owner: address, - /// The function signature associated with the registry entry. - payload_tx: vector, - /// Expiry of the task, represented in a timestamp in second. - expiry_time: u64, - /// The transaction hash of the request transaction. - tx_hash: vector, - /// Max gas amount of automation task - max_gas_amount: u64, - /// Maximum gas price cap for the task - gas_price_cap: u64, - /// Registration epoch number - registration_epoch: u64, - /// Registration epoch time - registration_time: u64, - /// Flag indicating whether the task is active. - is_active: bool - } #[event] /// Withdraw user's registration fee event @@ -111,11 +68,6 @@ module supra_framework::automation_registry { amount: u64 } - #[event] - /// Update automation gas limit event - struct UpdateAutomationGasLimit has drop, store { - automation_gas_limit: u64 - } #[event] /// Update duration upper limit event @@ -123,15 +75,11 @@ module supra_framework::automation_registry { duration_upper_limit: u64 } - #[event] - /// Remove automation task registry event - struct RemoveAutomationTask has drop, store { - id: u64 - } // todo : this function should call during initialzation, but since we already done genesis in that case who can access the function public fun initialize(supra_framework: &signer) { system_addresses::assert_supra_framework(supra_framework); + automation_registry_state::initialize(supra_framework, DEFAULT_AUTOMATION_GAS_LIMIT); let (registry_fee_resource_signer, registry_fee_address_signer_cap) = account::create_resource_account( supra_framework, @@ -139,49 +87,17 @@ module supra_framework::automation_registry { ); move_to(supra_framework, AutomationRegistry { - current_index: 0, - automation_gas_limit: DEFAULT_AUTOMATION_GAS_LIMIT, duration_upper_limit: DEFAULT_DURATION_UPPER_LIMIT, gas_committed_for_next_epoch: 0, automation_unit_price: DEFAULT_AUTOMATION_UNIT_PRICE, registry_fee_address: signer::address_of(®istry_fee_resource_signer), registry_fee_address_signer_cap, - tasks: enumerable_map::new_map(), }) } - public(friend) fun on_new_epoch(supra_framework: &signer) acquires AutomationRegistry { - system_addresses::assert_supra_framework(supra_framework); - - let automation_registry = borrow_global_mut(@supra_framework); - let ids = enumerable_map::get_map_list(&automation_registry.tasks); - - let current_time = timestamp::now_seconds(); - let epoch_interval = block::get_epoch_interval_secs(); - let expired_task_gas = 0; - - // Perform clean up and updation of state - vector::for_each(ids, |id| { - let task = enumerable_map::get_value_mut(&mut automation_registry.tasks, id); - - // Tasks that are active during this new epoch but will be already expired for the next epoch - if (task.expiry_time < (current_time + epoch_interval)) { - expired_task_gas = expired_task_gas + task.max_gas_amount; - }; - - if (task.expiry_time <= current_time) { - enumerable_map::remove_value(&mut automation_registry.tasks, id); - } else if (!task.is_active && task.expiry_time > current_time) { - task.is_active = true; - } - }); - - // Adjust the gas committed for the next epoch by subtracting the gas amount of the expired task - automation_registry.gas_committed_for_next_epoch = automation_registry.gas_committed_for_next_epoch - expired_task_gas; - } /// Withdraw accumulated automation task fees from the resource account - access by admin - entry fun withdraw_automation_task_fees( + public fun withdraw_automation_task_fees( supra_framework: &signer, to: address, amount: u64 @@ -200,17 +116,14 @@ module supra_framework::automation_registry { supra_account::transfer(&resource_signer, to, amount); } - /// Update Automation gas limit + /// Update Automation gas limit. + /// If the committed gas amount for the next epoch is greater then the new gas limit, then error is reported. public entry fun update_automation_gas_limit( supra_framework: &signer, automation_gas_limit: u64 - ) acquires AutomationRegistry { + ) { system_addresses::assert_supra_framework(supra_framework); - - let automation_registry = borrow_global_mut(@supra_framework); - automation_registry.automation_gas_limit = automation_gas_limit; - - event::emit(UpdateAutomationGasLimit { automation_gas_limit }); + automation_registry_state::update_automation_gas_limit(supra_framework, automation_gas_limit) } /// Update duration upper limit @@ -227,10 +140,10 @@ module supra_framework::automation_registry { } /// Deducts the automation fee from the user's account based on the selected expiry time. - fun charge_automation_fee_from_user(owner: &signer, fee: u64) { + fun charge_automation_fee_from_user(owner: &signer, automation_unit_price: u64, task_duration: u64, registry_fee_address: address) { + let automation_base_fee = task_duration * automation_unit_price; // todo : dynamic price calculation is pending - let registry_fee_address = get_registry_fee_address(); - supra_account::transfer(owner, registry_fee_address, fee); + supra_account::transfer(owner, registry_fee_address, automation_base_fee); } /// Get last epoch time in second @@ -245,69 +158,51 @@ module supra_framework::automation_registry { payload_tx: vector, expiry_time: u64, max_gas_amount: u64, - gas_price_cap: u64 + gas_price_cap: u64, + parent_hash: vector ) acquires AutomationRegistry { let registry_data = borrow_global_mut(@supra_framework); - // todo : well formedness check of payload_tx + //Well-formedness check of payload_tx is done in native layer beforehand. let current_time = timestamp::now_seconds(); assert!(expiry_time > current_time, EINVALID_EXPIRY_TIME); + let task_duration = expiry_time - current_time; + assert!(task_duration < registry_data.duration_upper_limit, EEXPIRY_TIME_UPPER); - let expiry_time_duration = expiry_time - current_time; - assert!(expiry_time_duration < registry_data.duration_upper_limit, EEXPIRY_TIME_UPPER); - + // Check that task is valid at least in the next epoch let epoch_interval = block::get_epoch_interval_secs(); let last_epoch_time = get_last_epoch_time_second(); assert!(expiry_time > (last_epoch_time + epoch_interval), EEXPIRY_BEFORE_NEXT_EPOCH); - registry_data.gas_committed_for_next_epoch = registry_data.gas_committed_for_next_epoch + max_gas_amount; - assert!(registry_data.gas_committed_for_next_epoch < registry_data.automation_gas_limit, EGAS_AMOUNT_UPPER); - - assert!(gas_price_cap > 0, EINVALID_GAS_PRICE); - - let fee = expiry_time_duration * registry_data.automation_unit_price; - charge_automation_fee_from_user(owner, fee); - - registry_data.current_index = registry_data.current_index + 1; - - let automation_task_metadata = AutomationTaskMetaData { - id: registry_data.current_index, - owner: signer::address_of(owner), + let epoch = reconfiguration::current_epoch(); + automation_registry_state::register( + owner, payload_tx, expiry_time, max_gas_amount, gas_price_cap, - is_active: false, - registration_epoch: reconfiguration::current_epoch(), - registration_time: timestamp::now_seconds(), - tx_hash: transaction_context::txn_app_hash() - }; - - enumerable_map::add_value(&mut registry_data.tasks, registry_data.current_index, automation_task_metadata); - - event::emit(automation_task_metadata); + epoch, + parent_hash); + + charge_automation_fee_from_user( + owner, + registry_data.automation_unit_price, + task_duration, + registry_data.registry_fee_address); } - /// Remove Automatioon task entry. - public entry fun remove_task(owner: &signer, registry_id: u64) acquires AutomationRegistry { - let user_addr = signer::address_of(owner); - + /// Cancel Automation task with specified id. + /// Only existing task, which is PENDING or ACTIVE, can be cancled and only by task onwer. + /// If the task is + /// - active, its state is updated to be CANCELLED. + /// - pending, it is removed form the list. + /// - cancelled, an error is reported + /// Committed gas-limit is updated by reducing it with the max-gas-amount of the cancelled task. + public entry fun cancel_task(owner: &signer, id: u64) acquires AutomationRegistry { + let automation_task_metadata = automation_registry_state::cancel_task(owner, id); let automation_registry = borrow_global_mut(@supra_framework); - assert!(enumerable_map::contains(&automation_registry.tasks, registry_id), EAUTOMATION_TASK_NOT_EXIST); - - let automation_task_metadata = enumerable_map::get_value(&automation_registry.tasks, registry_id); - assert!(automation_task_metadata.owner == user_addr, EUNAUTHORIZED_TASK_OWNER); - - enumerable_map::remove_value(&mut automation_registry.tasks, registry_id); - - // Adjust the gas committed for the next epoch by subtracting the gas amount of the expired task - automation_registry.gas_committed_for_next_epoch = automation_registry.gas_committed_for_next_epoch - automation_task_metadata.max_gas_amount; - - // Calculate refund fee and transfer it to user - refund_automation_task_fee(user_addr, automation_task_metadata, automation_registry.automation_unit_price); - - event::emit(RemoveAutomationTask { id: automation_task_metadata.id }); + refund_automation_task_fee(signer::address_of(owner), automation_task_metadata, automation_registry.automation_unit_price); } /// Refunds the automation task fee to the user who has removed their task registration from the list. @@ -317,7 +212,7 @@ module supra_framework::automation_registry { automation_unit_price: u64, ) acquires AutomationRegistry { let current_time = timestamp::now_seconds(); - let expiry_time_duration = automation_task_metadata.expiry_time - current_time; + let expiry_time_duration = automation_registry_state::task_expiry_time(&automation_task_metadata) - current_time; let refund_amount = expiry_time_duration * automation_unit_price; transfer_fee_to_account_internal(user, refund_amount); @@ -326,36 +221,29 @@ module supra_framework::automation_registry { #[view] /// Returns next task index in registry - public fun get_next_task_index(): u64 acquires AutomationRegistry { - let automation_registry = borrow_global(@supra_framework); - automation_registry.current_index + 1 + public fun get_next_task_index(): u64 { + automation_registry_state::get_next_task_index() } #[view] - /// List all the automation task ids - public fun get_active_task_ids(): vector acquires AutomationRegistry { - let automation_registry = borrow_global(@supra_framework); - - let active_task_ids = vector[]; - let ids = enumerable_map::get_map_list(&automation_registry.tasks); - - vector::for_each(ids, |id| { - let task = enumerable_map::get_value(&automation_registry.tasks, id); - if (task.is_active) { - vector::push_back(&mut active_task_ids, id); - }; - }); - return active_task_ids + /// List all active automation task ids for the current epoch. + /// Note that the tasks with CANCELLED state are still considered active for the current epoch, + /// as cancellation takes effect in the next epoch only. + public fun get_active_task_ids(): vector { + automation_registry_state::get_active_task_ids() } #[view] /// Retrieves the details of a automation task entry by its ID. - /// Returns a tuple where the first element indicates if the registry is completed/failed (`true`) or pending (`false`), - /// and the second element contains the `AutomationTaskMetaData` details. - public fun get_task_details(id: u64): AutomationTaskMetaData acquires AutomationRegistry { - let automation_task_metadata = borrow_global(@supra_framework); - assert!(enumerable_map::contains(&automation_task_metadata.tasks, id), EREGITRY_NOT_FOUND); - enumerable_map::get_value(&automation_task_metadata.tasks, id) + /// Error will be returned if entry with specified ID does not exist. + public fun get_task_details(id: u64): AutomationTaskMetaData { + automation_registry_state::get_task_details(id) + } + + #[view] + /// Checks whether there is an active task in registry with specified input task id. + public fun has_active_task_with_id(id: u64): bool { + automation_registry_state::has_active_task_with_id(id) } #[view] @@ -365,10 +253,9 @@ module supra_framework::automation_registry { } #[view] - /// Ge gas committed for next epoch - public fun get_gas_committed_for_next_epoch(): u64 acquires AutomationRegistry { - let automation_task_metadata = borrow_global(@supra_framework); - automation_task_metadata.gas_committed_for_next_epoch + /// Get gas committed for next epoch + public fun get_gas_committed_for_next_epoch(): u64 { + automation_registry_state::get_gas_committed_for_next_epoch() } #[test_only] @@ -391,11 +278,11 @@ module supra_framework::automation_registry { } #[test(supra_framework = @supra_framework, user = @0x1cafe)] - #[expected_failure(abort_code=196609, location=transaction_context)] fun test_registry(supra_framework: &signer, user: &signer) acquires AutomationRegistry { initialize_registry_test(supra_framework, user); let payload = x"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f4041424344"; - register(user, payload, 86400, 1000, 100000); + let parent_hash = x"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"; + register(user, payload, 86400, 1000, 100000, parent_hash); } } diff --git a/aptos-move/framework/supra-framework/sources/automation_registry_state.move b/aptos-move/framework/supra-framework/sources/automation_registry_state.move new file mode 100644 index 0000000000000..4e1f0d926fd2b --- /dev/null +++ b/aptos-move/framework/supra-framework/sources/automation_registry_state.move @@ -0,0 +1,631 @@ +/// Supra Automation Registry State +/// +/// This contract is part of the Supra Framework and is designed to manage automated task entries +module supra_framework::automation_registry_state { + + use std::signer; + use std::vector; + use supra_framework::event; + + use supra_std::enumerable_map::{Self, EnumerableMap}; + + use supra_framework::system_addresses; + use supra_framework::timestamp; + #[test_only] + use supra_framework::account; + + friend supra_framework::automation_registry; + friend supra_framework::block; + + /// Invalid expiry time: it cannot be earlier than the current time + const EINVALID_EXPIRY_TIME: u64 = 1; + /// Invalid gas price: it cannot be zero + const EINVALID_GAS_PRICE: u64 = 2; + /// Invalid max gas amount for automated task: it cannot be zero + const EINVALID_MAX_GAS_AMOUNT: u64 = 3; + /// Task with provided Id not found + const EAUTOMATION_TASK_NOT_FOUND: u64 = 4; + /// Gas amount does not go beyond upper cap limit + const EGAS_AMOUNT_UPPER: u64 = 5; + /// Unauthorized access: the caller is not the owner of the task + const EUNAUTHORIZED_TASK_OWNER: u64 = 6; + /// Upon new epoch entry failed to propertly calculated committed gas for the next epoch. + /// It is greater than current epoch committed gas. + const EINVALID_COMMITTED_GAS_CALCULATION: u64 = 7; + /// Transactoin hash that registring current task is invalid. Lenght should be 32. + const EINVALID_TXN_HASH: u64 = 8; + /// Current committed gas amount is greater than the automation gas limit. + const EUNACCEPTABLE_AUTOMATION_GAS_LIMIT: u64 = 9; + /// Task is already cancelled. + const EALREADY_CANCELLED: u64 = 10; + + /// The lenght of the transaction hash. + const TXN_HASH_LENGTH: u64 = 32; + + /// Conversion factor between microseconds and second + const MICROSECS_CONVERSION_FACTOR: u64 = 1_000_000; + + /// Constants describing task state. + const PENDING: u8 = 0; + const ACTIVE: u8 = 1; + const CANCELLED: u8 = 2; + + /// It tracks entries both pending and completed, organized by unique indices. + struct AutomationRegistryState has key, store { + /// A collection of automation task entries that are active state. + tasks: EnumerableMap, + current_index: u64, + gas_committed_for_next_epoch: u64, + automation_gas_limit: u64, + } + + #[event] + /// `AutomationTaskMetaData` represents a single automation task item, containing metadata. + struct AutomationTaskMetaData has copy, store, drop { + /// Automation task index in registry + id: u64, + /// The address of the task owner. + owner: address, + /// The function signature associated with the registry entry. + payload_tx: vector, + /// Expiry of the task, represented in a timestamp in second. + expiry_time: u64, + /// The transaction hash of the request transaction. + tx_hash: vector, + /// Max gas amount of automation task + max_gas_amount: u64, + /// Maximum gas price cap for the task + gas_price_cap: u64, + /// Registration epoch number + registration_epoch: u64, + /// Registration epoch time + registration_time: u64, + /// Flag indicating whether the task is active, canclled or pending. + state: u8 + } + + #[event] + /// Update automation gas limit event + struct UpdateAutomationGasLimit has drop, store { + automation_gas_limit: u64 + } + + #[event] + /// Cancelled automation task registry event + struct CancelledAutomationTask has drop, store { + id: u64 + } + + public(friend) fun task_expiry_time(task: &AutomationTaskMetaData): u64 { + task.expiry_time + } + + // todo : this function should call during initialzation, but since we already done genesis in that case who can access the function + public(friend) fun initialize(supra_framework: &signer, automation_gas_limit: u64) { + system_addresses::assert_supra_framework(supra_framework); + + move_to(supra_framework, AutomationRegistryState { + tasks: enumerable_map::new_map(), + current_index: 0, + gas_committed_for_next_epoch: 0, + automation_gas_limit + }) + } + + public(friend) fun on_new_epoch(epoch_interval_micro: u64) acquires AutomationRegistryState { + let state = borrow_global_mut(@supra_framework); + let ids = enumerable_map::get_map_list(&state.tasks); + + let epoch_interval_secs = epoch_interval_micro / MICROSECS_CONVERSION_FACTOR; + let current_time = timestamp::now_seconds(); + let gas_committed_for_next_epoch = 0; + + // Perform clean up and updation of state + vector::for_each(ids, |id| { + let task = enumerable_map::get_value_mut(&mut state.tasks, id); + + // Tasks that are active during next epoch and are not cancled + // current_time shows the start time of the current new epoch. + if (task.state != CANCELLED && task.expiry_time > (current_time + epoch_interval_secs) ) { + gas_committed_for_next_epoch = gas_committed_for_next_epoch + task.max_gas_amount; + }; + + // Drop or activate task for this current epoch. + if (task.expiry_time <= current_time || task.state == CANCELLED) { + enumerable_map::remove_value(&mut state.tasks, id); + } else { + task.state = ACTIVE; + } + }); + + state.gas_committed_for_next_epoch = gas_committed_for_next_epoch; + } + + /// Registers a new automation task entry. + public(friend) fun register( + owner: &signer, + payload_tx: vector, + expiry_time: u64, + max_gas_amount: u64, + gas_price_cap: u64, + registration_epoch: u64, + tx_hash: vector, + ) acquires AutomationRegistryState { + let registry_data = borrow_global_mut(@supra_framework); + let registration_time = timestamp::now_seconds(); + + assert!(expiry_time > registration_time, EINVALID_EXPIRY_TIME); + assert!(gas_price_cap > 0, EINVALID_GAS_PRICE); + assert!(max_gas_amount > 0, EINVALID_MAX_GAS_AMOUNT); + assert!(vector::length(&tx_hash) == TXN_HASH_LENGTH, EINVALID_TXN_HASH); + + let committed_gas = registry_data.gas_committed_for_next_epoch + max_gas_amount; + assert!(committed_gas < registry_data.automation_gas_limit, EGAS_AMOUNT_UPPER); + registry_data.gas_committed_for_next_epoch = committed_gas; + let task_index = registry_data.current_index; + + let automation_task_metadata = AutomationTaskMetaData { + id: task_index, + owner: signer::address_of(owner), + payload_tx, + expiry_time, + max_gas_amount, + gas_price_cap, + state: PENDING, + registration_epoch, + registration_time, + tx_hash, + }; + + enumerable_map::add_value(&mut registry_data.tasks, task_index, automation_task_metadata); + registry_data.current_index = registry_data.current_index + 1; + event::emit(automation_task_metadata); + } + + /// Cancel Automation task with specified id. + /// Only existing task, which is PENDING or ACTIVE, can be cancled and only by task onwer. + /// If the task is + /// - active, its state is updated to be CANCELLED. + /// - pending, it is removed form the list. + /// - cancelled, an error is reported + /// Committed gas-limit is updated by reducing it with the max-gas-amount of the cancelled task. + public (friend) fun cancel_task(owner: &signer, id: u64): AutomationTaskMetaData acquires AutomationRegistryState { + let state = borrow_global_mut(@supra_framework); + assert!(enumerable_map::contains(&state.tasks, id), EAUTOMATION_TASK_NOT_FOUND); + + let automation_task_metadata = enumerable_map::get_value(&state.tasks, id); + assert!(automation_task_metadata.owner == signer::address_of(owner), EUNAUTHORIZED_TASK_OWNER); + assert!(automation_task_metadata.state != CANCELLED, EALREADY_CANCELLED); + if (automation_task_metadata.state == PENDING) { + enumerable_map::remove_value(&mut state.tasks, id); + } else if (automation_task_metadata.state == ACTIVE) { + automation_task_metadata.state = CANCELLED; + enumerable_map::update_value(&mut state.tasks, id, automation_task_metadata); + }; + + // Adjust the gas committed for the next epoch by subtracting the gas amount of the cancelled task + state.gas_committed_for_next_epoch = state.gas_committed_for_next_epoch - automation_task_metadata.max_gas_amount; + + event::emit(CancelledAutomationTask { id: automation_task_metadata.id }); + automation_task_metadata + } + + /// Update Automation gas limit. + /// If the committed gas amount for the next epoch is greater then the new gas limit, then error is reported. + public (friend) fun update_automation_gas_limit( + supra_framework: &signer, + automation_gas_limit: u64 + ) acquires AutomationRegistryState { + system_addresses::assert_supra_framework(supra_framework); + + let state = borrow_global_mut(@supra_framework); + assert!(state.gas_committed_for_next_epoch < automation_gas_limit, EUNACCEPTABLE_AUTOMATION_GAS_LIMIT); + + state.automation_gas_limit = automation_gas_limit; + + event::emit(UpdateAutomationGasLimit { automation_gas_limit }); + } + + /// List all active automation task ids for the current epoch. + /// Note that the tasks with CANCELLED state are still considered active for the current epoch, + /// as cancellation takes effect in the next epoch only. + public(friend) fun get_active_task_ids(): vector acquires AutomationRegistryState { + let state = borrow_global(@supra_framework); + + let active_task_ids = vector[]; + let ids = enumerable_map::get_map_list(&state.tasks); + + vector::for_each(ids, |id| { + let task = enumerable_map::get_value_ref(&state.tasks, id); + if (task.state != PENDING) { + vector::push_back(&mut active_task_ids, id); + }; + }); + return active_task_ids + } + + /// Retrieves the details of a automation task entry by its ID. + /// Error will be returned if entry with specified ID does not exist. + public (friend) fun get_task_details(id: u64): AutomationTaskMetaData acquires AutomationRegistryState { + let automation_task_metadata = borrow_global(@supra_framework); + assert!(enumerable_map::contains(&automation_task_metadata.tasks, id), EAUTOMATION_TASK_NOT_FOUND); + enumerable_map::get_value(&automation_task_metadata.tasks, id) + } + + /// Checks whether there is an active task in registry with specified input task id. + public(friend) fun has_active_task_with_id(id: u64): bool acquires AutomationRegistryState { + let automation_task_metadata = borrow_global(@supra_framework); + if (enumerable_map::contains(&automation_task_metadata.tasks, id)) { + let value = enumerable_map::get_value_ref(&automation_task_metadata.tasks, id); + value.state != PENDING + } else { + false + } + } + #[test_only] + fun has_task_with_id(id: u64): bool acquires AutomationRegistryState { + let automation_task_metadata = borrow_global(@supra_framework); + enumerable_map::contains(&automation_task_metadata.tasks, id) + } + + /// Returns next task index in registry + public (friend) fun get_next_task_index(): u64 acquires AutomationRegistryState { + let state = borrow_global(@supra_framework); + state.current_index + } + + /// Ge gas committed for next epoch + public(friend) fun get_gas_committed_for_next_epoch(): u64 acquires AutomationRegistryState { + let state = borrow_global(@supra_framework); + state.gas_committed_for_next_epoch + } + + #[test_only] + const PARENT_HASH: vector = x"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"; + #[test_only] + const PAYLOAD: vector = x"0102030405060708090a0b0c0d0e0f0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20101112131415161718191a1b1c1d1e1f20"; + + #[test_only] + fun initialize_registry_state_test(framework: &signer) { + timestamp::set_time_has_started_for_testing(framework); + initialize(framework, 100); + } + + #[test(framework = @supra_framework)] + fun check_automation_gas_limit_success_update(framework: signer) acquires AutomationRegistryState { + initialize_registry_state_test(&framework); + let account = account::create_account_for_test(@0x123456); + register(&account, + PAYLOAD, + 100, + 50, + 20, + 1, + PARENT_HASH, + ); + + // Next epoch gas committed gas is less than the new limit value. + update_automation_gas_limit(&framework, 75); + let state = borrow_global(@supra_framework); + assert!(state.automation_gas_limit == 75, 1); + } + + #[test(framework = @supra_framework)] + #[expected_failure(abort_code = EUNACCEPTABLE_AUTOMATION_GAS_LIMIT, location = Self)] + fun check_automation_gas_limit_failed_update(framework: signer) acquires AutomationRegistryState { + initialize_registry_state_test(&framework); + let account = account::create_account_for_test(@0x123456); + register(&account, + PAYLOAD, + 100, + 50, + 20, + 1, + PARENT_HASH, + ); + + // Next epoch gas committed gas is greater than the new limit value. + update_automation_gas_limit(&framework, 45); + } + + #[test(framework = @supra_framework)] + fun check_task_registration(framework: signer) acquires AutomationRegistryState { + initialize_registry_state_test(&framework); + + let account = account::create_account_for_test(@0x123456); + register(&account, + PAYLOAD, + 100, + 10, + 20, + 1, + PARENT_HASH, + ); + assert!(1 == get_next_task_index(), 1); + assert!(10 == get_gas_committed_for_next_epoch(), 1) + } + + #[test(framework = @supra_framework)] + #[expected_failure(abort_code = EINVALID_EXPIRY_TIME, location = Self)] + fun check_registration_invalid_expiry_time(framework: signer) acquires AutomationRegistryState { + initialize_registry_state_test(&framework); + + timestamp::update_global_time_for_test_secs(50); + let account = account::create_account_for_test(@0x123456); + register(&account, + PAYLOAD, + 25, + 70, + 20, + 1, + PARENT_HASH, + ); + } + + #[test(framework = @supra_framework)] + #[expected_failure(abort_code = EINVALID_GAS_PRICE, location = Self)] + fun check_registration_invalid_gas_price_cap(framework: signer) acquires AutomationRegistryState { + initialize_registry_state_test(&framework); + + let account = account::create_account_for_test(@0x123456); + register(&account, + PAYLOAD, + 25, + 70, + 0, + 1, + PARENT_HASH, + ); + } + + #[test(framework = @supra_framework)] + #[expected_failure(abort_code = EINVALID_MAX_GAS_AMOUNT, location = Self)] + fun check_registration_invalid_max_gas_amount(framework: signer) acquires AutomationRegistryState { + initialize_registry_state_test(&framework); + + let account = account::create_account_for_test(@0x123456); + register(&account, + PAYLOAD, + 25, + 0, + 70, + 1, + PARENT_HASH, + ); + } + + #[test(framework = @supra_framework)] + #[expected_failure(abort_code = EINVALID_TXN_HASH, location = Self)] + fun check_registration_invalid_parent_hash(framework: signer) acquires AutomationRegistryState { + initialize_registry_state_test(&framework); + + let account = account::create_account_for_test(@0x123456); + register(&account, + PAYLOAD, + 25, + 10, + 70, + 1, + vector[0, 1, 2, 3], + ); + } + + + #[test(framework = @supra_framework)] + #[expected_failure(abort_code = EGAS_AMOUNT_UPPER, location = Self)] + fun check_registration_with_overflow_gas_limit(framework: signer) acquires AutomationRegistryState { + initialize_registry_state_test(&framework); + + let account = account::create_account_for_test(@0x123456); + register(&account, + PAYLOAD, + 100, + 70, + 20, + 1, + PARENT_HASH + ); + assert!(1 == get_next_task_index(), 1); + assert!(70 == get_gas_committed_for_next_epoch(), 1); + register(&account, + PAYLOAD, + 100, + 70, + 20, + 1, + PARENT_HASH + ); + } + + #[test(framework = @supra_framework)] + fun check_task_activation_on_new_epoch(framework: signer) acquires AutomationRegistryState { + initialize_registry_state_test(&framework); + + let account = account::create_account_for_test(@0x123456); + register(&account, + PAYLOAD, + 100, + 10, + 20, + 1, + PARENT_HASH + ); + // When moving to next epoch this task will be considered as expired + register(&account, + PAYLOAD, + 25, + 10, + 20, + 1, + PARENT_HASH + ); + register(&account, + PAYLOAD, + 150, + 10, + 20, + 1, + PARENT_HASH + ); + // When moving to next epoch this task will be considered as expired for the updcoming new epoch + register(&account, + PAYLOAD, + 75, + 10, + 20, + 1, + PARENT_HASH + ); + + // No active task and committed gas for the next epoch is total of the all registered tasks + assert!(40 == get_gas_committed_for_next_epoch(), 1); + let active_task_ids = get_active_task_ids(); + assert!(active_task_ids == vector[], 1); + + timestamp::update_global_time_for_test_secs(50); + on_new_epoch(30 * MICROSECS_CONVERSION_FACTOR); + // Committed gas for the next epoch only for 2 tasks 0 and 2, the task 3 will not be active during upcoming epoch + assert!(20 == get_gas_committed_for_next_epoch(), 1); + let active_task_ids = get_active_task_ids(); + // But here task 3 is in the active list as it is still active in this new epoch. + let expected_ids = vector[0, 2, 3]; + vector::for_each(active_task_ids, |id| { + assert!(vector::contains(&expected_ids, &id), 1); + }); + } + + #[test(framework = @supra_framework)] + fun check_task_successful_cancellation(framework: signer) acquires AutomationRegistryState { + initialize_registry_state_test(&framework); + + let account = account::create_account_for_test(@0x123456); + register(&account, + PAYLOAD, + 100, + 10, + 20, + 1, + PARENT_HASH + ); + // When moving to next epoch this task will be considered as expired + register(&account, + PAYLOAD, + 25, + 10, + 20, + 1, + PARENT_HASH + ); + register(&account, + PAYLOAD, + 150, + 10, + 20, + 1, + PARENT_HASH + ); + // When moving to next epoch this task will be considered as expired for the updcoming new epoch + register(&account, + PAYLOAD, + 75, + 10, + 20, + 1, + PARENT_HASH + ); + + timestamp::update_global_time_for_test_secs(50); + on_new_epoch(30 * MICROSECS_CONVERSION_FACTOR); + // Committed gas for the next epoch only for 2 tasks 0 and 2, the task 3 will not be active during upcoming epoch + assert!(20 == get_gas_committed_for_next_epoch(), 1); + let active_task_ids = get_active_task_ids(); + // But here task 3 is in the active list as it is still active in this new epoch. + let expected_ids = vector[0, 2, 3]; + vector::for_each(active_task_ids, |id| { + assert!(vector::contains(&expected_ids, &id), 1); + }); + + // Cancle task 2. The committed gas for the next epoch will be updated, + // but when requested active task it will be still available in the list + cancel_task(&account, 2); + // Task will be still available in the registry but with cancled state + let task_2_details = get_task_details(2); + assert!(task_2_details.state == CANCELLED, 1); + + assert!(10 == get_gas_committed_for_next_epoch(), 1); + let active_task_ids = get_active_task_ids(); + let expected_ids = vector[0, 2, 3]; + vector::for_each(active_task_ids, |id| { + assert!(vector::contains(&expected_ids, &id), 1); + }); + + // Add and cancel the task in the same epoch. Task index will be 4 + assert!(get_next_task_index() == 4, 1); + register(&account, + PAYLOAD, + 75, + 10, + 20, + 1, + PARENT_HASH + ); + cancel_task(&account, 4); + assert!(10 == get_gas_committed_for_next_epoch(), 1); + let active_task_ids = get_active_task_ids(); + let expected_ids = vector[0, 2, 3]; + vector::for_each(active_task_ids, |id| { + assert!(vector::contains(&expected_ids, &id), 1); + }); + // there is no task with index 4 and the next task index will be 5. + assert!(!has_task_with_id(4), 1); + assert!(get_next_task_index() == 5, 1) + } + + #[test(framework = @supra_framework)] + #[expected_failure(abort_code = EAUTOMATION_TASK_NOT_FOUND, location = Self)] + fun check_cancellation_of_non_existing_task(framework: signer) acquires AutomationRegistryState { + initialize_registry_state_test(&framework); + + let account = account::create_account_for_test(@0x123456); + cancel_task(&account, 1); + } + + #[test(framework = @supra_framework)] + #[expected_failure(abort_code = EUNAUTHORIZED_TASK_OWNER, location = Self)] + fun check_unauthorized_cancellation_(framework: signer) acquires AutomationRegistryState { + initialize_registry_state_test(&framework); + + let account1 = account::create_account_for_test(@0x123456); + let account2 = account::create_account_for_test(@0x654321); + register(&account1, + PAYLOAD, + 75, + 10, + 20, + 1, + PARENT_HASH + ); + cancel_task(&account2, 0); + } + + #[test(framework = @supra_framework)] + #[expected_failure(abort_code = EALREADY_CANCELLED, location = Self)] + fun check_cacellation_of_cancelled_task(framework: signer) acquires AutomationRegistryState { + initialize_registry_state_test(&framework); + + let account = account::create_account_for_test(@0x123456); + register(&account, + PAYLOAD, + 100, + 10, + 20, + 1, + PARENT_HASH + ); + timestamp::update_global_time_for_test_secs(50); + on_new_epoch(30 * MICROSECS_CONVERSION_FACTOR); + // Cancel the same task 2 times + cancel_task(&account, 0); + cancel_task(&account, 0); + } +} diff --git a/aptos-move/framework/supra-framework/sources/block.move b/aptos-move/framework/supra-framework/sources/block.move index 1f079012a5d96..8403c9bff6fe8 100644 --- a/aptos-move/framework/supra-framework/sources/block.move +++ b/aptos-move/framework/supra-framework/sources/block.move @@ -6,6 +6,7 @@ module supra_framework::block { use std::option; use aptos_std::table_with_length::{Self, TableWithLength}; use std::option::Option; + use supra_framework::automation_registry_state; use supra_framework::randomness; use supra_framework::account; @@ -235,6 +236,7 @@ module supra_framework::block { randomness::on_new_block(&vm, epoch, round, option::none()); if (timestamp - reconfiguration::last_reconfiguration_time() >= epoch_interval) { reconfiguration::reconfigure(); + automation_registry_state::on_new_epoch(epoch_interval); }; } @@ -264,6 +266,7 @@ module supra_framework::block { if (timestamp - reconfiguration::last_reconfiguration_time() >= epoch_interval) { reconfiguration_with_dkg::try_start(); + automation_registry_state::on_new_epoch(epoch_interval); }; } diff --git a/aptos-move/framework/supra-framework/sources/transaction_context.move b/aptos-move/framework/supra-framework/sources/transaction_context.move index f61370518b38e..0b4c163107f36 100644 --- a/aptos-move/framework/supra-framework/sources/transaction_context.move +++ b/aptos-move/framework/supra-framework/sources/transaction_context.move @@ -132,14 +132,6 @@ module supra_framework::transaction_context { } native fun entry_function_payload_internal(): Option; - /// Returns the original transaction hash calculated on the raw-bytes. - /// This function aborts if called outside of the transaction prologue, execution, or epilogue phases. - public fun txn_app_hash(): vector { - assert!(features::transaction_context_extension_enabled(), error::invalid_state(ETRANSACTION_CONTEXT_EXTENSION_NOT_ENABLED)); - txn_app_hash_internal() - } - native fun txn_app_hash_internal(): vector; - /// Returns the account address of the entry function payload. public fun account_address(payload: &EntryFunctionPayload): address { assert!(features::transaction_context_extension_enabled(), error::invalid_state(ETRANSACTION_CONTEXT_EXTENSION_NOT_ENABLED)); @@ -267,11 +259,4 @@ module supra_framework::transaction_context { // expected to fail with the error code of `invalid_state(E_TRANSACTION_CONTEXT_NOT_AVAILABLE)` let _multisig = multisig_payload(); } - - #[test] - #[expected_failure(abort_code=196609, location = Self)] - fun test_call_txn_app_hash() { - // expected to fail with the error code of `invalid_state(E_TRANSACTION_CONTEXT_NOT_AVAILABLE)` - let _txn_app_hash = txn_app_hash(); - } } diff --git a/aptos-move/framework/supra-stdlib/doc/enumerable_map.md b/aptos-move/framework/supra-stdlib/doc/enumerable_map.md index a7eb88ab35e4d..dedf74812516f 100644 --- a/aptos-move/framework/supra-stdlib/doc/enumerable_map.md +++ b/aptos-move/framework/supra-stdlib/doc/enumerable_map.md @@ -21,6 +21,7 @@ The module includes error handling and a suite of test functions for validation. - [Function `remove_value_bulk`](#0x1_enumerable_map_remove_value_bulk) - [Function `clear`](#0x1_enumerable_map_clear) - [Function `get_value`](#0x1_enumerable_map_get_value) +- [Function `get_value_ref`](#0x1_enumerable_map_get_value_ref) - [Function `get_value_mut`](#0x1_enumerable_map_get_value_mut) - [Function `get_map_list`](#0x1_enumerable_map_get_map_list) - [Function `contains`](#0x1_enumerable_map_contains) @@ -427,6 +428,31 @@ Returns the value of a key that is present in Enumerable Map + + + + +## Function `get_value_ref` + +Returns reference to the value of a key that is present in Enumerable Map + + +
public fun get_value_ref<K: copy, drop, V: copy, drop, store>(map: &enumerable_map::EnumerableMap<K, V>, key: K): &V
+
+ + + +
+Implementation + + +
public fun get_value_ref<K : copy+drop, V : store+drop+copy>(map: & EnumerableMap<K, V>, key: K): &V {
+    &table::borrow(&map.map, key).value
+}
+
+ + +
diff --git a/aptos-move/framework/supra-stdlib/sources/enumerable_map.move b/aptos-move/framework/supra-stdlib/sources/enumerable_map.move index 5c2b968f776b0..dfc4c22d0895a 100644 --- a/aptos-move/framework/supra-stdlib/sources/enumerable_map.move +++ b/aptos-move/framework/supra-stdlib/sources/enumerable_map.move @@ -132,6 +132,12 @@ module supra_std::enumerable_map { table::borrow(&map.map, key).value } + /// Returns reference to the value of a key that is present in Enumerable Map + public fun get_value_ref(map: & EnumerableMap, key: K): &V { + &table::borrow(&map.map, key).value + } + + /// Returns the value of a key that is present in Enumerable Map public fun get_value_mut(map: &mut EnumerableMap, key: K): &mut V { &mut table::borrow_mut(&mut map.map, key).value diff --git a/types/src/transaction/automation.rs b/types/src/transaction/automation.rs index f65ed486c5bd9..2dbbcb5d5e67b 100644 --- a/types/src/transaction/automation.rs +++ b/types/src/transaction/automation.rs @@ -36,13 +36,14 @@ pub struct RegistrationParams { } impl RegistrationParams { - pub fn serialized_args_with_sender(&self, sender: AccountAddress) -> Vec> { + pub fn serialized_args_with_sender_and_parent_hash(&self, sender: AccountAddress, parent_hash: Vec) -> Vec> { serialize_values(&[ MoveValue::Address(sender), MoveValue::vector_u8(bcs::to_bytes(&self.automated_function).unwrap()), MoveValue::U64(self.expiration_timestamp_secs), MoveValue::U64(self.max_gas_amount), - MoveValue::U64(self.gas_price_cap) + MoveValue::U64(self.gas_price_cap), + MoveValue::vector_u8(parent_hash), ]) } } diff --git a/types/src/transaction/user_transaction_context.rs b/types/src/transaction/user_transaction_context.rs index b90c9b79ca0a3..b140f57c0ce86 100644 --- a/types/src/transaction/user_transaction_context.rs +++ b/types/src/transaction/user_transaction_context.rs @@ -56,7 +56,6 @@ pub struct UserTransactionContext { gas_unit_price: u64, chain_id: u8, payload_type_reference: PayloadTypeReferenceContext, - txn_app_hash: Vec, } impl UserTransactionContext { @@ -68,7 +67,6 @@ impl UserTransactionContext { gas_unit_price: u64, chain_id: u8, payload_type_reference: PayloadTypeReferenceContext, - txn_app_hash: Vec, ) -> Self { Self { sender, @@ -78,7 +76,6 @@ impl UserTransactionContext { gas_unit_price, chain_id, payload_type_reference, - txn_app_hash, } } @@ -117,9 +114,6 @@ impl UserTransactionContext { pub fn is_automation_registration(&self) -> bool { self.payload_type_reference.is_automation_registration() } - pub fn txn_app_hash(&self) -> Vec { - self.txn_app_hash.clone() - } } #[derive(Debug, Clone)]