From cdbbbe02a983d11855ba2d8de601e9f29b508fb5 Mon Sep 17 00:00:00 2001 From: nizam-supraoracles Date: Thu, 7 Nov 2024 15:32:17 +0530 Subject: [PATCH 01/14] feat - initial commite for supra automation registry smart contract --- .../sources/automation_registry.move | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 aptos-move/framework/supra-framework/sources/automation_registry.move diff --git a/aptos-move/framework/supra-framework/sources/automation_registry.move b/aptos-move/framework/supra-framework/sources/automation_registry.move new file mode 100644 index 0000000000000..0dc23c7bdddf8 --- /dev/null +++ b/aptos-move/framework/supra-framework/sources/automation_registry.move @@ -0,0 +1,89 @@ +/// Supra Automation Registry +/// +/// This contract is part of the Supra Framework and is designed to manage automated registry entries +module supra_framework::automation_registry { + + use std::signer; + use aptos_std::table; + use supra_framework::event; + use supra_framework::transaction_context; + use supra_framework::system_addresses; + + /// Registry Id not found + const EREGITRY_NOT_FOUND: u64 = 1; + + /// It tracks entries both pending and completed, organized by unique indices. + struct RegistryData has key, store { + /// The current unique index counter for registries. This value increments as new registries are added. + current_index: u64, + /// A collection of registry entries that are pending completion. + pending_registries: table::Table, + /// A collection of registry entries that have been marked as completed or expired. + completed_registries: table::Table, + } + + #[event] + /// `RegistryEntry` represents a single registry item, containing metadata. + struct RegistryEntry has copy, store, drop { + registry_id: u64, + /// The address of the registry owner. + owner: address, + /// The function signature associated with the registry entry. + function_sig: vector, + /// Expiry of the registry entry, represented in either epoch time or a timestamp. + /// todo : duration either epoch or timestamp - needs to be confirm, + expiry: u64, + /// The transaction hash of the request transaction. + tx_hash: vector, + /// A boolean status indicating whether the registry entry processed or not. If it's expired -> false + status: bool + } + + // todo : this function should call during initialzation, but since we already done genesis in that case who can access the function + fun initialize(supra_framework: &signer) { + system_addresses::assert_supra_framework(supra_framework); + move_to(supra_framework, RegistryData { + current_index: 0, + pending_registries: table::new(), + completed_registries: table::new() + }) + } + + /// Registers a new registry entry. + public entry fun register(owner: &signer, function_sig: vector, expiry: u64) acquires RegistryData { + // todo : well formedness check of function_sig + + // todo : pre-paid amount collect from the user + + let registry_data = borrow_global_mut(@supra_framework); + registry_data.current_index = registry_data.current_index + 1; + + let registry_entry = RegistryEntry { + registry_id: registry_data.current_index, + owner: signer::address_of(owner), + function_sig, + expiry, + status: false, + tx_hash: transaction_context::get_transaction_hash() // todo : need to double check is that work or not + }; + + table::add(&mut registry_data.pending_registries, registry_data.current_index, registry_entry); + + event::emit(registry_entry); + } + + #[view] + /// Retrieves the details of a registry 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 `RegistryEntry` details. + public fun get_registry(registry_id: u64): (bool, RegistryEntry) acquires RegistryData { + let registry = borrow_global(@supra_framework); + if (table::contains(®istry.pending_registries, registry_id)) { + (false, *table::borrow(®istry.pending_registries, registry_id)) + } else if (table::contains(®istry.completed_registries, registry_id)) { + (true, *table::borrow(®istry.completed_registries, registry_id)) + } else { + abort EREGITRY_NOT_FOUND + } + } +} From 30b8ee80bfbc547656d10cf0d527f154891341dd Mon Sep 17 00:00:00 2001 From: nizam-supraoracles Date: Thu, 21 Nov 2024 14:45:06 +0530 Subject: [PATCH 02/14] restructe automation registry contract --- .../sources/automation_registry.move | 65 +++++++++---------- 1 file changed, 29 insertions(+), 36 deletions(-) diff --git a/aptos-move/framework/supra-framework/sources/automation_registry.move b/aptos-move/framework/supra-framework/sources/automation_registry.move index 0dc23c7bdddf8..c8582248ff186 100644 --- a/aptos-move/framework/supra-framework/sources/automation_registry.move +++ b/aptos-move/framework/supra-framework/sources/automation_registry.move @@ -1,6 +1,6 @@ /// Supra Automation Registry /// -/// This contract is part of the Supra Framework and is designed to manage automated registry entries +/// This contract is part of the Supra Framework and is designed to manage automated task entries module supra_framework::automation_registry { use std::signer; @@ -16,27 +16,24 @@ module supra_framework::automation_registry { struct RegistryData has key, store { /// The current unique index counter for registries. This value increments as new registries are added. current_index: u64, - /// A collection of registry entries that are pending completion. - pending_registries: table::Table, - /// A collection of registry entries that have been marked as completed or expired. - completed_registries: table::Table, + /// A collection of automation task entries that are active state. + pending_registries: table::Table, } #[event] - /// `RegistryEntry` represents a single registry item, containing metadata. - struct RegistryEntry has copy, store, drop { - registry_id: u64, + /// `AutomationTaskMetaData` represents a single automation task item, containing metadata. + struct AutomationTaskMetaData has copy, store, drop { + id: u64, /// The address of the registry owner. owner: address, /// The function signature associated with the registry entry. - function_sig: vector, + payload_tx: vector, /// Expiry of the registry entry, represented in either epoch time or a timestamp. - /// todo : duration either epoch or timestamp - needs to be confirm, - expiry: u64, + expiry_time: u64, /// The transaction hash of the request transaction. tx_hash: vector, - /// A boolean status indicating whether the registry entry processed or not. If it's expired -> false - status: bool + /// A boolean is_active indicating whether the registry entry processed or not. If it's expired -> false + is_active: bool } // todo : this function should call during initialzation, but since we already done genesis in that case who can access the function @@ -45,45 +42,41 @@ module supra_framework::automation_registry { move_to(supra_framework, RegistryData { current_index: 0, pending_registries: table::new(), - completed_registries: table::new() }) } - /// Registers a new registry entry. - public entry fun register(owner: &signer, function_sig: vector, expiry: u64) acquires RegistryData { - // todo : well formedness check of function_sig - + /// Registers a new automation task entry. + public entry fun register(owner: &signer, payload_tx: vector, expiry_time: u64) acquires RegistryData { + // todo : well formedness check of payload_tx // todo : pre-paid amount collect from the user + // todo : duration/expiry in seconds + // todo : expiry does not go beyond upper cap duration set by admin/governance + // todo : expiry should not be before the start of next epoch let registry_data = borrow_global_mut(@supra_framework); registry_data.current_index = registry_data.current_index + 1; - let registry_entry = RegistryEntry { - registry_id: registry_data.current_index, + let automation_task_metadata = AutomationTaskMetaData { + id: registry_data.current_index, owner: signer::address_of(owner), - function_sig, - expiry, - status: false, + payload_tx, + expiry_time, + is_active: false, tx_hash: transaction_context::get_transaction_hash() // todo : need to double check is that work or not }; - table::add(&mut registry_data.pending_registries, registry_data.current_index, registry_entry); + table::add(&mut registry_data.pending_registries, registry_data.current_index, automation_task_metadata); - event::emit(registry_entry); + event::emit(automation_task_metadata); } #[view] - /// Retrieves the details of a registry entry by its ID. + /// 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 `RegistryEntry` details. - public fun get_registry(registry_id: u64): (bool, RegistryEntry) acquires RegistryData { - let registry = borrow_global(@supra_framework); - if (table::contains(®istry.pending_registries, registry_id)) { - (false, *table::borrow(®istry.pending_registries, registry_id)) - } else if (table::contains(®istry.completed_registries, registry_id)) { - (true, *table::borrow(®istry.completed_registries, registry_id)) - } else { - abort EREGITRY_NOT_FOUND - } + /// and the second element contains the `AutomationTaskMetaData` details. + public fun get_task_details(id: u64): AutomationTaskMetaData acquires RegistryData { + let automation_task_metadata = borrow_global(@supra_framework); + assert!(table::contains(&automation_task_metadata.pending_registries, id), EREGITRY_NOT_FOUND); + *table::borrow(&automation_task_metadata.pending_registries, id) } } From 3c97e47e0789870f18106f11ac7405dffbf70bb0 Mon Sep 17 00:00:00 2001 From: nizam-supraoracles Date: Mon, 25 Nov 2024 18:05:27 +0530 Subject: [PATCH 03/14] registry contract refactor functions and the struct --- .../sources/automation_registry.move | 92 ++++++++++++++++--- 1 file changed, 79 insertions(+), 13 deletions(-) diff --git a/aptos-move/framework/supra-framework/sources/automation_registry.move b/aptos-move/framework/supra-framework/sources/automation_registry.move index c8582248ff186..5ca394066c84d 100644 --- a/aptos-move/framework/supra-framework/sources/automation_registry.move +++ b/aptos-move/framework/supra-framework/sources/automation_registry.move @@ -5,6 +5,10 @@ module supra_framework::automation_registry { use std::signer; use aptos_std::table; + use supra_framework::timestamp; + use supra_framework::reconfiguration; + use supra_framework::account; + use supra_framework::account::SignerCapability; use supra_framework::event; use supra_framework::transaction_context; use supra_framework::system_addresses; @@ -12,48 +16,98 @@ module supra_framework::automation_registry { /// Registry Id not found const EREGITRY_NOT_FOUND: u64 = 1; + /// Default automation gas limit + const DEFAULT_AUTOMATION_GAS_LIMIT: u64 = 1000000; + /// Default upper gas duration in seconds + const DEFAULT_DURATION_UPPER_LIMIT: u64 = 2592000; + /// Registry resource creation seed + const REGISTRY_RESOURCE_SEED: vector = b"supra_framework::automation_registry"; + /// It tracks entries both pending and completed, organized by unique indices. - struct RegistryData has key, store { + struct AutomationRegistry has key, store { /// The current unique index counter for registries. This value increments as new registries are added. current_index: u64, + /// automation gas limit + automation_gas_limit: u64, + /// duration upper limit + duration_upper_limit: u64, + /// it's resource address which is use to deposit user automation fee + registry_fee_address: address, + /// resource account signature capability + registry_fee_address_signer_cap: SignerCapability, /// A collection of automation task entries that are active state. - pending_registries: table::Table, + registries: table::Table, } #[event] /// `AutomationTaskMetaData` represents a single automation task item, containing metadata. struct AutomationTaskMetaData has copy, store, drop { + /// Automation task request id which is unique id: u64, /// The address of the registry owner. owner: address, /// The function signature associated with the registry entry. payload_tx: vector, - /// Expiry of the registry entry, represented in either epoch time or a timestamp. + /// Expiry of the registry entry, represented in a timestamp. expiry_time: u64, /// The transaction hash of the request transaction. tx_hash: vector, - /// A boolean is_active indicating whether the registry entry processed or not. If it's expired -> false + /// Max gas fee of automation task + max_gas: u64, + /// maximum gas price cap for the task + gas_price_cap: u64, + /// registration epoch number + registration_epoch: u64, + /// registration epoch time + registration_time: u64, + /// A boolean is_active indicating whether the registry entry start processing or not is_active: bool } // todo : this function should call during initialzation, but since we already done genesis in that case who can access the function fun initialize(supra_framework: &signer) { system_addresses::assert_supra_framework(supra_framework); - move_to(supra_framework, RegistryData { + + let (registry_fee_resource_signer, registry_fee_address_signer_cap) = account::create_resource_account( + supra_framework, + REGISTRY_RESOURCE_SEED + ); + + move_to(supra_framework, AutomationRegistry { current_index: 0, - pending_registries: table::new(), + automation_gas_limit: DEFAULT_AUTOMATION_GAS_LIMIT, + duration_upper_limit: DEFAULT_DURATION_UPPER_LIMIT, + registry_fee_address: signer::address_of(®istry_fee_resource_signer), + registry_fee_address_signer_cap, + registries: table::new(), }) } + // todo withdraw amount from resource accoun to specified address + fun withdraw() {} + + + public(friend) fun on_new_epoch() { + // todo : should perform clean up and updation of state + } + /// Registers a new automation task entry. - public entry fun register(owner: &signer, payload_tx: vector, expiry_time: u64) acquires RegistryData { + public entry fun register( + owner: &signer, + payload_tx: vector, + expiry_time: u64, + max_gas: u64, + gas_price_cap: u64 + ) acquires AutomationRegistry { // todo : well formedness check of payload_tx // todo : pre-paid amount collect from the user // todo : duration/expiry in seconds // todo : expiry does not go beyond upper cap duration set by admin/governance // todo : expiry should not be before the start of next epoch + // todo : automation_gas_limit check + // todo : gas_price_cap should not below chain minimum - let registry_data = borrow_global_mut(@supra_framework); + let registry_data = borrow_global_mut(@supra_framework); registry_data.current_index = registry_data.current_index + 1; let automation_task_metadata = AutomationTaskMetaData { @@ -61,22 +115,34 @@ module supra_framework::automation_registry { owner: signer::address_of(owner), payload_tx, expiry_time, + max_gas, + gas_price_cap, is_active: false, + registration_epoch: reconfiguration::current_epoch(), + registration_time: timestamp::now_seconds(), tx_hash: transaction_context::get_transaction_hash() // todo : need to double check is that work or not }; - table::add(&mut registry_data.pending_registries, registry_data.current_index, automation_task_metadata); + table::add(&mut registry_data.registries, registry_data.current_index, automation_task_metadata); event::emit(automation_task_metadata); } + #[view] + /// List all the active automation task + public fun get_active_tasks(): vector acquires AutomationRegistry { + let automation_task_metadata = borrow_global(@supra_framework); + // todo : It's depends upto are we using vector or table + vector[] + } + #[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 RegistryData { - let automation_task_metadata = borrow_global(@supra_framework); - assert!(table::contains(&automation_task_metadata.pending_registries, id), EREGITRY_NOT_FOUND); - *table::borrow(&automation_task_metadata.pending_registries, id) + public fun get_task_details(id: u64): AutomationTaskMetaData acquires AutomationRegistry { + let automation_task_metadata = borrow_global(@supra_framework); + assert!(table::contains(&automation_task_metadata.registries, id), EREGITRY_NOT_FOUND); + *table::borrow(&automation_task_metadata.registries, id) } } From c2243b0543e5648c31aff68bef13d29ca0363ff0 Mon Sep 17 00:00:00 2001 From: nizam-supraoracles Date: Tue, 26 Nov 2024 23:37:46 +0530 Subject: [PATCH 04/14] chore - registry sc, commenting correction --- .../sources/automation_registry.move | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/aptos-move/framework/supra-framework/sources/automation_registry.move b/aptos-move/framework/supra-framework/sources/automation_registry.move index 5ca394066c84d..21bd5feb6c653 100644 --- a/aptos-move/framework/supra-framework/sources/automation_registry.move +++ b/aptos-move/framework/supra-framework/sources/automation_registry.move @@ -25,42 +25,42 @@ 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 registries. This value increments as new registries are added. + /// The current unique index counter for registered tasks. This value increments as new tasks are added. current_index: u64, - /// automation gas limit + /// Automation task gas limit automation_gas_limit: u64, - /// duration upper limit + /// Automation task duration upper limit. duration_upper_limit: u64, - /// it's resource address which is use to deposit user automation fee + /// It's resource address which is use to deposit user automation fee registry_fee_address: address, - /// resource account signature capability + /// Resource account signature capability registry_fee_address_signer_cap: SignerCapability, /// A collection of automation task entries that are active state. - registries: table::Table, + tasks: table::Table, } #[event] /// `AutomationTaskMetaData` represents a single automation task item, containing metadata. struct AutomationTaskMetaData has copy, store, drop { - /// Automation task request id which is unique + /// Automation task index in registry id: u64, - /// The address of the registry owner. + /// The address of the task owner. owner: address, /// The function signature associated with the registry entry. payload_tx: vector, - /// Expiry of the registry entry, represented in a timestamp. + /// 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 fee of automation task - max_gas: u64, - /// maximum gas price cap for the task + /// 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 number registration_epoch: u64, - /// registration epoch time + /// Registration epoch time registration_time: u64, - /// A boolean is_active indicating whether the registry entry start processing or not + /// Flag indicating whether the task is active. is_active: bool } @@ -79,7 +79,7 @@ module supra_framework::automation_registry { duration_upper_limit: DEFAULT_DURATION_UPPER_LIMIT, registry_fee_address: signer::address_of(®istry_fee_resource_signer), registry_fee_address_signer_cap, - registries: table::new(), + tasks: table::new(), }) } @@ -96,7 +96,7 @@ module supra_framework::automation_registry { owner: &signer, payload_tx: vector, expiry_time: u64, - max_gas: u64, + max_gas_amount: u64, gas_price_cap: u64 ) acquires AutomationRegistry { // todo : well formedness check of payload_tx @@ -115,7 +115,7 @@ module supra_framework::automation_registry { owner: signer::address_of(owner), payload_tx, expiry_time, - max_gas, + max_gas_amount, gas_price_cap, is_active: false, registration_epoch: reconfiguration::current_epoch(), @@ -123,7 +123,7 @@ module supra_framework::automation_registry { tx_hash: transaction_context::get_transaction_hash() // todo : need to double check is that work or not }; - table::add(&mut registry_data.registries, registry_data.current_index, automation_task_metadata); + table::add(&mut registry_data.tasks, registry_data.current_index, automation_task_metadata); event::emit(automation_task_metadata); } @@ -131,7 +131,7 @@ module supra_framework::automation_registry { #[view] /// List all the active automation task public fun get_active_tasks(): vector acquires AutomationRegistry { - let automation_task_metadata = borrow_global(@supra_framework); + let _automation_task_metadata = borrow_global(@supra_framework); // todo : It's depends upto are we using vector or table vector[] } @@ -142,7 +142,7 @@ module supra_framework::automation_registry { /// 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!(table::contains(&automation_task_metadata.registries, id), EREGITRY_NOT_FOUND); - *table::borrow(&automation_task_metadata.registries, id) + assert!(table::contains(&automation_task_metadata.tasks, id), EREGITRY_NOT_FOUND); + *table::borrow(&automation_task_metadata.tasks, id) } } From e80b25d025f257146e758e566b7f8a0bc1b7b4bb Mon Sep 17 00:00:00 2001 From: nizam-supraoracles Date: Wed, 27 Nov 2024 18:05:28 +0530 Subject: [PATCH 05/14] introduce supra-lib enumerable_map --- .gitignore | 3 + .../sources/supra-lib/enumerable_map.move | 271 ++++++++++++++++++ 2 files changed, 274 insertions(+) create mode 100644 aptos-move/framework/supra-framework/sources/supra-lib/enumerable_map.move diff --git a/.gitignore b/.gitignore index 47156ef12d9ac..ce6ff19906b8b 100644 --- a/.gitignore +++ b/.gitignore @@ -135,3 +135,6 @@ test_indexer_grpc/* *.dot *.bytecode !third_party/move/move-prover/tests/xsources/design/*.bytecode + +*supra_history +**/supra_node_logs \ No newline at end of file diff --git a/aptos-move/framework/supra-framework/sources/supra-lib/enumerable_map.move b/aptos-move/framework/supra-framework/sources/supra-lib/enumerable_map.move new file mode 100644 index 0000000000000..fb8236a32aeb6 --- /dev/null +++ b/aptos-move/framework/supra-framework/sources/supra-lib/enumerable_map.move @@ -0,0 +1,271 @@ +/// This module provides an implementation of an enumerable map, a data structure that maintains key-value pairs with +/// efficient operations for addition, removal, and retrieval. +/// It allows for enumeration of keys in insertion order, bulk operations, and updates while ensuring data consistency. +/// The module includes error handling and a suite of test functions for validation. +module supra_framework::enumerable_map { + use std::error; + use std::vector; + use aptos_std::table; + + /// Key is already present in the map + const EKEY_ALREADY_ADDED: u64 = 1; + /// Key is absent in the map + const EKEY_ABSENT: u64 = 2; + /// Vector is empty + const EVECTOR_EMPTY: u64 = 3; + + /// Enumerable Map to store the key value pairs + struct EnumerableMap has store { + /// List of all keys + list: vector, + /// Key mapped to a tuple containing the (position of key in list and value corresponding to the key) + map: table::Table>, + } + + /// Tuple to store the position of key in list and value corresponding to the key + struct Tuple has store, copy, drop { + position: u64, + value: V, + } + + /// Return type + struct KeyValue has store, copy, drop { + key: K, + value: V, + } + + /// To create an empty enum map + public fun new_map(): EnumerableMap { + return EnumerableMap { list: vector::empty(), map: table::new>() } + } + + /// Add Single Key in the Enumerable Map + public fun add_value(map: &mut EnumerableMap, key: K, value: V) { + assert!(!contains(map, key), error::already_exists(EKEY_ALREADY_ADDED)); + table::add(&mut map.map, key, Tuple { position: vector::length(&map.list), value }); + vector::push_back(&mut map.list, key); + } + + /// Add Multiple Keys in the Enumerable Map + public fun add_value_bulk( + map: &mut EnumerableMap, + keys: vector, + values: vector + ): vector { + assert!(!vector::is_empty(&values), error::invalid_argument(EVECTOR_EMPTY)); + let current_key_list_length = vector::length(&map.list); + let updated_keys = vector::empty(); + + vector::zip_reverse(keys, values, |key, value| { + if (!contains(map, key)) { + table::add(&mut map.map, key, Tuple { position: current_key_list_length, value }); + vector::push_back(&mut map.list, key); + current_key_list_length = current_key_list_length + 1; + + vector::push_back(&mut updated_keys, key); + }; + }); + + return updated_keys + } + + /// Update the value of a key thats already present in the Enumerable Map + public fun update_value( + map: &mut EnumerableMap, + key: K, + new_value: V + ): KeyValue { + assert!(contains(map, key), error::not_found(EKEY_ABSENT)); + table::borrow_mut(&mut map.map, key).value = new_value; + KeyValue { key, value: new_value } + } + + /// Remove single Key from the Enumerable Map + public fun remove_value(map: &mut EnumerableMap, key: K) { + assert!(contains(map, key), error::not_found(EKEY_ABSENT)); + + let map_last_index = vector::length(&map.list) - 1; + let index_of_element = table::borrow(&map.map, key).position; + let tuple_to_modify = table::borrow_mut(&mut map.map, *vector::borrow(&map.list, map_last_index)); + + vector::swap(&mut map.list, index_of_element, map_last_index); + tuple_to_modify.position = index_of_element; + vector::pop_back(&mut map.list); + table::remove(&mut map.map, key); + } + + /// Remove Multiple Keys from the Enumerable Map + public fun remove_value_bulk( + map: &mut EnumerableMap, + keys: vector + ): vector { + assert!(!vector::is_empty(&keys), error::invalid_argument(EVECTOR_EMPTY)); + + let map_length = vector::length(&map.list); + let removed_keys = vector::empty(); + + vector::for_each_reverse(keys, |key| { + if (contains(map, key)) { + let index_of_element = table::borrow(&map.map, key).position; + map_length = map_length - 1; + let tuple_to_modify = table::borrow_mut(&mut map.map, *vector::borrow(&map.list, map_length)); + vector::swap(&mut map.list, index_of_element, map_length); + tuple_to_modify.position = index_of_element; + vector::pop_back(&mut map.list); + table::remove(&mut map.map, key); + + vector::push_back(&mut removed_keys, key); + }; + }); + + return removed_keys + } + + /// Will clear the entire data from the Enumerable Map + public fun clear(map: &mut EnumerableMap) { + let list = get_map_list(map); + remove_value_bulk(map, list); + } + + /// Returns the value of a key that is present in Enumerable Map + public fun get_value(map: & EnumerableMap, key: K): V { + table::borrow(&map.map, key).value + } + + /// Returns the list of keys that the Enumerable Map contains + public fun get_map_list(map: &EnumerableMap): vector { + return map.list + } + + /// Check whether Key is present into the Enumerable map or not + public fun contains(map: &EnumerableMap, key: K): bool { + table::contains(&map.map, key) + } + + /// Return current length of the EnumerableSetRing + public fun length(set: &EnumerableMap): u64 { + return vector::length(&set.list) + } + + #[test_only] + struct EnumerableMapTest has key { + e: EnumerableMap + } + + #[test(owner= @0x1111)] + public fun test_add_value(owner: &signer) { + let enum_map = new_map(); + + add_value(&mut enum_map, 1, 1); + add_value(&mut enum_map, 2, 2); + add_value(&mut enum_map, 3, 3); + add_value(&mut enum_map, 4, 4); + add_value(&mut enum_map, 5, 5); + add_value(&mut enum_map, 6, 6); + + assert!(contains(&enum_map, 3), 1); + assert!(length(&enum_map) == 6, 2); + + move_to(owner, EnumerableMapTest { e: enum_map }) + } + + #[test(owner= @0x1111)] + public fun test_add_value_bulk(owner: &signer) { + let enum_map = new_map(); + + add_value(&mut enum_map, 1, 1); + add_value(&mut enum_map, 2, 2); + add_value(&mut enum_map, 3, 3); + add_value(&mut enum_map, 4, 4); + add_value(&mut enum_map, 5, 5); + add_value(&mut enum_map, 6, 6); + + add_value_bulk(&mut enum_map, vector[7, 8, 9], vector[7, 8, 9]); + + assert!(contains(&enum_map, 8), 1); + assert!(length(&enum_map) == 9, 2); + + move_to(owner, EnumerableMapTest { e: enum_map }); + } + + #[test(owner= @0x1111)] + #[expected_failure(abort_code = 1, location = Self)] + public fun test_remove_value(owner: &signer) { + let enum_map = new_map(); + + add_value(&mut enum_map, 1, 1); + add_value(&mut enum_map, 2, 2); + add_value(&mut enum_map, 3, 3); + add_value(&mut enum_map, 4, 4); + add_value(&mut enum_map, 5, 5); + add_value(&mut enum_map, 6, 6); + + remove_value(&mut enum_map, 1); + remove_value(&mut enum_map, 2); + remove_value(&mut enum_map, 3); + + assert!(contains(&enum_map, 3), 1); + assert!(length(&enum_map) == 3, 2); + + move_to(owner, EnumerableMapTest { e: enum_map }) + } + + #[test(owner= @0x1111)] + #[expected_failure(abort_code = 2, location = Self)] + public fun test_remove_bulk_value(owner: &signer) { + let enum_map = new_map(); + + add_value(&mut enum_map, 1, 1); + add_value(&mut enum_map, 2, 2); + add_value(&mut enum_map, 3, 3); + add_value(&mut enum_map, 4, 4); + add_value(&mut enum_map, 5, 5); + add_value(&mut enum_map, 6, 6); + + remove_value_bulk(&mut enum_map, vector[1, 2, 3]); + + assert!(contains(&enum_map, 4), 1); + assert!(length(&enum_map) == 6, 2); + + move_to(owner, EnumerableMapTest { e: enum_map }) + } + + #[test(owner= @0x1111)] + #[expected_failure(abort_code = 3, location = Self)] + public fun test_update_value(owner: &signer) { + let enum_map = new_map(); + + add_value(&mut enum_map, 1, 1); + add_value(&mut enum_map, 2, 2); + add_value(&mut enum_map, 3, 3); + add_value(&mut enum_map, 4, 4); + add_value(&mut enum_map, 5, 5); + add_value(&mut enum_map, 6, 6); + + update_value(&mut enum_map, 1, 7); + + assert!(contains(&enum_map, 4), 1); + assert!(length(&enum_map) == 6, 2); + assert!(get_value(&enum_map, 1) == 1, 3); + + move_to(owner, EnumerableMapTest { e: enum_map }) + } + + #[test(owner= @0x1111)] + public fun test_clear(owner: &signer) { + let enum_map = new_map(); + + add_value(&mut enum_map, 1, 1); + add_value(&mut enum_map, 2, 2); + add_value(&mut enum_map, 3, 3); + add_value(&mut enum_map, 4, 4); + add_value(&mut enum_map, 5, 5); + add_value(&mut enum_map, 6, 6); + + clear(&mut enum_map); + + assert!(length(&enum_map) == 0, 2); + + move_to(owner, EnumerableMapTest { e: enum_map }) + } +} \ No newline at end of file From 58b70e0ed9cd96e3d006113700bf8c2d1e9c82be Mon Sep 17 00:00:00 2001 From: nizam-supraoracles Date: Wed, 27 Nov 2024 23:52:32 +0530 Subject: [PATCH 06/14] supra automation - replace table with enumerable_map and did related updates --- .../sources/automation_registry.move | 36 ++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/aptos-move/framework/supra-framework/sources/automation_registry.move b/aptos-move/framework/supra-framework/sources/automation_registry.move index 21bd5feb6c653..5c8d88b42792c 100644 --- a/aptos-move/framework/supra-framework/sources/automation_registry.move +++ b/aptos-move/framework/supra-framework/sources/automation_registry.move @@ -4,17 +4,20 @@ module supra_framework::automation_registry { use std::signer; - use aptos_std::table; - use supra_framework::timestamp; - use supra_framework::reconfiguration; + use supra_framework::account; use supra_framework::account::SignerCapability; + use supra_framework::enumerable_map::{Self, EnumerableMap}; use supra_framework::event; - use supra_framework::transaction_context; + use supra_framework::reconfiguration; use supra_framework::system_addresses; + use supra_framework::timestamp; + use supra_framework::transaction_context; /// Registry Id not found const EREGITRY_NOT_FOUND: u64 = 1; + /// Expiry time does not go beyond upper cap duration + const EEXPIRY_TIME_UPPER: u64 = 2; /// Default automation gas limit const DEFAULT_AUTOMATION_GAS_LIMIT: u64 = 1000000; @@ -36,7 +39,7 @@ module supra_framework::automation_registry { /// Resource account signature capability registry_fee_address_signer_cap: SignerCapability, /// A collection of automation task entries that are active state. - tasks: table::Table, + tasks: EnumerableMap, } #[event] @@ -79,7 +82,7 @@ module supra_framework::automation_registry { duration_upper_limit: DEFAULT_DURATION_UPPER_LIMIT, registry_fee_address: signer::address_of(®istry_fee_resource_signer), registry_fee_address_signer_cap, - tasks: table::new(), + tasks: enumerable_map::new_map(), }) } @@ -99,15 +102,17 @@ module supra_framework::automation_registry { max_gas_amount: u64, gas_price_cap: u64 ) acquires AutomationRegistry { + let registry_data = borrow_global_mut(@supra_framework); + // todo : well formedness check of payload_tx // todo : pre-paid amount collect from the user // todo : duration/expiry in seconds - // todo : expiry does not go beyond upper cap duration set by admin/governance + // Expiry time does not go beyond upper cap duration set by admin/governance + assert!(expiry_time < registry_data.duration_upper_limit, EEXPIRY_TIME_UPPER); // todo : expiry should not be before the start of next epoch // todo : automation_gas_limit check // todo : gas_price_cap should not below chain minimum - let registry_data = borrow_global_mut(@supra_framework); registry_data.current_index = registry_data.current_index + 1; let automation_task_metadata = AutomationTaskMetaData { @@ -123,17 +128,16 @@ module supra_framework::automation_registry { tx_hash: transaction_context::get_transaction_hash() // todo : need to double check is that work or not }; - table::add(&mut registry_data.tasks, registry_data.current_index, automation_task_metadata); + enumerable_map::add_value(&mut registry_data.tasks, registry_data.current_index, automation_task_metadata); event::emit(automation_task_metadata); } #[view] - /// List all the active automation task - public fun get_active_tasks(): vector acquires AutomationRegistry { - let _automation_task_metadata = borrow_global(@supra_framework); - // todo : It's depends upto are we using vector or table - vector[] + /// List all the automation task ids + public fun get_active_task_ids(): vector acquires AutomationRegistry { + let automation_registry = borrow_global(@supra_framework); + enumerable_map::get_map_list(&automation_registry.tasks) } #[view] @@ -142,7 +146,7 @@ module supra_framework::automation_registry { /// 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!(table::contains(&automation_task_metadata.tasks, id), EREGITRY_NOT_FOUND); - *table::borrow(&automation_task_metadata.tasks, id) + assert!(enumerable_map::contains(&automation_task_metadata.tasks, id), EREGITRY_NOT_FOUND); + enumerable_map::get_value(&automation_task_metadata.tasks, id) } } From d737095787cf9769697f57e6d462885e8ceab5fb Mon Sep 17 00:00:00 2001 From: nizam-supraoracles Date: Fri, 6 Dec 2024 15:23:42 +0530 Subject: [PATCH 07/14] move enumerable_map file into supra-stdlib, add `gas_committed_for_next_epoch` field, add more assersion in `register` function, also `get_active_task_ids` function will return only active task ids --- .../framework/supra-framework/Move.toml | 1 + .../sources/automation_registry.move | 75 ++++++++++++++----- aptos-move/framework/supra-stdlib/Move.toml | 9 +++ .../sources}/enumerable_map.move | 2 +- 4 files changed, 67 insertions(+), 20 deletions(-) create mode 100644 aptos-move/framework/supra-stdlib/Move.toml rename aptos-move/framework/{supra-framework/sources/supra-lib => supra-stdlib/sources}/enumerable_map.move (99%) diff --git a/aptos-move/framework/supra-framework/Move.toml b/aptos-move/framework/supra-framework/Move.toml index cf6daf68a323e..137a264a0ceab 100644 --- a/aptos-move/framework/supra-framework/Move.toml +++ b/aptos-move/framework/supra-framework/Move.toml @@ -14,3 +14,4 @@ vm_reserved = "0x0" [dependencies] AptosStdlib = { local = "../aptos-stdlib" } MoveStdlib = { local = "../move-stdlib" } +SupraStdlib = { local = "../supra-stdlib" } diff --git a/aptos-move/framework/supra-framework/sources/automation_registry.move b/aptos-move/framework/supra-framework/sources/automation_registry.move index 5c8d88b42792c..04d8a7939f917 100644 --- a/aptos-move/framework/supra-framework/sources/automation_registry.move +++ b/aptos-move/framework/supra-framework/sources/automation_registry.move @@ -4,10 +4,12 @@ module supra_framework::automation_registry { use std::signer; + use std::vector; - use supra_framework::account; - use supra_framework::account::SignerCapability; - use supra_framework::enumerable_map::{Self, EnumerableMap}; + use supra_std::enumerable_map::{Self, EnumerableMap}; + + use supra_framework::account::{Self, SignerCapability}; + use supra_framework::block; use supra_framework::event; use supra_framework::reconfiguration; use supra_framework::system_addresses; @@ -16,13 +18,21 @@ module supra_framework::automation_registry { /// 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; /// Expiry time does not go beyond upper cap duration - const EEXPIRY_TIME_UPPER: u64 = 2; - - /// Default automation gas limit - const DEFAULT_AUTOMATION_GAS_LIMIT: u64 = 1000000; - /// Default upper gas duration in seconds + const EEXPIRY_TIME_UPPER: u64 = 3; + /// 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; + + /// The default automation task gas limit + const DEFAULT_AUTOMATION_GAS_LIMIT: u64 = 100000000; + /// The default upper limit duration for automation task, specified in seconds (30 days). const DEFAULT_DURATION_UPPER_LIMIT: u64 = 2592000; + /// Conversion factor between microseconds and millisecond || millisecond and second + const MILLISECOND_CONVERSION_FACTOR: u64 = 1000; /// Registry resource creation seed const REGISTRY_RESOURCE_SEED: vector = b"supra_framework::automation_registry"; @@ -30,10 +40,12 @@ module supra_framework::automation_registry { 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 task gas limit. todo : updatable automation_gas_limit: u64, - /// Automation task duration upper limit. + /// Automation task duration upper limit. todo : updatable duration_upper_limit: u64, + /// Gas committed for next epoch + gas_committed_for_next_epoch: u64, /// It's resource address which is use to deposit user automation fee registry_fee_address: address, /// Resource account signature capability @@ -80,18 +92,24 @@ module supra_framework::automation_registry { current_index: 0, automation_gas_limit: DEFAULT_AUTOMATION_GAS_LIMIT, duration_upper_limit: DEFAULT_DURATION_UPPER_LIMIT, + gas_committed_for_next_epoch: 0, registry_fee_address: signer::address_of(®istry_fee_resource_signer), registry_fee_address_signer_cap, tasks: enumerable_map::new_map(), }) } - // todo withdraw amount from resource accoun to specified address + // todo withdraw amount from resource account to specified address fun withdraw() {} - public(friend) fun on_new_epoch() { // todo : should perform clean up and updation of state + // sumup gas_committed_for_next_epoch whatever is add or remove + } + + /// Calculate and collect registry charge from user + fun collect_from_owner(_owner: &signer, _expiry_time: u64, _max_gas_amount: u64) { + // todo : calculate and collect pre-paid amount from the user } /// Registers a new automation task entry. @@ -105,14 +123,23 @@ module supra_framework::automation_registry { let registry_data = borrow_global_mut(@supra_framework); // todo : well formedness check of payload_tx - // todo : pre-paid amount collect from the user - // todo : duration/expiry in seconds - // Expiry time does not go beyond upper cap duration set by admin/governance - assert!(expiry_time < registry_data.duration_upper_limit, EEXPIRY_TIME_UPPER); - // todo : expiry should not be before the start of next epoch - // todo : automation_gas_limit check + + let current_time = timestamp::now_seconds(); + assert!(expiry_time > current_time, EINVALID_EXPIRY_TIME); + assert!((expiry_time - current_time) < registry_data.duration_upper_limit, EEXPIRY_TIME_UPPER); + + let epoch_interval = block::get_epoch_interval_secs(); + let last_epoch_time_ms = reconfiguration::last_reconfiguration_time() / MILLISECOND_CONVERSION_FACTOR; + let last_epoch_time = last_epoch_time_ms / MILLISECOND_CONVERSION_FACTOR; + 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); + // todo : gas_price_cap should not below chain minimum + collect_from_owner(owner, expiry_time, max_gas_amount); + registry_data.current_index = registry_data.current_index + 1; let automation_task_metadata = AutomationTaskMetaData { @@ -137,7 +164,17 @@ module supra_framework::automation_registry { /// List all the automation task ids public fun get_active_task_ids(): vector acquires AutomationRegistry { let automation_registry = borrow_global(@supra_framework); - enumerable_map::get_map_list(&automation_registry.tasks) + + 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 } #[view] diff --git a/aptos-move/framework/supra-stdlib/Move.toml b/aptos-move/framework/supra-stdlib/Move.toml new file mode 100644 index 0000000000000..1a2c243004318 --- /dev/null +++ b/aptos-move/framework/supra-stdlib/Move.toml @@ -0,0 +1,9 @@ +[package] +name = "SupraStdlib" +version = "1.0.0" + +[dependencies] +AptosStdlib = { local = "../aptos-stdlib" } + +[addresses] +supra_std = "0x1" diff --git a/aptos-move/framework/supra-framework/sources/supra-lib/enumerable_map.move b/aptos-move/framework/supra-stdlib/sources/enumerable_map.move similarity index 99% rename from aptos-move/framework/supra-framework/sources/supra-lib/enumerable_map.move rename to aptos-move/framework/supra-stdlib/sources/enumerable_map.move index fb8236a32aeb6..5acb7e0e51e8b 100644 --- a/aptos-move/framework/supra-framework/sources/supra-lib/enumerable_map.move +++ b/aptos-move/framework/supra-stdlib/sources/enumerable_map.move @@ -2,7 +2,7 @@ /// efficient operations for addition, removal, and retrieval. /// It allows for enumeration of keys in insertion order, bulk operations, and updates while ensuring data consistency. /// The module includes error handling and a suite of test functions for validation. -module supra_framework::enumerable_map { +module supra_std::enumerable_map { use std::error; use std::vector; use aptos_std::table; From 39bb59f2680ae63b604d7959b4fbf371d81d3905 Mon Sep 17 00:00:00 2001 From: nizam-supraoracles Date: Wed, 11 Dec 2024 14:44:03 +0530 Subject: [PATCH 08/14] automation registry - implement `on_new_epoch`, `update_automation_gas_limit` and `update_duration_upper_limit` --- .../sources/automation_registry.move | 46 +++++++++++++++++-- .../supra-stdlib/sources/enumerable_map.move | 5 ++ 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/aptos-move/framework/supra-framework/sources/automation_registry.move b/aptos-move/framework/supra-framework/sources/automation_registry.move index 04d8a7939f917..d80089fe314a4 100644 --- a/aptos-move/framework/supra-framework/sources/automation_registry.move +++ b/aptos-move/framework/supra-framework/sources/automation_registry.move @@ -40,9 +40,9 @@ module supra_framework::automation_registry { 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. todo : updatable + /// Automation task gas limit. automation_gas_limit: u64, - /// Automation task duration upper limit. todo : updatable + /// Automation task duration upper limit. duration_upper_limit: u64, /// Gas committed for next epoch gas_committed_for_next_epoch: u64, @@ -102,9 +102,45 @@ module supra_framework::automation_registry { // todo withdraw amount from resource account to specified address fun withdraw() {} - public(friend) fun on_new_epoch() { - // todo : should perform clean up and updation of state - // sumup gas_committed_for_next_epoch whatever is add or remove + public(friend) fun on_new_epoch() acquires AutomationRegistry { + let automation_registry = borrow_global_mut(@supra_framework); + let ids = enumerable_map::get_map_list(&automation_registry.tasks); + + let current_time = timestamp::now_seconds(); + + // Perform clean up and updation of state + vector::for_each(ids, |id| { + let task = enumerable_map::get_value_mut(&mut automation_registry.tasks, id); + 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; + } + }); + + // todo : sumup gas_committed_for_next_epoch whatever is add or remove + } + + /// 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(@supra_framework); + automation_registry.automation_gas_limit = automation_gas_limit; + } + + /// Update duration upper limit + public entry fun update_duration_upper_limit( + supra_framework: &signer, + duration_upper_limit: u64 + ) acquires AutomationRegistry { + system_addresses::assert_supra_framework(supra_framework); + + let automation_registry = borrow_global_mut(@supra_framework); + automation_registry.duration_upper_limit = duration_upper_limit; } /// Calculate and collect registry charge from user diff --git a/aptos-move/framework/supra-stdlib/sources/enumerable_map.move b/aptos-move/framework/supra-stdlib/sources/enumerable_map.move index 5acb7e0e51e8b..212711e5085c4 100644 --- a/aptos-move/framework/supra-stdlib/sources/enumerable_map.move +++ b/aptos-move/framework/supra-stdlib/sources/enumerable_map.move @@ -132,6 +132,11 @@ module supra_std::enumerable_map { 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 + } + /// Returns the list of keys that the Enumerable Map contains public fun get_map_list(map: &EnumerableMap): vector { return map.list From af3f11594db4213d8d91ca8214ac4d6e94fe186d Mon Sep 17 00:00:00 2001 From: nizam-supraoracles Date: Wed, 11 Dec 2024 14:46:43 +0530 Subject: [PATCH 09/14] registry function modify as get tx_hash from `txn_app_hash` and `gas_price_cap` should not be zero also collect static registry fee from user --- .../sources/automation_registry.move | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/aptos-move/framework/supra-framework/sources/automation_registry.move b/aptos-move/framework/supra-framework/sources/automation_registry.move index d80089fe314a4..08523d9d22e25 100644 --- a/aptos-move/framework/supra-framework/sources/automation_registry.move +++ b/aptos-move/framework/supra-framework/sources/automation_registry.move @@ -12,6 +12,7 @@ module supra_framework::automation_registry { 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; @@ -26,6 +27,8 @@ module supra_framework::automation_registry { 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; /// The default automation task gas limit const DEFAULT_AUTOMATION_GAS_LIMIT: u64 = 100000000; @@ -144,8 +147,11 @@ module supra_framework::automation_registry { } /// Calculate and collect registry charge from user - fun collect_from_owner(_owner: &signer, _expiry_time: u64, _max_gas_amount: u64) { + fun collect_from_owner(owner: &signer, _expiry_time: u64, _max_gas_amount: u64) { // todo : calculate and collect pre-paid amount from the user + let static_amount = 100000000; // 1 Aptos + let registry_fee_address = get_registry_fee_address(); + supra_account::transfer(owner, registry_fee_address, static_amount); } /// Registers a new automation task entry. @@ -172,7 +178,7 @@ module supra_framework::automation_registry { 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); - // todo : gas_price_cap should not below chain minimum + assert!(gas_price_cap > 0, EINVALID_GAS_PRICE); collect_from_owner(owner, expiry_time, max_gas_amount); @@ -188,7 +194,7 @@ module supra_framework::automation_registry { is_active: false, registration_epoch: reconfiguration::current_epoch(), registration_time: timestamp::now_seconds(), - tx_hash: transaction_context::get_transaction_hash() // todo : need to double check is that work or not + tx_hash: transaction_context::txn_app_hash() }; enumerable_map::add_value(&mut registry_data.tasks, registry_data.current_index, automation_task_metadata); @@ -222,4 +228,9 @@ module supra_framework::automation_registry { assert!(enumerable_map::contains(&automation_task_metadata.tasks, id), EREGITRY_NOT_FOUND); enumerable_map::get_value(&automation_task_metadata.tasks, id) } + + #[view] + public fun get_registry_fee_address(): address { + account::create_resource_address(&@supra_framework, REGISTRY_RESOURCE_SEED) + } } From 060e85317ebe212ff87e1dc4f7e44328db00ac57 Mon Sep 17 00:00:00 2001 From: nizam-supraoracles Date: Wed, 11 Dec 2024 16:05:48 +0530 Subject: [PATCH 10/14] add `test_registry` unit testcase in supra automation registry --- .../sources/automation_registry.move | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/aptos-move/framework/supra-framework/sources/automation_registry.move b/aptos-move/framework/supra-framework/sources/automation_registry.move index 08523d9d22e25..1b49d5941aa12 100644 --- a/aptos-move/framework/supra-framework/sources/automation_registry.move +++ b/aptos-move/framework/supra-framework/sources/automation_registry.move @@ -17,6 +17,13 @@ module supra_framework::automation_registry { use supra_framework::timestamp; use supra_framework::transaction_context; + #[test_only] + use supra_framework::coin; + #[test_only] + use supra_framework::supra_coin::{Self, SupraCoin}; + + 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 @@ -83,7 +90,7 @@ module supra_framework::automation_registry { } // todo : this function should call during initialzation, but since we already done genesis in that case who can access the function - fun initialize(supra_framework: &signer) { + public(friend) fun initialize(supra_framework: &signer) { system_addresses::assert_supra_framework(supra_framework); let (registry_fee_resource_signer, registry_fee_address_signer_cap) = account::create_resource_account( @@ -233,4 +240,31 @@ module supra_framework::automation_registry { public fun get_registry_fee_address(): address { account::create_resource_address(&@supra_framework, REGISTRY_RESOURCE_SEED) } + + #[test_only] + fun initialize_registry_test(supra_framework: &signer, user: &signer) { + let user_addr = signer::address_of(user); + account::create_account_for_test(user_addr); + account::create_account_for_test(@supra_framework); + + let (burn_cap, mint_cap) = supra_coin::initialize_for_test(supra_framework); + coin::register(user); + supra_coin::mint(supra_framework, user_addr, 100000000); + coin::destroy_burn_cap(burn_cap); + coin::destroy_mint_cap(mint_cap); + + block::initialize_for_test(supra_framework, 7200000000); + timestamp::set_time_has_started_for_testing(supra_framework); + reconfiguration::initialize_for_test(supra_framework); + + initialize(supra_framework); + } + + #[test(supra_framework = @supra_framework, user = @0x1cafe)] + 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); + } } From 1414818f6dd77f3cd9adb80f2c04be32e114b126 Mon Sep 17 00:00:00 2001 From: nizam-supraoracles Date: Thu, 12 Dec 2024 15:52:45 +0530 Subject: [PATCH 11/14] automation_registry - implement `withdraw_fee` and `remove_task`, also include missing events --- .../sources/automation_registry.move | 74 ++++++++++++++++++- 1 file changed, 70 insertions(+), 4 deletions(-) diff --git a/aptos-move/framework/supra-framework/sources/automation_registry.move b/aptos-move/framework/supra-framework/sources/automation_registry.move index 1b49d5941aa12..43e738e057343 100644 --- a/aptos-move/framework/supra-framework/sources/automation_registry.move +++ b/aptos-move/framework/supra-framework/sources/automation_registry.move @@ -36,6 +36,10 @@ module supra_framework::automation_registry { 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; /// The default automation task gas limit const DEFAULT_AUTOMATION_GAS_LIMIT: u64 = 100000000; @@ -89,6 +93,31 @@ module supra_framework::automation_registry { is_active: bool } + #[event] + /// Withdraw user's registration fee event + struct FeeWithdrawn has drop, store { + to: address, + amount: u64 + } + + #[event] + /// Update automation gas limit event + struct UpdateAutomationGasLimit has drop, store { + automation_gas_limit: u64 + } + + #[event] + /// Update duration upper limit event + struct UpdateDurationUpperLimit has drop, store { + 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(friend) fun initialize(supra_framework: &signer) { system_addresses::assert_supra_framework(supra_framework); @@ -109,26 +138,42 @@ module supra_framework::automation_registry { }) } - // todo withdraw amount from resource account to specified address - fun withdraw() {} + public(friend) fun on_new_epoch(supra_framework: &signer) acquires AutomationRegistry { + system_addresses::assert_supra_framework(supra_framework); - public(friend) fun on_new_epoch() acquires AutomationRegistry { 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 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); if (task.expiry_time < current_time) { + expired_task_gas = expired_task_gas + task.max_gas_amount; enumerable_map::remove_value(&mut automation_registry.tasks, id); } else if (!task.is_active && task.expiry_time > current_time) { task.is_active = true; } }); - // todo : sumup gas_committed_for_next_epoch whatever is add or remove + // 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 user's deposited fee from the resource account + entry fun withdraw_fee(supra_framework: &signer, to: address, amount: u64) acquires AutomationRegistry { + system_addresses::assert_supra_framework(supra_framework); + + let automation_registry = borrow_global_mut(@supra_framework); + let resource_signer = account::create_signer_with_capability( + &automation_registry.registry_fee_address_signer_cap + ); + + supra_account::transfer(&resource_signer, to, amount); + + event::emit(FeeWithdrawn { to, amount }); } /// Update Automation gas limit @@ -140,6 +185,8 @@ module supra_framework::automation_registry { let automation_registry = borrow_global_mut(@supra_framework); automation_registry.automation_gas_limit = automation_gas_limit; + + event::emit(UpdateAutomationGasLimit { automation_gas_limit }); } /// Update duration upper limit @@ -151,6 +198,8 @@ module supra_framework::automation_registry { let automation_registry = borrow_global_mut(@supra_framework); automation_registry.duration_upper_limit = duration_upper_limit; + + event::emit(UpdateDurationUpperLimit { duration_upper_limit }); } /// Calculate and collect registry charge from user @@ -209,6 +258,23 @@ module supra_framework::automation_registry { event::emit(automation_task_metadata); } + /// Remove Automatioon task entry. + public entry fun remove_task(owner: &signer, registry_id: u64) acquires AutomationRegistry { + 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 == signer::address_of(owner), 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; + + // todo : refund to user + event::emit(RemoveAutomationTask { id: automation_task_metadata.id }); + } + #[view] /// List all the automation task ids public fun get_active_task_ids(): vector acquires AutomationRegistry { From 39a216ab72d5565b799da1634210343950943aff Mon Sep 17 00:00:00 2001 From: nizam-supraoracles Date: Fri, 13 Dec 2024 19:29:50 +0530 Subject: [PATCH 12/14] address PR comments --- .../sources/automation_registry.move | 42 ++++++++++++++----- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/aptos-move/framework/supra-framework/sources/automation_registry.move b/aptos-move/framework/supra-framework/sources/automation_registry.move index 43e738e057343..3349d515cbce0 100644 --- a/aptos-move/framework/supra-framework/sources/automation_registry.move +++ b/aptos-move/framework/supra-framework/sources/automation_registry.move @@ -45,6 +45,8 @@ module supra_framework::automation_registry { const DEFAULT_AUTOMATION_GAS_LIMIT: u64 = 100000000; /// The default upper limit duration for automation task, specified in seconds (30 days). const DEFAULT_DURATION_UPPER_LIMIT: u64 = 2592000; + /// The default Automation unit price for per second, in Quants + const DEFAULT_AUTOMATION_UNIT_PRICE: u64 = 1000; /// Conversion factor between microseconds and millisecond || millisecond and second const MILLISECOND_CONVERSION_FACTOR: u64 = 1000; /// Registry resource creation seed @@ -60,6 +62,8 @@ module supra_framework::automation_registry { duration_upper_limit: u64, /// Gas committed for next epoch gas_committed_for_next_epoch: u64, + /// Automation task unit price per second + automation_unit_price: u64, /// It's resource address which is use to deposit user automation fee registry_fee_address: address, /// Resource account signature capability @@ -132,6 +136,7 @@ module supra_framework::automation_registry { 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(), @@ -145,13 +150,19 @@ module supra_framework::automation_registry { 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); - if (task.expiry_time < current_time) { + + // 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; @@ -162,8 +173,12 @@ module supra_framework::automation_registry { automation_registry.gas_committed_for_next_epoch = automation_registry.gas_committed_for_next_epoch - expired_task_gas; } - /// Withdraw user's deposited fee from the resource account - entry fun withdraw_fee(supra_framework: &signer, to: address, amount: u64) acquires AutomationRegistry { + /// Withdraw accumulated automation task fees from the resource account + entry fun withdraw_automation_task_fees( + supra_framework: &signer, + to: address, + amount: u64 + ) acquires AutomationRegistry { system_addresses::assert_supra_framework(supra_framework); let automation_registry = borrow_global_mut(@supra_framework); @@ -202,12 +217,17 @@ module supra_framework::automation_registry { event::emit(UpdateDurationUpperLimit { duration_upper_limit }); } - /// Calculate and collect registry charge from user - fun collect_from_owner(owner: &signer, _expiry_time: u64, _max_gas_amount: u64) { - // todo : calculate and collect pre-paid amount from the user - let static_amount = 100000000; // 1 Aptos + /// 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) { + // todo : dynamic price calculation is pending let registry_fee_address = get_registry_fee_address(); - supra_account::transfer(owner, registry_fee_address, static_amount); + supra_account::transfer(owner, registry_fee_address, fee); + } + + /// Get last epoch time in second + fun get_last_epoch_time_second(): u64 { + let last_epoch_time_ms = reconfiguration::last_reconfiguration_time() / MILLISECOND_CONVERSION_FACTOR; + last_epoch_time_ms / MILLISECOND_CONVERSION_FACTOR } /// Registers a new automation task entry. @@ -227,8 +247,7 @@ module supra_framework::automation_registry { assert!((expiry_time - current_time) < registry_data.duration_upper_limit, EEXPIRY_TIME_UPPER); let epoch_interval = block::get_epoch_interval_secs(); - let last_epoch_time_ms = reconfiguration::last_reconfiguration_time() / MILLISECOND_CONVERSION_FACTOR; - let last_epoch_time = last_epoch_time_ms / MILLISECOND_CONVERSION_FACTOR; + 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; @@ -236,7 +255,8 @@ module supra_framework::automation_registry { assert!(gas_price_cap > 0, EINVALID_GAS_PRICE); - collect_from_owner(owner, expiry_time, max_gas_amount); + let fee = expiry_time * registry_data.automation_unit_price; + charge_automation_fee_from_user(owner, fee); registry_data.current_index = registry_data.current_index + 1; From 8e5f87485c783bcd767d79cf3711a8000a865f84 Mon Sep 17 00:00:00 2001 From: nizam-supraoracles Date: Tue, 17 Dec 2024 13:32:11 +0530 Subject: [PATCH 13/14] create `refund_automation_task_fee` function and common function `transfer_fee_to_account_internal` --- .../sources/automation_registry.move | 49 +++++++++++++++---- 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/aptos-move/framework/supra-framework/sources/automation_registry.move b/aptos-move/framework/supra-framework/sources/automation_registry.move index 3349d515cbce0..4bed17d08a7ee 100644 --- a/aptos-move/framework/supra-framework/sources/automation_registry.move +++ b/aptos-move/framework/supra-framework/sources/automation_registry.move @@ -99,11 +99,18 @@ module supra_framework::automation_registry { #[event] /// Withdraw user's registration fee event - struct FeeWithdrawn has drop, store { + struct FeeWithdrawnAdmin has drop, store { to: address, amount: u64 } + #[event] + /// Withdraw user's registration fee event + struct RefundFeeUser has drop, store { + user: address, + amount: u64 + } + #[event] /// Update automation gas limit event struct UpdateAutomationGasLimit has drop, store { @@ -162,7 +169,7 @@ module supra_framework::automation_registry { expired_task_gas = expired_task_gas + task.max_gas_amount; }; - if (task.expiry_time < current_time) { + 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; @@ -173,22 +180,24 @@ module supra_framework::automation_registry { 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 + /// 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 ) acquires AutomationRegistry { system_addresses::assert_supra_framework(supra_framework); + transfer_fee_to_account_internal(to, amount); + event::emit(FeeWithdrawnAdmin { to, amount }); + } + /// Transfers the specified fee amount from the resource account to the target account. + fun transfer_fee_to_account_internal(to: address, amount: u64) acquires AutomationRegistry { let automation_registry = borrow_global_mut(@supra_framework); let resource_signer = account::create_signer_with_capability( &automation_registry.registry_fee_address_signer_cap ); - supra_account::transfer(&resource_signer, to, amount); - - event::emit(FeeWithdrawn { to, amount }); } /// Update Automation gas limit @@ -244,7 +253,9 @@ module supra_framework::automation_registry { let current_time = timestamp::now_seconds(); assert!(expiry_time > current_time, EINVALID_EXPIRY_TIME); - assert!((expiry_time - current_time) < 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); let epoch_interval = block::get_epoch_interval_secs(); let last_epoch_time = get_last_epoch_time_second(); @@ -255,7 +266,7 @@ module supra_framework::automation_registry { assert!(gas_price_cap > 0, EINVALID_GAS_PRICE); - let fee = expiry_time * registry_data.automation_unit_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; @@ -280,21 +291,39 @@ module supra_framework::automation_registry { /// Remove Automatioon task entry. public entry fun remove_task(owner: &signer, registry_id: u64) acquires AutomationRegistry { + let user_addr = signer::address_of(owner); + 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 == signer::address_of(owner), EUNAUTHORIZED_TASK_OWNER); + 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; - // todo : refund to user + // 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 }); } + /// 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: 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 refund_amount = expiry_time_duration * automation_unit_price; + transfer_fee_to_account_internal(user, refund_amount); + event::emit(RefundFeeUser { user, amount: refund_amount }); + } + #[view] /// List all the automation task ids public fun get_active_task_ids(): vector acquires AutomationRegistry { From afbbb9209ed6fd08c4d798a2054a9b58425b7904 Mon Sep 17 00:00:00 2001 From: nizam-supraoracles Date: Tue, 17 Dec 2024 13:35:50 +0530 Subject: [PATCH 14/14] create view function `get_gas_committed_for_next_epoch` --- .../supra-framework/sources/automation_registry.move | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/aptos-move/framework/supra-framework/sources/automation_registry.move b/aptos-move/framework/supra-framework/sources/automation_registry.move index 4bed17d08a7ee..52f7d2d4f0c11 100644 --- a/aptos-move/framework/supra-framework/sources/automation_registry.move +++ b/aptos-move/framework/supra-framework/sources/automation_registry.move @@ -352,10 +352,18 @@ module supra_framework::automation_registry { } #[view] + /// Get registry fee resource account address public fun get_registry_fee_address(): address { account::create_resource_address(&@supra_framework, REGISTRY_RESOURCE_SEED) } + #[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 + } + #[test_only] fun initialize_registry_test(supra_framework: &signer, user: &signer) { let user_addr = signer::address_of(user);