From 4b934e230e9cc44cdfdeed8409675699fe485629 Mon Sep 17 00:00:00 2001 From: Aaron Gao Date: Tue, 10 Dec 2024 17:57:16 -0600 Subject: [PATCH 1/6] [aptos-framework] permissioned delegation with rate limiter --- .../framework/aptos-framework/doc/overview.md | 2 + .../doc/permissioned_delegation.md | 276 ++++++++++++------ .../aptos-framework/doc/rate_limiter.md | 181 ++++++++++++ .../aptos-framework/doc/token_bucket.md | 176 +++++++++++ .../account/permissioned_delegation.move | 194 ++++++++++++ .../sources/account/rate_limiter.move | 166 +++++++++++ .../executor/tests/internal_indexer_test.rs | 2 + 7 files changed, 902 insertions(+), 95 deletions(-) create mode 100644 aptos-move/framework/aptos-framework/doc/rate_limiter.md create mode 100644 aptos-move/framework/aptos-framework/doc/token_bucket.md create mode 100644 aptos-move/framework/aptos-framework/sources/account/permissioned_delegation.move create mode 100644 aptos-move/framework/aptos-framework/sources/account/rate_limiter.move diff --git a/aptos-move/framework/aptos-framework/doc/overview.md b/aptos-move/framework/aptos-framework/doc/overview.md index 4e7b3a475209b..d4373122fece2 100644 --- a/aptos-move/framework/aptos-framework/doc/overview.md +++ b/aptos-move/framework/aptos-framework/doc/overview.md @@ -50,12 +50,14 @@ This is the reference documentation of the Aptos framework. - [`0x1::object_code_deployment`](object_code_deployment.md#0x1_object_code_deployment) - [`0x1::optional_aggregator`](optional_aggregator.md#0x1_optional_aggregator) - [`0x1::ordered_map`](ordered_map.md#0x1_ordered_map) +- [`0x1::permissioned_delegation`](permissioned_delegation.md#0x1_permissioned_delegation) - [`0x1::permissioned_signer`](permissioned_signer.md#0x1_permissioned_signer) - [`0x1::primary_fungible_store`](primary_fungible_store.md#0x1_primary_fungible_store) - [`0x1::randomness`](randomness.md#0x1_randomness) - [`0x1::randomness_api_v0_config`](randomness_api_v0_config.md#0x1_randomness_api_v0_config) - [`0x1::randomness_config`](randomness_config.md#0x1_randomness_config) - [`0x1::randomness_config_seqnum`](randomness_config_seqnum.md#0x1_randomness_config_seqnum) +- [`0x1::rate_limiter`](rate_limiter.md#0x1_rate_limiter) - [`0x1::reconfiguration`](reconfiguration.md#0x1_reconfiguration) - [`0x1::reconfiguration_state`](reconfiguration_state.md#0x1_reconfiguration_state) - [`0x1::reconfiguration_with_dkg`](reconfiguration_with_dkg.md#0x1_reconfiguration_with_dkg) diff --git a/aptos-move/framework/aptos-framework/doc/permissioned_delegation.md b/aptos-move/framework/aptos-framework/doc/permissioned_delegation.md index 2edb6975559be..77552ac3267f0 100644 --- a/aptos-move/framework/aptos-framework/doc/permissioned_delegation.md +++ b/aptos-move/framework/aptos-framework/doc/permissioned_delegation.md @@ -5,34 +5,123 @@ -- [Resource `Delegation`](#0x1_permissioned_delegation_Delegation) +- [Enum `AccountDelegation`](#0x1_permissioned_delegation_AccountDelegation) +- [Enum `DelegationKey`](#0x1_permissioned_delegation_DelegationKey) +- [Resource `RegisteredDelegations`](#0x1_permissioned_delegation_RegisteredDelegations) - [Constants](#@Constants_0) +- [Function `fetch_handle`](#0x1_permissioned_delegation_fetch_handle) - [Function `add_permissioned_handle`](#0x1_permissioned_delegation_add_permissioned_handle) - [Function `remove_permissioned_handle`](#0x1_permissioned_delegation_remove_permissioned_handle) - [Function `permissioned_signer_by_key`](#0x1_permissioned_delegation_permissioned_signer_by_key) -- [Function `remove_permissioned_handle_by_delegate`](#0x1_permissioned_delegation_remove_permissioned_handle_by_delegate) - [Function `handle_address_by_key`](#0x1_permissioned_delegation_handle_address_by_key) - [Function `authenticate`](#0x1_permissioned_delegation_authenticate) -- [Function `get_permissioned_signer`](#0x1_permissioned_delegation_get_permissioned_signer) +- [Function `get_storable_permissioned_handle`](#0x1_permissioned_delegation_get_storable_permissioned_handle) -
use 0x1::bcs_stream;
+
use 0x1::auth_data;
+use 0x1::bcs_stream;
 use 0x1::ed25519;
 use 0x1::error;
+use 0x1::option;
 use 0x1::permissioned_signer;
+use 0x1::rate_limiter;
 use 0x1::signer;
 use 0x1::table;
 
- + -## Resource `Delegation` +## Enum `AccountDelegation` -
struct Delegation has key
+
enum AccountDelegation has store
+
+ + + +
+Variants + + +
+V1 + + +
+Fields + + +
+
+handle: permissioned_signer::StorablePermissionedHandle +
+
+ +
+
+rate_limiter: option::Option<rate_limiter::RateLimiter> +
+
+ +
+
+ + +
+ +
+ +
+ + + +## Enum `DelegationKey` + + + +
enum DelegationKey has copy, drop
+
+ + + +
+Variants + + +
+Ed25519PublicKey + + +
+Fields + + +
+
+0: ed25519::UnvalidatedPublicKey +
+
+ +
+
+ + +
+ +
+ +
+ + + +## Resource `RegisteredDelegations` + + + +
struct RegisteredDelegations has key
 
@@ -43,7 +132,7 @@
-handles: table::Table<ed25519::UnvalidatedPublicKey, permissioned_signer::StorablePermissionedHandle> +handle_bundles: table::Table<permissioned_delegation::DelegationKey, permissioned_delegation::AccountDelegation>
@@ -103,13 +192,22 @@ - + -## Function `add_permissioned_handle` + + +
const ERATE_LIMITED: u64 = 6;
+
+ + + + + +## Function `fetch_handle` -
public fun add_permissioned_handle(master: &signer, key: vector<u8>, expiration_time: u64): signer
+
fun fetch_handle(bundle: &mut permissioned_delegation::AccountDelegation, check_rate_limit: bool): &permissioned_signer::StorablePermissionedHandle
 
@@ -118,25 +216,12 @@ Implementation -
public fun add_permissioned_handle(
-    master: &signer,
-    key: vector<u8>,
-    expiration_time: u64,
-): signer acquires Delegation {
-    assert!(!is_permissioned_signer(master), error::permission_denied(ENOT_MASTER_SIGNER));
-    let addr = signer::address_of(master);
-    let pubkey = ed25519::new_unvalidated_public_key_from_bytes(key);
-    if (!exists<Delegation>(addr)) {
-        move_to(master, Delegation {
-            handles: table::new()
-        });
+
inline fun fetch_handle(bundle: &mut AccountDelegation, check_rate_limit: bool): &StorablePermissionedHandle {
+    let token_bucket = &mut bundle.rate_limiter;
+    if (check_rate_limit && token_bucket.is_some()) {
+        assert!(rate_limiter::request(token_bucket.borrow_mut(), 1), std::error::permission_denied(ERATE_LIMITED));
     };
-    let handles = &mut borrow_global_mut<Delegation>(addr).handles;
-    assert!(!table::contains(handles, pubkey), error::already_exists(EHANDLE_EXISTENCE));
-    let handle = permissioned_signer::create_storable_permissioned_handle(master, expiration_time);
-    let permissioned_signer = permissioned_signer::signer_from_storable_permissioned(&handle);
-    table::add(handles, pubkey, handle);
-    permissioned_signer
+    &bundle.handle
 }
 
@@ -144,13 +229,13 @@ - + -## Function `remove_permissioned_handle` +## Function `add_permissioned_handle` -
public fun remove_permissioned_handle(master: &signer, key: vector<u8>)
+
public fun add_permissioned_handle(master: &signer, key: permissioned_delegation::DelegationKey, rate_limiter: option::Option<rate_limiter::RateLimiter>, expiration_time: u64): signer
 
@@ -159,16 +244,25 @@ Implementation -
public fun remove_permissioned_handle(
+
public fun add_permissioned_handle(
     master: &signer,
-    key: vector<u8>,
-) acquires Delegation {
+    key: DelegationKey,
+    rate_limiter: Option<RateLimiter>,
+    expiration_time: u64,
+): signer acquires RegisteredDelegations {
     assert!(!is_permissioned_signer(master), error::permission_denied(ENOT_MASTER_SIGNER));
     let addr = signer::address_of(master);
-    let pubkey = ed25519::new_unvalidated_public_key_from_bytes(key);
-    let handles = &mut borrow_global_mut<Delegation>(addr).handles;
-    assert!(table::contains(handles, pubkey), error::not_found(EHANDLE_EXISTENCE));
-    permissioned_signer::destroy_storable_permissioned_handle(table::remove(handles, pubkey));
+    if (!exists<RegisteredDelegations>(addr)) {
+        move_to(master, RegisteredDelegations {
+            handle_bundles: table::new()
+        });
+    };
+    let handles = &mut borrow_global_mut<RegisteredDelegations>(addr).handle_bundles;
+    assert!(!handles.contains(key), error::already_exists(EHANDLE_EXISTENCE));
+    let handle = permissioned_signer::create_storable_permissioned_handle(master, expiration_time);
+    let permissioned_signer = permissioned_signer::signer_from_storable_permissioned_handle(&handle);
+    handles.add(key, AccountDelegation::V1 { handle, rate_limiter });
+    permissioned_signer
 }
 
@@ -176,13 +270,13 @@ - + -## Function `permissioned_signer_by_key` +## Function `remove_permissioned_handle` -
public fun permissioned_signer_by_key(master: &signer, key: vector<u8>): signer
+
public fun remove_permissioned_handle(master: &signer, key: permissioned_delegation::DelegationKey)
 
@@ -191,14 +285,20 @@ Implementation -
public fun permissioned_signer_by_key(
+
public fun remove_permissioned_handle(
     master: &signer,
-    key: vector<u8>,
-): signer acquires Delegation {
+    key: DelegationKey,
+) acquires RegisteredDelegations {
     assert!(!is_permissioned_signer(master), error::permission_denied(ENOT_MASTER_SIGNER));
     let addr = signer::address_of(master);
-    let pubkey = ed25519::new_unvalidated_public_key_from_bytes(key);
-    get_permissioned_signer(addr, pubkey)
+    let handle_bundles = &mut borrow_global_mut<RegisteredDelegations>(addr).handle_bundles;
+    assert!(handle_bundles.contains(key), error::not_found(EHANDLE_EXISTENCE));
+    let bundle = handle_bundles.remove(key);
+    match (bundle) {
+        AccountDelegation::V1 { handle, rate_limiter: _ } => {
+            permissioned_signer::destroy_storable_permissioned_handle(handle);
+        }
+    };
 }
 
@@ -206,13 +306,13 @@ - + -## Function `remove_permissioned_handle_by_delegate` +## Function `permissioned_signer_by_key` -
public fun remove_permissioned_handle_by_delegate(master: address, signature: vector<u8>): permissioned_signer::StorablePermissionedHandle
+
public fun permissioned_signer_by_key(master: &signer, key: permissioned_delegation::DelegationKey): signer
 
@@ -221,28 +321,14 @@ Implementation -
public fun remove_permissioned_handle_by_delegate(
-    master: address,
-    signature: vector<u8>,
-): StorablePermissionedHandle acquires Delegation {
-    let stream = bcs_stream::new(signature);
-    let public_key = new_unvalidated_public_key_from_bytes(
-        bcs_stream::deserialize_vector<u8>(&mut stream, |x| deserialize_u8(x))
-    );
-    let signature = new_signature_from_bytes(
-        bcs_stream::deserialize_vector<u8>(&mut stream, |x| deserialize_u8(x))
-    );
-    assert!(
-        ed25519::signature_verify_strict(
-            &signature,
-            &public_key,
-            vector[1, 2, 3],
-        ),
-        error::permission_denied(EINVALID_SIGNATURE)
-    );
-    let handles = &mut borrow_global_mut<Delegation>(master).handles;
-    assert!(table::contains(handles, public_key), error::not_found(EHANDLE_EXISTENCE));
-    table::remove(handles, public_key)
+
public fun permissioned_signer_by_key(
+    master: &signer,
+    key: DelegationKey,
+): signer acquires RegisteredDelegations {
+    assert!(!is_permissioned_signer(master), error::permission_denied(ENOT_MASTER_SIGNER));
+    let addr = signer::address_of(master);
+    let handle = get_storable_permissioned_handle(addr, key, false);
+    permissioned_signer::signer_from_storable_permissioned_handle(handle)
 }
 
@@ -256,8 +342,7 @@ -
#[view]
-public fun handle_address_by_key(master: address, key: vector<u8>): address
+
public fun handle_address_by_key(master: address, key: permissioned_delegation::DelegationKey): address
 
@@ -266,11 +351,9 @@ Implementation -
public fun handle_address_by_key(master: address, key: vector<u8>): address acquires Delegation {
-    let pubkey = ed25519::new_unvalidated_public_key_from_bytes(key);
-    let handles = &borrow_global<Delegation>(master).handles;
-    assert!(table::contains(handles, pubkey), error::not_found(EHANDLE_EXISTENCE));
-    permissioned_signer::permission_address(table::borrow(handles, pubkey))
+
public fun handle_address_by_key(master: address, key: DelegationKey): address acquires RegisteredDelegations {
+    let handle = get_storable_permissioned_handle(master, key, false);
+    permissioned_signer::permissions_storage_address(handle)
 }
 
@@ -285,7 +368,7 @@ Authorization function for account abstraction. -
public fun authenticate(account: signer, transaction_hash: vector<u8>, signature: vector<u8>): signer
+
public fun authenticate(account: signer, abstraction_auth_data: auth_data::AbstractionAuthData): signer
 
@@ -296,11 +379,10 @@ Authorization function for account abstraction.
public fun authenticate(
     account: signer,
-    transaction_hash: vector<u8>,
-    signature: vector<u8>
-): signer acquires Delegation {
+    abstraction_auth_data: AbstractionAuthData
+): signer acquires RegisteredDelegations {
     let addr = signer::address_of(&account);
-    let stream = bcs_stream::new(signature);
+    let stream = bcs_stream::new(*auth_data::authenticator(&abstraction_auth_data));
     let public_key = new_unvalidated_public_key_from_bytes(
         bcs_stream::deserialize_vector<u8>(&mut stream, |x| deserialize_u8(x))
     );
@@ -311,11 +393,12 @@ Authorization function for account abstraction.
         ed25519::signature_verify_strict(
             &signature,
             &public_key,
-            transaction_hash,
+            *auth_data::digest(&abstraction_auth_data),
         ),
         error::permission_denied(EINVALID_SIGNATURE)
     );
-    get_permissioned_signer(addr, public_key)
+    let handle = get_storable_permissioned_handle(addr, DelegationKey::Ed25519PublicKey(public_key), true);
+    permissioned_signer::signer_from_storable_permissioned_handle(handle)
 }
 
@@ -323,13 +406,13 @@ Authorization function for account abstraction. - + -## Function `get_permissioned_signer` +## Function `get_storable_permissioned_handle` -
fun get_permissioned_signer(master: address, pubkey: ed25519::UnvalidatedPublicKey): signer
+
fun get_storable_permissioned_handle(master: address, key: permissioned_delegation::DelegationKey, count_rate: bool): &permissioned_signer::StorablePermissionedHandle
 
@@ -338,12 +421,15 @@ Authorization function for account abstraction. Implementation -
inline fun get_permissioned_signer(master: address, pubkey: UnvalidatedPublicKey): signer {
-    if (exists<Delegation>(master)) {
-        let handles = &borrow_global<Delegation>(master).handles;
-        if (table::contains(handles, pubkey)) {
-            let signer = permissioned_signer::signer_from_storable_permissioned(table::borrow(handles, pubkey));
-            signer
+
inline fun get_storable_permissioned_handle(
+    master: address,
+    key: DelegationKey,
+    count_rate: bool
+): &StorablePermissionedHandle {
+    if (exists<RegisteredDelegations>(master)) {
+        let bundles = &mut borrow_global_mut<RegisteredDelegations>(master).handle_bundles;
+        if (bundles.contains(key)) {
+            fetch_handle(bundles.borrow_mut(key), count_rate)
         } else {
             abort error::permission_denied(EINVALID_SIGNATURE)
         }
diff --git a/aptos-move/framework/aptos-framework/doc/rate_limiter.md b/aptos-move/framework/aptos-framework/doc/rate_limiter.md
new file mode 100644
index 0000000000000..a5a09b4b96b38
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/doc/rate_limiter.md
@@ -0,0 +1,181 @@
+
+
+
+# Module `0x1::rate_limiter`
+
+
+
+-  [Enum Resource `RateLimiter`](#0x1_rate_limiter_RateLimiter)
+-  [Function `initialize`](#0x1_rate_limiter_initialize)
+-  [Function `request`](#0x1_rate_limiter_request)
+-  [Function `refill`](#0x1_rate_limiter_refill)
+
+
+
use 0x1::math64;
+use 0x1::timestamp;
+
+ + + + + +## Enum Resource `RateLimiter` + + + +
enum RateLimiter has copy, drop, store, key
+
+ + + +
+Variants + + +
+TokenBucket + + +
+Fields + + +
+
+capacity: u64 +
+
+ +
+
+current_amount: u64 +
+
+ +
+
+refill_interval: u64 +
+
+ +
+
+last_refill_timestamp: u64 +
+
+ +
+
+fractional_accumulated: u64 +
+
+ +
+
+ + +
+ +
+ +
+ + + +## Function `initialize` + + + +
public fun initialize(capacity: u64, refill_interval: u64): rate_limiter::RateLimiter
+
+ + + +
+Implementation + + +
public fun initialize(capacity: u64, refill_interval: u64): RateLimiter {
+    RateLimiter::TokenBucket {
+        capacity,
+        current_amount: capacity, // Start with a full bucket (full capacity of transactions allowed)
+        refill_interval,
+        last_refill_timestamp: timestamp::now_seconds(),
+        fractional_accumulated: 0, // Start with no fractional accumulated
+    }
+}
+
+ + + +
+ + + +## Function `request` + + + +
public fun request(limiter: &mut rate_limiter::RateLimiter, num_token_requested: u64): bool
+
+ + + +
+Implementation + + +
public fun request(limiter: &mut RateLimiter, num_token_requested: u64): bool {
+    refill(limiter);
+    if (limiter.current_amount >= num_token_requested) {
+        limiter.current_amount = limiter.current_amount - num_token_requested;
+        true
+    } else {
+        false
+    }
+}
+
+ + + +
+ + + +## Function `refill` + + + +
fun refill(limiter: &mut rate_limiter::RateLimiter)
+
+ + + +
+Implementation + + +
fun refill(limiter: &mut RateLimiter) {
+    let current_time = timestamp::now_seconds();
+    let time_passed = current_time - limiter.last_refill_timestamp;
+    // Calculate the full tokens that can be added
+    let accumulated_amount = time_passed * limiter.capacity + limiter.fractional_accumulated;
+    let new_tokens = accumulated_amount / limiter.refill_interval;
+    // Calculate the remaining fractional amount.
+
+    if (new_tokens > 0) {
+        limiter.current_amount = math64::min(limiter.current_amount + new_tokens, limiter.capacity);
+        limiter.last_refill_timestamp = current_time;
+    };
+
+    // Update the fractional amount accumulated for the next refill cycle
+    limiter.fractional_accumulated = accumulated_amount % limiter.refill_interval;
+}
+
+ + + +
+ + +[move-book]: https://aptos.dev/move/book/SUMMARY diff --git a/aptos-move/framework/aptos-framework/doc/token_bucket.md b/aptos-move/framework/aptos-framework/doc/token_bucket.md new file mode 100644 index 0000000000000..743721e304652 --- /dev/null +++ b/aptos-move/framework/aptos-framework/doc/token_bucket.md @@ -0,0 +1,176 @@ + + + +# Module `0x1::token_bucket` + + + +- [Resource `Bucket`](#0x1_token_bucket_Bucket) +- [Function `initialize_bucket`](#0x1_token_bucket_initialize_bucket) +- [Function `request`](#0x1_token_bucket_request) +- [Function `refill`](#0x1_token_bucket_refill) + + +
use 0x1::math64;
+use 0x1::timestamp;
+
+ + + + + +## Resource `Bucket` + + + +
struct Bucket has copy, drop, store, key
+
+ + + +
+Fields + + +
+
+capacity: u64 +
+
+ +
+
+tokens: u64 +
+
+ +
+
+refill_rate_per_minute: u64 +
+
+ +
+
+last_refill_timestamp: u64 +
+
+ +
+
+fractional_time_accumulated: u64 +
+
+ +
+
+ + +
+ + + +## Function `initialize_bucket` + + + +
public fun initialize_bucket(capacity: u64): token_bucket::Bucket
+
+ + + +
+Implementation + + +
public fun initialize_bucket(capacity: u64): Bucket {
+    let bucket = Bucket {
+        capacity,
+        tokens: capacity, // Start with a full bucket (full capacity of transactions allowed)
+        refill_rate_per_minute: capacity,
+        last_refill_timestamp: timestamp::now_seconds(),
+        fractional_time_accumulated: 0, // Start with no fractional time accumulated
+    };
+    bucket
+}
+
+ + + +
+ + + +## Function `request` + + + +
public fun request(bucket: &mut token_bucket::Bucket, num_token_requested: u64): bool
+
+ + + +
+Implementation + + +
public fun request(bucket: &mut Bucket, num_token_requested: u64): bool {
+    refill(bucket);
+    if (bucket.tokens >= num_token_requested) {
+        bucket.tokens = bucket.tokens - num_token_requested;
+        true
+    } else {
+        false
+    }
+}
+
+ + + +
+ + + +## Function `refill` + + + +
fun refill(bucket: &mut token_bucket::Bucket)
+
+ + + +
+Implementation + + +
fun refill(bucket: &mut Bucket) {
+    let current_time = timestamp::now_seconds();
+    let time_passed = current_time - bucket.last_refill_timestamp;
+
+    // Total time passed including fractional accumulated time
+    let total_time = time_passed + bucket.fractional_time_accumulated;
+
+    // Calculate the full tokens that can be added
+    let new_tokens = total_time * bucket.refill_rate_per_minute / 60;
+
+    // Calculate the remaining fractional time
+    let remaining_fractional_time = total_time % 60;
+
+    // Refill the bucket with the full tokens
+    if (new_tokens > 0) {
+        bucket.tokens = math64::min(bucket.tokens + new_tokens, bucket.capacity);
+        bucket.last_refill_timestamp = current_time;
+    };
+
+    // Update the fractional time accumulated for the next refill cycle
+    bucket.fractional_time_accumulated = remaining_fractional_time;
+}
+
+ + + +
+ + +[move-book]: https://aptos.dev/move/book/SUMMARY diff --git a/aptos-move/framework/aptos-framework/sources/account/permissioned_delegation.move b/aptos-move/framework/aptos-framework/sources/account/permissioned_delegation.move new file mode 100644 index 0000000000000..09f70e42542c1 --- /dev/null +++ b/aptos-move/framework/aptos-framework/sources/account/permissioned_delegation.move @@ -0,0 +1,194 @@ +module aptos_framework::permissioned_delegation { + use std::error; + use std::option::Option; + use std::signer; + use aptos_std::ed25519; + use aptos_std::ed25519::{new_signature_from_bytes, new_unvalidated_public_key_from_bytes, UnvalidatedPublicKey}; + use aptos_std::table::{Self, Table}; + use aptos_framework::auth_data::{Self, AbstractionAuthData}; + use aptos_framework::bcs_stream::{Self, deserialize_u8}; + use aptos_framework::permissioned_signer::{Self, is_permissioned_signer, StorablePermissionedHandle}; + use aptos_framework::rate_limiter; + use aptos_framework::rate_limiter::RateLimiter; + #[test_only] + use std::bcs; + #[test_only] + use std::option; + + const ENOT_MASTER_SIGNER: u64 = 1; + const EINVALID_PUBLIC_KEY: u64 = 2; + const EPUBLIC_KEY_NOT_FOUND: u64 = 3; + const EINVALID_SIGNATURE: u64 = 4; + const EHANDLE_EXISTENCE: u64 = 5; + const ERATE_LIMITED: u64 = 6; + + enum AccountDelegation has store { + V1 { handle: StorablePermissionedHandle, rate_limiter: Option } + } + + enum DelegationKey has copy, drop { + Ed25519PublicKey(UnvalidatedPublicKey) + } + + struct RegisteredDelegations has key { + handle_bundles: Table + } + + inline fun fetch_handle(bundle: &mut AccountDelegation, check_rate_limit: bool): &StorablePermissionedHandle { + let token_bucket = &mut bundle.rate_limiter; + if (check_rate_limit && token_bucket.is_some()) { + assert!(rate_limiter::request(token_bucket.borrow_mut(), 1), std::error::permission_denied(ERATE_LIMITED)); + }; + &bundle.handle + } + + public fun add_permissioned_handle( + master: &signer, + key: DelegationKey, + rate_limiter: Option, + expiration_time: u64, + ): signer acquires RegisteredDelegations { + assert!(!is_permissioned_signer(master), error::permission_denied(ENOT_MASTER_SIGNER)); + let addr = signer::address_of(master); + if (!exists(addr)) { + move_to(master, RegisteredDelegations { + handle_bundles: table::new() + }); + }; + let handles = &mut borrow_global_mut(addr).handle_bundles; + assert!(!handles.contains(key), error::already_exists(EHANDLE_EXISTENCE)); + let handle = permissioned_signer::create_storable_permissioned_handle(master, expiration_time); + let permissioned_signer = permissioned_signer::signer_from_storable_permissioned_handle(&handle); + handles.add(key, AccountDelegation::V1 { handle, rate_limiter }); + permissioned_signer + } + + public fun remove_permissioned_handle( + master: &signer, + key: DelegationKey, + ) acquires RegisteredDelegations { + assert!(!is_permissioned_signer(master), error::permission_denied(ENOT_MASTER_SIGNER)); + let addr = signer::address_of(master); + let handle_bundles = &mut borrow_global_mut(addr).handle_bundles; + assert!(handle_bundles.contains(key), error::not_found(EHANDLE_EXISTENCE)); + let bundle = handle_bundles.remove(key); + match (bundle) { + AccountDelegation::V1 { handle, rate_limiter: _ } => { + permissioned_signer::destroy_storable_permissioned_handle(handle); + } + }; + } + + public fun permissioned_signer_by_key( + master: &signer, + key: DelegationKey, + ): signer acquires RegisteredDelegations { + assert!(!is_permissioned_signer(master), error::permission_denied(ENOT_MASTER_SIGNER)); + let addr = signer::address_of(master); + let handle = get_storable_permissioned_handle(addr, key, false); + permissioned_signer::signer_from_storable_permissioned_handle(handle) + } + + public fun handle_address_by_key(master: address, key: DelegationKey): address acquires RegisteredDelegations { + let handle = get_storable_permissioned_handle(master, key, false); + permissioned_signer::permissions_storage_address(handle) + } + + /// Authorization function for account abstraction. + public fun authenticate( + account: signer, + abstraction_auth_data: AbstractionAuthData + ): signer acquires RegisteredDelegations { + let addr = signer::address_of(&account); + let stream = bcs_stream::new(*auth_data::authenticator(&abstraction_auth_data)); + let public_key = new_unvalidated_public_key_from_bytes( + bcs_stream::deserialize_vector(&mut stream, |x| deserialize_u8(x)) + ); + let signature = new_signature_from_bytes( + bcs_stream::deserialize_vector(&mut stream, |x| deserialize_u8(x)) + ); + assert!( + ed25519::signature_verify_strict( + &signature, + &public_key, + *auth_data::digest(&abstraction_auth_data), + ), + error::permission_denied(EINVALID_SIGNATURE) + ); + let handle = get_storable_permissioned_handle(addr, DelegationKey::Ed25519PublicKey(public_key), true); + permissioned_signer::signer_from_storable_permissioned_handle(handle) + } + + inline fun get_storable_permissioned_handle( + master: address, + key: DelegationKey, + count_rate: bool + ): &StorablePermissionedHandle { + if (exists(master)) { + let bundles = &mut borrow_global_mut(master).handle_bundles; + if (bundles.contains(key)) { + fetch_handle(bundles.borrow_mut(key), count_rate) + } else { + abort error::permission_denied(EINVALID_SIGNATURE) + } + } else { + abort error::permission_denied(EINVALID_SIGNATURE) + } + } + + #[test_only] + use aptos_std::ed25519::{sign_arbitrary_bytes, generate_keys, validated_public_key_to_bytes, Signature, + public_key_into_unvalidated + }; + #[test_only] + use aptos_framework::account::create_signer_for_test; + #[test_only] + use aptos_framework::timestamp; + + #[test_only] + struct SignatureBundle has drop { + pubkey: UnvalidatedPublicKey, + signature: Signature, + } + + #[test(account = @0xcafe, account_copy = @0xcafe)] + fun test_basics(account: signer, account_copy: signer) acquires RegisteredDelegations { + let aptos_framework = create_signer_for_test(@aptos_framework); + timestamp::set_time_has_started_for_testing(&aptos_framework); + let (sk, vpk) = generate_keys(); + let signature = sign_arbitrary_bytes(&sk, vector[1, 2, 3]); + let pubkey_bytes = validated_public_key_to_bytes(&vpk); + let key = DelegationKey::Ed25519PublicKey(public_key_into_unvalidated(vpk)); + let sig_bundle = SignatureBundle { + pubkey: new_unvalidated_public_key_from_bytes(pubkey_bytes), + signature, + }; + let auth_data = auth_data::create_auth_data(vector[1, 2, 3], bcs::to_bytes(&sig_bundle)); + assert!(!is_permissioned_signer(&account), 1); + add_permissioned_handle(&account, key, option::none(), 60); + let permissioned_signer = authenticate(account, auth_data); + assert!(is_permissioned_signer(&permissioned_signer), 2); + remove_permissioned_handle(&account_copy, key); + } + + #[test(account = @0xcafe, account_copy = @0xcafe, account_copy_2 = @0xcafe)] + #[expected_failure(abort_code = 0x50006, location = Self)] + fun test_rate_limit(account: signer, account_copy: signer, account_copy_2: signer) acquires RegisteredDelegations { + let aptos_framework = create_signer_for_test(@aptos_framework); + timestamp::set_time_has_started_for_testing(&aptos_framework); + let (sk, vpk) = generate_keys(); + let signature = sign_arbitrary_bytes(&sk, vector[1, 2, 3]); + let pubkey_bytes = validated_public_key_to_bytes(&vpk); + let key = DelegationKey::Ed25519PublicKey(public_key_into_unvalidated(vpk)); + let sig_bundle = SignatureBundle { + pubkey: new_unvalidated_public_key_from_bytes(pubkey_bytes), + signature, + }; + let auth_data = auth_data::create_auth_data(vector[1, 2, 3], bcs::to_bytes(&sig_bundle)); + assert!(!is_permissioned_signer(&account), 1); + add_permissioned_handle(&account, key, option::none(), 60); + authenticate(account, auth_data); + authenticate(account_copy, auth_data); + remove_permissioned_handle(&account_copy_2, key); + } +} diff --git a/aptos-move/framework/aptos-framework/sources/account/rate_limiter.move b/aptos-move/framework/aptos-framework/sources/account/rate_limiter.move new file mode 100644 index 0000000000000..253d25fbfba85 --- /dev/null +++ b/aptos-move/framework/aptos-framework/sources/account/rate_limiter.move @@ -0,0 +1,166 @@ +module aptos_framework::rate_limiter { + use aptos_std::math64; + use aptos_framework::timestamp; + + enum RateLimiter has key, store, copy, drop { + // Struct to represent a Token Bucket that refills every minute + TokenBucket { + // Maximum number of tokens allowed at any time. + capacity: u64, + // Current number of tokens remaining in this interval. + current_amount: u64, + // refill `capacity` number of tokens every `refill_interval` in seconds. + refill_interval: u64, + // Last time the bucket was refilled (in seconds) + last_refill_timestamp: u64, + // accumulated amount that hasn't yet added up to a full token + fractional_accumulated: u64, + } + } + + // Public entry function to initialize a Token Bucket based rate limiter. + public fun initialize(capacity: u64, refill_interval: u64): RateLimiter { + RateLimiter::TokenBucket { + capacity, + current_amount: capacity, // Start with a full bucket (full capacity of transactions allowed) + refill_interval, + last_refill_timestamp: timestamp::now_seconds(), + fractional_accumulated: 0, // Start with no fractional accumulated + } + } + + // Public function to request a transaction from the bucket + public fun request(limiter: &mut RateLimiter, num_token_requested: u64): bool { + refill(limiter); + if (limiter.current_amount >= num_token_requested) { + limiter.current_amount = limiter.current_amount - num_token_requested; + true + } else { + false + } + } + + // Function to refill the transactions in the bucket based on time passed + fun refill(limiter: &mut RateLimiter) { + let current_time = timestamp::now_seconds(); + let time_passed = current_time - limiter.last_refill_timestamp; + // Calculate the full tokens that can be added + let accumulated_amount = time_passed * limiter.capacity + limiter.fractional_accumulated; + let new_tokens = accumulated_amount / limiter.refill_interval; + // Calculate the remaining fractional amount. + + if (new_tokens > 0) { + limiter.current_amount = math64::min(limiter.current_amount + new_tokens, limiter.capacity); + limiter.last_refill_timestamp = current_time; + }; + + // Update the fractional amount accumulated for the next refill cycle + limiter.fractional_accumulated = accumulated_amount % limiter.refill_interval; + } + + #[test(aptos_framework = @0x1)] + fun test_initialize_bucket(aptos_framework: &signer) { + timestamp::set_time_has_started_for_testing(aptos_framework); + let bucket = initialize(10, 60); + assert!(bucket.capacity == 10, 100); + assert!(bucket.current_amount == 10, 101); + assert!(bucket.refill_interval == 60, 102); + } + + #[test(aptos_framework = @0x1)] + fun test_request_success(aptos_framework: &signer) { + timestamp::set_time_has_started_for_testing(aptos_framework); + let bucket = initialize(10, 30); + let success = request(&mut bucket, 5); + assert!(success, 200); // Should succeed since 5 <= 10 + assert!(bucket.current_amount == 5, 201); // Remaining tokens should be 5 + } + + #[test(aptos_framework = @0x1)] + fun test_request_failure(aptos_framework: &signer) { + timestamp::set_time_has_started_for_testing(aptos_framework); + let bucket = initialize(10, 30); + let success = request(&mut bucket, 15); + assert!(!success, 300); // Should fail since 15 > 10 + assert!(bucket.current_amount == 10, 301); // Tokens should remain unchanged + } + + #[test(aptos_framework = @0x1)] + fun test_refill(aptos_framework: &signer) { + timestamp::set_time_has_started_for_testing(aptos_framework); + let bucket = initialize(10, 60); + + // Simulate a passage of 31 seconds + timestamp::update_global_time_for_test_secs(timestamp::now_seconds() + 31); + + // Refill the bucket + refill(&mut bucket); + + // Should have refilled 5 tokens (half of the capacity) + assert!(bucket.current_amount == 10, 400); // Bucket was already full, so should remain full + + // Request 5 tokens + let success = request(&mut bucket, 5); + assert!(success, 401); // Request should succeed + assert!(bucket.current_amount == 5, 402); // Remaining tokens should be 5 + + // Simulate another passage of 23 seconds + timestamp::update_global_time_for_test_secs(timestamp::now_seconds() + 23); + + // Refill again + refill(&mut bucket); + + // Should refill 4 tokens + assert!(bucket.current_amount == 9, 403); // Should now be full again + } + + #[test(aptos_framework= @0x1)] + fun test_fractional_accumulation(aptos_framework: &signer) { + timestamp::set_time_has_started_for_testing(aptos_framework); + let bucket = initialize(10, 60); + assert!(request(&mut bucket, 10), 1); // Request should succeed + + // Simulate 10 seconds passing + timestamp::update_global_time_for_test_secs(timestamp::now_seconds() + 10); + + // Refill the bucket + refill(&mut bucket); + // Should add 1/6th of the tokens (because 10 seconds is 1/6th of a minute) + assert!(bucket.current_amount == 6, 500); // No token will be added since it rounds down + assert!(bucket.fractional_accumulated == 40, 501); // Accumulate the 4 seconds of fractional amount + + // Simulate another 50 seconds passing (total 60 seconds) + timestamp::update_global_time_for_test_secs(timestamp::now_seconds() + 50); + + // Refill the bucket again + refill(&mut bucket); + + assert!(bucket.current_amount == 10, 502); // Should be full now + assert!(bucket.fractional_accumulated == 0, 503); // Fractional time should reset + } + + #[test(aptos_framework= @0x1)] + fun test_multiple_refills(aptos_framework: &signer) { + timestamp::set_time_has_started_for_testing(aptos_framework); + let bucket = initialize(10, 60); + + // Request 8 tokens + let success = request(&mut bucket, 8); + assert!(success, 600); // Should succeed + assert!(bucket.current_amount == 2, 601); // Remaining tokens should be 2 + + // Simulate a passage of 30 seconds + timestamp::update_global_time_for_test_secs(timestamp::now_seconds() + 30); + + // Refill the bucket + refill(&mut bucket); + assert!(bucket.current_amount == 7, 602); // Should add 5 tokens (half of the refill rate) + + // Simulate another 30 seconds + timestamp::update_global_time_for_test_secs(timestamp::now_seconds() + 30); + + // Refill the bucket again + refill(&mut bucket); + assert!(bucket.current_amount == 10, 603); // Should be full again + } +} diff --git a/execution/executor/tests/internal_indexer_test.rs b/execution/executor/tests/internal_indexer_test.rs index b80161c391cca..a0ff25cef9ce5 100644 --- a/execution/executor/tests/internal_indexer_test.rs +++ b/execution/executor/tests/internal_indexer_test.rs @@ -260,6 +260,7 @@ fn test_db_indexer_data() { ident_str!("gas_schedule"), ident_str!("managed_coin"), ident_str!("math_fixed64"), + ident_str!("rate_limiter"), ident_str!("ristretto255"), ident_str!("smart_vector"), ident_str!("string_utils"), @@ -306,6 +307,7 @@ fn test_db_indexer_data() { ident_str!("object_code_deployment"), ident_str!("primary_fungible_store"), ident_str!("transaction_validation"), + ident_str!("permissioned_delegation"), ident_str!("storage_slots_allocator"), ident_str!("randomness_api_v0_config"), ident_str!("randomness_config_seqnum"), From afa445c5f0e4dcc9b5e180fa42adff7a1e07a1cf Mon Sep 17 00:00:00 2001 From: Aaron Gao Date: Wed, 15 Jan 2025 21:56:03 +0800 Subject: [PATCH 2/6] fixup! [aptos-framework] permissioned delegation with rate limiter --- .../doc/permissioned_delegation.md | 24 +++++++++---------- .../aptos-framework/doc/rate_limiter.md | 8 ++----- .../account/permissioned_delegation.move | 24 +++++++++---------- .../sources/account/rate_limiter.move | 17 ++++++------- 4 files changed, 35 insertions(+), 38 deletions(-) diff --git a/aptos-move/framework/aptos-framework/doc/permissioned_delegation.md b/aptos-move/framework/aptos-framework/doc/permissioned_delegation.md index 77552ac3267f0..4663691d80d7c 100644 --- a/aptos-move/framework/aptos-framework/doc/permissioned_delegation.md +++ b/aptos-move/framework/aptos-framework/doc/permissioned_delegation.md @@ -20,13 +20,13 @@
use 0x1::auth_data;
 use 0x1::bcs_stream;
+use 0x1::big_ordered_map;
 use 0x1::ed25519;
 use 0x1::error;
 use 0x1::option;
 use 0x1::permissioned_signer;
 use 0x1::rate_limiter;
 use 0x1::signer;
-use 0x1::table;
 
@@ -82,7 +82,7 @@ -
enum DelegationKey has copy, drop
+
enum DelegationKey has copy, drop, store
 
@@ -132,7 +132,7 @@
-handle_bundles: table::Table<permissioned_delegation::DelegationKey, permissioned_delegation::AccountDelegation> +delegations: big_ordered_map::BigOrderedMap<permissioned_delegation::DelegationKey, permissioned_delegation::AccountDelegation>
@@ -254,11 +254,11 @@ let addr = signer::address_of(master); if (!exists<RegisteredDelegations>(addr)) { move_to(master, RegisteredDelegations { - handle_bundles: table::new() + delegations: big_ordered_map::new_with_config(50, 20, true, 5) }); }; - let handles = &mut borrow_global_mut<RegisteredDelegations>(addr).handle_bundles; - assert!(!handles.contains(key), error::already_exists(EHANDLE_EXISTENCE)); + let handles = &mut borrow_global_mut<RegisteredDelegations>(addr).delegations; + assert!(!handles.contains(&key), error::already_exists(EHANDLE_EXISTENCE)); let handle = permissioned_signer::create_storable_permissioned_handle(master, expiration_time); let permissioned_signer = permissioned_signer::signer_from_storable_permissioned_handle(&handle); handles.add(key, AccountDelegation::V1 { handle, rate_limiter }); @@ -291,9 +291,9 @@ ) acquires RegisteredDelegations { assert!(!is_permissioned_signer(master), error::permission_denied(ENOT_MASTER_SIGNER)); let addr = signer::address_of(master); - let handle_bundles = &mut borrow_global_mut<RegisteredDelegations>(addr).handle_bundles; - assert!(handle_bundles.contains(key), error::not_found(EHANDLE_EXISTENCE)); - let bundle = handle_bundles.remove(key); + let handle_bundles = &mut borrow_global_mut<RegisteredDelegations>(addr).delegations; + assert!(handle_bundles.contains(&key), error::not_found(EHANDLE_EXISTENCE)); + let bundle = handle_bundles.remove(&key); match (bundle) { AccountDelegation::V1 { handle, rate_limiter: _ } => { permissioned_signer::destroy_storable_permissioned_handle(handle); @@ -427,9 +427,9 @@ Authorization function for account abstraction. count_rate: bool ): &StorablePermissionedHandle { if (exists<RegisteredDelegations>(master)) { - let bundles = &mut borrow_global_mut<RegisteredDelegations>(master).handle_bundles; - if (bundles.contains(key)) { - fetch_handle(bundles.borrow_mut(key), count_rate) + let bundles = &mut borrow_global_mut<RegisteredDelegations>(master).delegations; + if (bundles.contains(&key)) { + fetch_handle(bundles.borrow_mut(&key), count_rate) } else { abort error::permission_denied(EINVALID_SIGNATURE) } diff --git a/aptos-move/framework/aptos-framework/doc/rate_limiter.md b/aptos-move/framework/aptos-framework/doc/rate_limiter.md index a5a09b4b96b38..95d64ff629bb3 100644 --- a/aptos-move/framework/aptos-framework/doc/rate_limiter.md +++ b/aptos-move/framework/aptos-framework/doc/rate_limiter.md @@ -161,12 +161,8 @@ // Calculate the full tokens that can be added let accumulated_amount = time_passed * limiter.capacity + limiter.fractional_accumulated; let new_tokens = accumulated_amount / limiter.refill_interval; - // Calculate the remaining fractional amount. - - if (new_tokens > 0) { - limiter.current_amount = math64::min(limiter.current_amount + new_tokens, limiter.capacity); - limiter.last_refill_timestamp = current_time; - }; + limiter.current_amount = math64::min(limiter.current_amount + new_tokens, limiter.capacity); + limiter.last_refill_timestamp = current_time; // Update the fractional amount accumulated for the next refill cycle limiter.fractional_accumulated = accumulated_amount % limiter.refill_interval; diff --git a/aptos-move/framework/aptos-framework/sources/account/permissioned_delegation.move b/aptos-move/framework/aptos-framework/sources/account/permissioned_delegation.move index 09f70e42542c1..a31b105795f9c 100644 --- a/aptos-move/framework/aptos-framework/sources/account/permissioned_delegation.move +++ b/aptos-move/framework/aptos-framework/sources/account/permissioned_delegation.move @@ -4,7 +4,7 @@ module aptos_framework::permissioned_delegation { use std::signer; use aptos_std::ed25519; use aptos_std::ed25519::{new_signature_from_bytes, new_unvalidated_public_key_from_bytes, UnvalidatedPublicKey}; - use aptos_std::table::{Self, Table}; + use aptos_std::big_ordered_map::{Self, BigOrderedMap}; use aptos_framework::auth_data::{Self, AbstractionAuthData}; use aptos_framework::bcs_stream::{Self, deserialize_u8}; use aptos_framework::permissioned_signer::{Self, is_permissioned_signer, StorablePermissionedHandle}; @@ -26,12 +26,12 @@ module aptos_framework::permissioned_delegation { V1 { handle: StorablePermissionedHandle, rate_limiter: Option } } - enum DelegationKey has copy, drop { + enum DelegationKey has copy, store, drop { Ed25519PublicKey(UnvalidatedPublicKey) } struct RegisteredDelegations has key { - handle_bundles: Table + delegations: BigOrderedMap } inline fun fetch_handle(bundle: &mut AccountDelegation, check_rate_limit: bool): &StorablePermissionedHandle { @@ -52,11 +52,11 @@ module aptos_framework::permissioned_delegation { let addr = signer::address_of(master); if (!exists(addr)) { move_to(master, RegisteredDelegations { - handle_bundles: table::new() + delegations: big_ordered_map::new_with_config(50, 20, true, 5) }); }; - let handles = &mut borrow_global_mut(addr).handle_bundles; - assert!(!handles.contains(key), error::already_exists(EHANDLE_EXISTENCE)); + let handles = &mut borrow_global_mut(addr).delegations; + assert!(!handles.contains(&key), error::already_exists(EHANDLE_EXISTENCE)); let handle = permissioned_signer::create_storable_permissioned_handle(master, expiration_time); let permissioned_signer = permissioned_signer::signer_from_storable_permissioned_handle(&handle); handles.add(key, AccountDelegation::V1 { handle, rate_limiter }); @@ -69,9 +69,9 @@ module aptos_framework::permissioned_delegation { ) acquires RegisteredDelegations { assert!(!is_permissioned_signer(master), error::permission_denied(ENOT_MASTER_SIGNER)); let addr = signer::address_of(master); - let handle_bundles = &mut borrow_global_mut(addr).handle_bundles; - assert!(handle_bundles.contains(key), error::not_found(EHANDLE_EXISTENCE)); - let bundle = handle_bundles.remove(key); + let handle_bundles = &mut borrow_global_mut(addr).delegations; + assert!(handle_bundles.contains(&key), error::not_found(EHANDLE_EXISTENCE)); + let bundle = handle_bundles.remove(&key); match (bundle) { AccountDelegation::V1 { handle, rate_limiter: _ } => { permissioned_signer::destroy_storable_permissioned_handle(handle); @@ -125,9 +125,9 @@ module aptos_framework::permissioned_delegation { count_rate: bool ): &StorablePermissionedHandle { if (exists(master)) { - let bundles = &mut borrow_global_mut(master).handle_bundles; - if (bundles.contains(key)) { - fetch_handle(bundles.borrow_mut(key), count_rate) + let bundles = &mut borrow_global_mut(master).delegations; + if (bundles.contains(&key)) { + fetch_handle(bundles.borrow_mut(&key), count_rate) } else { abort error::permission_denied(EINVALID_SIGNATURE) } diff --git a/aptos-move/framework/aptos-framework/sources/account/rate_limiter.move b/aptos-move/framework/aptos-framework/sources/account/rate_limiter.move index 253d25fbfba85..8ee837e6dc182 100644 --- a/aptos-move/framework/aptos-framework/sources/account/rate_limiter.move +++ b/aptos-move/framework/aptos-framework/sources/account/rate_limiter.move @@ -47,15 +47,16 @@ module aptos_framework::rate_limiter { // Calculate the full tokens that can be added let accumulated_amount = time_passed * limiter.capacity + limiter.fractional_accumulated; let new_tokens = accumulated_amount / limiter.refill_interval; - // Calculate the remaining fractional amount. - - if (new_tokens > 0) { - limiter.current_amount = math64::min(limiter.current_amount + new_tokens, limiter.capacity); - limiter.last_refill_timestamp = current_time; + limiter.current_amount = limiter.current_amount + new_tokens; + if (limiter.current_amount + new_tokens >= limiter.capacity) { + limiter.current_amount = limiter.capacity; + limiter.fractional_accumulated = 0; + } else { + limiter.current_amount = limiter.current_amount + new_tokens; + // Update the fractional amount accumulated for the next refill cycle + limiter.fractional_accumulated = accumulated_amount % limiter.refill_interval; }; - - // Update the fractional amount accumulated for the next refill cycle - limiter.fractional_accumulated = accumulated_amount % limiter.refill_interval; + limiter.last_refill_timestamp = current_time; } #[test(aptos_framework = @0x1)] From 74dd01be64c7f597ed05670766ce681edec1a66c Mon Sep 17 00:00:00 2001 From: Igor Date: Thu, 16 Jan 2025 17:39:21 -0800 Subject: [PATCH 3/6] doc files update --- .../doc/permissioned_delegation.md | 8 ++++---- .../aptos-framework/doc/rate_limiter.md | 16 ++++++++++------ .../sources/account/rate_limiter.move | 1 - 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/aptos-move/framework/aptos-framework/doc/permissioned_delegation.md b/aptos-move/framework/aptos-framework/doc/permissioned_delegation.md index 4663691d80d7c..18494ff74745c 100644 --- a/aptos-move/framework/aptos-framework/doc/permissioned_delegation.md +++ b/aptos-move/framework/aptos-framework/doc/permissioned_delegation.md @@ -19,7 +19,7 @@
use 0x1::auth_data;
-use 0x1::bcs_stream;
+use 0x1::bcs_stream;
 use 0x1::big_ordered_map;
 use 0x1::ed25519;
 use 0x1::error;
@@ -382,12 +382,12 @@ Authorization function for account abstraction.
     abstraction_auth_data: AbstractionAuthData
 ): signer acquires RegisteredDelegations {
     let addr = signer::address_of(&account);
-    let stream = bcs_stream::new(*auth_data::authenticator(&abstraction_auth_data));
+    let stream = bcs_stream::new(*auth_data::authenticator(&abstraction_auth_data));
     let public_key = new_unvalidated_public_key_from_bytes(
-        bcs_stream::deserialize_vector<u8>(&mut stream, |x| deserialize_u8(x))
+        bcs_stream::deserialize_vector<u8>(&mut stream, |x| deserialize_u8(x))
     );
     let signature = new_signature_from_bytes(
-        bcs_stream::deserialize_vector<u8>(&mut stream, |x| deserialize_u8(x))
+        bcs_stream::deserialize_vector<u8>(&mut stream, |x| deserialize_u8(x))
     );
     assert!(
         ed25519::signature_verify_strict(
diff --git a/aptos-move/framework/aptos-framework/doc/rate_limiter.md b/aptos-move/framework/aptos-framework/doc/rate_limiter.md
index 95d64ff629bb3..6ea62e338615c 100644
--- a/aptos-move/framework/aptos-framework/doc/rate_limiter.md
+++ b/aptos-move/framework/aptos-framework/doc/rate_limiter.md
@@ -11,8 +11,7 @@
 -  [Function `refill`](#0x1_rate_limiter_refill)
 
 
-
use 0x1::math64;
-use 0x1::timestamp;
+
use 0x1::timestamp;
 
@@ -161,11 +160,16 @@ // Calculate the full tokens that can be added let accumulated_amount = time_passed * limiter.capacity + limiter.fractional_accumulated; let new_tokens = accumulated_amount / limiter.refill_interval; - limiter.current_amount = math64::min(limiter.current_amount + new_tokens, limiter.capacity); + limiter.current_amount = limiter.current_amount + new_tokens; + if (limiter.current_amount + new_tokens >= limiter.capacity) { + limiter.current_amount = limiter.capacity; + limiter.fractional_accumulated = 0; + } else { + limiter.current_amount = limiter.current_amount + new_tokens; + // Update the fractional amount accumulated for the next refill cycle + limiter.fractional_accumulated = accumulated_amount % limiter.refill_interval; + }; limiter.last_refill_timestamp = current_time; - - // Update the fractional amount accumulated for the next refill cycle - limiter.fractional_accumulated = accumulated_amount % limiter.refill_interval; }
diff --git a/aptos-move/framework/aptos-framework/sources/account/rate_limiter.move b/aptos-move/framework/aptos-framework/sources/account/rate_limiter.move index 8ee837e6dc182..84016732137ba 100644 --- a/aptos-move/framework/aptos-framework/sources/account/rate_limiter.move +++ b/aptos-move/framework/aptos-framework/sources/account/rate_limiter.move @@ -1,5 +1,4 @@ module aptos_framework::rate_limiter { - use aptos_std::math64; use aptos_framework::timestamp; enum RateLimiter has key, store, copy, drop { From a481b6b29aacdb726f6fea463818a151d3cecb63 Mon Sep 17 00:00:00 2001 From: Igor Date: Thu, 16 Jan 2025 21:58:34 -0800 Subject: [PATCH 4/6] fix unit tests --- .../aptos-framework/doc/rate_limiter.md | 1 - .../sources/account/rate_limiter.move | 18 ++++++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/aptos-move/framework/aptos-framework/doc/rate_limiter.md b/aptos-move/framework/aptos-framework/doc/rate_limiter.md index 6ea62e338615c..a52929b1efc8a 100644 --- a/aptos-move/framework/aptos-framework/doc/rate_limiter.md +++ b/aptos-move/framework/aptos-framework/doc/rate_limiter.md @@ -160,7 +160,6 @@ // Calculate the full tokens that can be added let accumulated_amount = time_passed * limiter.capacity + limiter.fractional_accumulated; let new_tokens = accumulated_amount / limiter.refill_interval; - limiter.current_amount = limiter.current_amount + new_tokens; if (limiter.current_amount + new_tokens >= limiter.capacity) { limiter.current_amount = limiter.capacity; limiter.fractional_accumulated = 0; diff --git a/aptos-move/framework/aptos-framework/sources/account/rate_limiter.move b/aptos-move/framework/aptos-framework/sources/account/rate_limiter.move index 84016732137ba..68c2ccc98501f 100644 --- a/aptos-move/framework/aptos-framework/sources/account/rate_limiter.move +++ b/aptos-move/framework/aptos-framework/sources/account/rate_limiter.move @@ -46,7 +46,6 @@ module aptos_framework::rate_limiter { // Calculate the full tokens that can be added let accumulated_amount = time_passed * limiter.capacity + limiter.fractional_accumulated; let new_tokens = accumulated_amount / limiter.refill_interval; - limiter.current_amount = limiter.current_amount + new_tokens; if (limiter.current_amount + new_tokens >= limiter.capacity) { limiter.current_amount = limiter.capacity; limiter.fractional_accumulated = 0; @@ -96,13 +95,16 @@ module aptos_framework::rate_limiter { // Refill the bucket refill(&mut bucket); - // Should have refilled 5 tokens (half of the capacity) - assert!(bucket.current_amount == 10, 400); // Bucket was already full, so should remain full + // Should have refilled 5 tokens (half of the capacity), + // but bucket was already full, so should remain full + assert!(bucket.current_amount == 10, 400); + assert!(bucket.fractional_accumulated == 0, 401); // Request 5 tokens let success = request(&mut bucket, 5); assert!(success, 401); // Request should succeed assert!(bucket.current_amount == 5, 402); // Remaining tokens should be 5 + assert!(bucket.fractional_accumulated == 0, 403); // Simulate another passage of 23 seconds timestamp::update_global_time_for_test_secs(timestamp::now_seconds() + 23); @@ -110,8 +112,10 @@ module aptos_framework::rate_limiter { // Refill again refill(&mut bucket); - // Should refill 4 tokens - assert!(bucket.current_amount == 9, 403); // Should now be full again + // Should refill 3 tokens + assert!(bucket.current_amount == 8, 403); + // and have 230-180 leftover + assert!(bucket.fractional_accumulated == 50, 404); } #[test(aptos_framework= @0x1)] @@ -120,13 +124,15 @@ module aptos_framework::rate_limiter { let bucket = initialize(10, 60); assert!(request(&mut bucket, 10), 1); // Request should succeed + assert!(bucket.current_amount == 0, 500); // No token will be added since it rounds down + // Simulate 10 seconds passing timestamp::update_global_time_for_test_secs(timestamp::now_seconds() + 10); // Refill the bucket refill(&mut bucket); // Should add 1/6th of the tokens (because 10 seconds is 1/6th of a minute) - assert!(bucket.current_amount == 6, 500); // No token will be added since it rounds down + assert!(bucket.current_amount == 1, 500); // 1 token will be added since it rounds down assert!(bucket.fractional_accumulated == 40, 501); // Accumulate the 4 seconds of fractional amount // Simulate another 50 seconds passing (total 60 seconds) From 9b798550cd07b433b95e963c39858e780ea68675 Mon Sep 17 00:00:00 2001 From: Igor Date: Thu, 16 Jan 2025 23:03:01 -0800 Subject: [PATCH 5/6] remove borrow_mut from big_ordered_map --- .../doc/permissioned_delegation.md | 18 ++++++++++-------- .../account/permissioned_delegation.move | 12 +++++++----- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/aptos-move/framework/aptos-framework/doc/permissioned_delegation.md b/aptos-move/framework/aptos-framework/doc/permissioned_delegation.md index 18494ff74745c..5b400fb5a0aae 100644 --- a/aptos-move/framework/aptos-framework/doc/permissioned_delegation.md +++ b/aptos-move/framework/aptos-framework/doc/permissioned_delegation.md @@ -9,7 +9,7 @@ - [Enum `DelegationKey`](#0x1_permissioned_delegation_DelegationKey) - [Resource `RegisteredDelegations`](#0x1_permissioned_delegation_RegisteredDelegations) - [Constants](#@Constants_0) -- [Function `fetch_handle`](#0x1_permissioned_delegation_fetch_handle) +- [Function `check_txn_rate`](#0x1_permissioned_delegation_check_txn_rate) - [Function `add_permissioned_handle`](#0x1_permissioned_delegation_add_permissioned_handle) - [Function `remove_permissioned_handle`](#0x1_permissioned_delegation_remove_permissioned_handle) - [Function `permissioned_signer_by_key`](#0x1_permissioned_delegation_permissioned_signer_by_key) @@ -201,13 +201,13 @@ - + -## Function `fetch_handle` +## Function `check_txn_rate` -
fun fetch_handle(bundle: &mut permissioned_delegation::AccountDelegation, check_rate_limit: bool): &permissioned_signer::StorablePermissionedHandle
+
fun check_txn_rate(bundle: &mut permissioned_delegation::AccountDelegation, check_rate_limit: bool)
 
@@ -216,12 +216,11 @@ Implementation -
inline fun fetch_handle(bundle: &mut AccountDelegation, check_rate_limit: bool): &StorablePermissionedHandle {
+
inline fun check_txn_rate(bundle: &mut AccountDelegation, check_rate_limit: bool) {
     let token_bucket = &mut bundle.rate_limiter;
     if (check_rate_limit && token_bucket.is_some()) {
         assert!(rate_limiter::request(token_bucket.borrow_mut(), 1), std::error::permission_denied(ERATE_LIMITED));
     };
-    &bundle.handle
 }
 
@@ -254,7 +253,7 @@ let addr = signer::address_of(master); if (!exists<RegisteredDelegations>(addr)) { move_to(master, RegisteredDelegations { - delegations: big_ordered_map::new_with_config(50, 20, true, 5) + delegations: big_ordered_map::new_with_config(50, 20, false, 0) }); }; let handles = &mut borrow_global_mut<RegisteredDelegations>(addr).delegations; @@ -429,7 +428,10 @@ Authorization function for account abstraction. if (exists<RegisteredDelegations>(master)) { let bundles = &mut borrow_global_mut<RegisteredDelegations>(master).delegations; if (bundles.contains(&key)) { - fetch_handle(bundles.borrow_mut(&key), count_rate) + let delegation = bundles.remove(&key); + check_txn_rate(&mut delegation, count_rate); + bundles.add(key, delegation); + &bundles.borrow(&key).handle } else { abort error::permission_denied(EINVALID_SIGNATURE) } diff --git a/aptos-move/framework/aptos-framework/sources/account/permissioned_delegation.move b/aptos-move/framework/aptos-framework/sources/account/permissioned_delegation.move index a31b105795f9c..4618c09c886fe 100644 --- a/aptos-move/framework/aptos-framework/sources/account/permissioned_delegation.move +++ b/aptos-move/framework/aptos-framework/sources/account/permissioned_delegation.move @@ -34,12 +34,11 @@ module aptos_framework::permissioned_delegation { delegations: BigOrderedMap } - inline fun fetch_handle(bundle: &mut AccountDelegation, check_rate_limit: bool): &StorablePermissionedHandle { + inline fun check_txn_rate(bundle: &mut AccountDelegation, check_rate_limit: bool) { let token_bucket = &mut bundle.rate_limiter; if (check_rate_limit && token_bucket.is_some()) { assert!(rate_limiter::request(token_bucket.borrow_mut(), 1), std::error::permission_denied(ERATE_LIMITED)); }; - &bundle.handle } public fun add_permissioned_handle( @@ -52,7 +51,7 @@ module aptos_framework::permissioned_delegation { let addr = signer::address_of(master); if (!exists(addr)) { move_to(master, RegisteredDelegations { - delegations: big_ordered_map::new_with_config(50, 20, true, 5) + delegations: big_ordered_map::new_with_config(50, 20, false, 0) }); }; let handles = &mut borrow_global_mut(addr).delegations; @@ -127,7 +126,10 @@ module aptos_framework::permissioned_delegation { if (exists(master)) { let bundles = &mut borrow_global_mut(master).delegations; if (bundles.contains(&key)) { - fetch_handle(bundles.borrow_mut(&key), count_rate) + let delegation = bundles.remove(&key); + check_txn_rate(&mut delegation, count_rate); + bundles.add(key, delegation); + &bundles.borrow(&key).handle } else { abort error::permission_denied(EINVALID_SIGNATURE) } @@ -186,7 +188,7 @@ module aptos_framework::permissioned_delegation { }; let auth_data = auth_data::create_auth_data(vector[1, 2, 3], bcs::to_bytes(&sig_bundle)); assert!(!is_permissioned_signer(&account), 1); - add_permissioned_handle(&account, key, option::none(), 60); + add_permissioned_handle(&account, key, option::some(rate_limiter::initialize(1, 10)), 60); authenticate(account, auth_data); authenticate(account_copy, auth_data); remove_permissioned_handle(&account_copy_2, key); From 48d007e1164ae3d61da49e737c71534b8d6eb649 Mon Sep 17 00:00:00 2001 From: Igor Date: Fri, 17 Jan 2025 08:42:36 -0800 Subject: [PATCH 6/6] additions to delegation --- .../doc/permissioned_delegation.md | 59 +++++++++++++++---- .../account/permissioned_delegation.move | 40 +++++++++---- 2 files changed, 74 insertions(+), 25 deletions(-) diff --git a/aptos-move/framework/aptos-framework/doc/permissioned_delegation.md b/aptos-move/framework/aptos-framework/doc/permissioned_delegation.md index 5b400fb5a0aae..fccff1c7917f3 100644 --- a/aptos-move/framework/aptos-framework/doc/permissioned_delegation.md +++ b/aptos-move/framework/aptos-framework/doc/permissioned_delegation.md @@ -9,6 +9,7 @@ - [Enum `DelegationKey`](#0x1_permissioned_delegation_DelegationKey) - [Resource `RegisteredDelegations`](#0x1_permissioned_delegation_RegisteredDelegations) - [Constants](#@Constants_0) +- [Function `gen_ed25519_key`](#0x1_permissioned_delegation_gen_ed25519_key) - [Function `check_txn_rate`](#0x1_permissioned_delegation_check_txn_rate) - [Function `add_permissioned_handle`](#0x1_permissioned_delegation_add_permissioned_handle) - [Function `remove_permissioned_handle`](#0x1_permissioned_delegation_remove_permissioned_handle) @@ -16,6 +17,7 @@ - [Function `handle_address_by_key`](#0x1_permissioned_delegation_handle_address_by_key) - [Function `authenticate`](#0x1_permissioned_delegation_authenticate) - [Function `get_storable_permissioned_handle`](#0x1_permissioned_delegation_get_storable_permissioned_handle) +- [Specification](#@Specification_1)
use 0x1::auth_data;
@@ -165,11 +167,11 @@
 
 
 
-
+
 
 
 
-
const EHANDLE_EXISTENCE: u64 = 5;
+
const EDELEGATION_EXISTENCE: u64 = 5;
 
@@ -201,6 +203,30 @@ + + +## Function `gen_ed25519_key` + + + +
public fun gen_ed25519_key(key: ed25519::UnvalidatedPublicKey): permissioned_delegation::DelegationKey
+
+ + + +
+Implementation + + +
public fun gen_ed25519_key(key: UnvalidatedPublicKey): DelegationKey {
+    DelegationKey::Ed25519PublicKey(key)
+}
+
+ + + +
+ ## Function `check_txn_rate` @@ -257,7 +283,7 @@ }); }; let handles = &mut borrow_global_mut<RegisteredDelegations>(addr).delegations; - assert!(!handles.contains(&key), error::already_exists(EHANDLE_EXISTENCE)); + assert!(!handles.contains(&key), error::already_exists(EDELEGATION_EXISTENCE)); let handle = permissioned_signer::create_storable_permissioned_handle(master, expiration_time); let permissioned_signer = permissioned_signer::signer_from_storable_permissioned_handle(&handle); handles.add(key, AccountDelegation::V1 { handle, rate_limiter }); @@ -290,10 +316,10 @@ ) acquires RegisteredDelegations { assert!(!is_permissioned_signer(master), error::permission_denied(ENOT_MASTER_SIGNER)); let addr = signer::address_of(master); - let handle_bundles = &mut borrow_global_mut<RegisteredDelegations>(addr).delegations; - assert!(handle_bundles.contains(&key), error::not_found(EHANDLE_EXISTENCE)); - let bundle = handle_bundles.remove(&key); - match (bundle) { + let delegations = &mut borrow_global_mut<RegisteredDelegations>(addr).delegations; + assert!(delegations.contains(&key), error::not_found(EDELEGATION_EXISTENCE)); + let delegation = delegations.remove(&key); + match (delegation) { AccountDelegation::V1 { handle, rate_limiter: _ } => { permissioned_signer::destroy_storable_permissioned_handle(handle); } @@ -426,12 +452,12 @@ Authorization function for account abstraction. count_rate: bool ): &StorablePermissionedHandle { if (exists<RegisteredDelegations>(master)) { - let bundles = &mut borrow_global_mut<RegisteredDelegations>(master).delegations; - if (bundles.contains(&key)) { - let delegation = bundles.remove(&key); + let delegations = &mut borrow_global_mut<RegisteredDelegations>(master).delegations; + if (delegations.contains(&key)) { + let delegation = delegations.remove(&key); check_txn_rate(&mut delegation, count_rate); - bundles.add(key, delegation); - &bundles.borrow(&key).handle + delegations.add(key, delegation); + &delegations.borrow(&key).handle } else { abort error::permission_denied(EINVALID_SIGNATURE) } @@ -445,5 +471,14 @@ Authorization function for account abstraction. + + +## Specification + + + +
pragma verify = false;
+
+ [move-book]: https://aptos.dev/move/book/SUMMARY diff --git a/aptos-move/framework/aptos-framework/sources/account/permissioned_delegation.move b/aptos-move/framework/aptos-framework/sources/account/permissioned_delegation.move index 4618c09c886fe..12050706211d1 100644 --- a/aptos-move/framework/aptos-framework/sources/account/permissioned_delegation.move +++ b/aptos-move/framework/aptos-framework/sources/account/permissioned_delegation.move @@ -2,8 +2,12 @@ module aptos_framework::permissioned_delegation { use std::error; use std::option::Option; use std::signer; - use aptos_std::ed25519; - use aptos_std::ed25519::{new_signature_from_bytes, new_unvalidated_public_key_from_bytes, UnvalidatedPublicKey}; + use aptos_std::ed25519::{ + Self, + new_signature_from_bytes, + new_unvalidated_public_key_from_bytes, + UnvalidatedPublicKey + }; use aptos_std::big_ordered_map::{Self, BigOrderedMap}; use aptos_framework::auth_data::{Self, AbstractionAuthData}; use aptos_framework::bcs_stream::{Self, deserialize_u8}; @@ -19,7 +23,7 @@ module aptos_framework::permissioned_delegation { const EINVALID_PUBLIC_KEY: u64 = 2; const EPUBLIC_KEY_NOT_FOUND: u64 = 3; const EINVALID_SIGNATURE: u64 = 4; - const EHANDLE_EXISTENCE: u64 = 5; + const EDELEGATION_EXISTENCE: u64 = 5; const ERATE_LIMITED: u64 = 6; enum AccountDelegation has store { @@ -30,6 +34,10 @@ module aptos_framework::permissioned_delegation { Ed25519PublicKey(UnvalidatedPublicKey) } + public fun gen_ed25519_key(key: UnvalidatedPublicKey): DelegationKey { + DelegationKey::Ed25519PublicKey(key) + } + struct RegisteredDelegations has key { delegations: BigOrderedMap } @@ -55,7 +63,7 @@ module aptos_framework::permissioned_delegation { }); }; let handles = &mut borrow_global_mut(addr).delegations; - assert!(!handles.contains(&key), error::already_exists(EHANDLE_EXISTENCE)); + assert!(!handles.contains(&key), error::already_exists(EDELEGATION_EXISTENCE)); let handle = permissioned_signer::create_storable_permissioned_handle(master, expiration_time); let permissioned_signer = permissioned_signer::signer_from_storable_permissioned_handle(&handle); handles.add(key, AccountDelegation::V1 { handle, rate_limiter }); @@ -68,10 +76,10 @@ module aptos_framework::permissioned_delegation { ) acquires RegisteredDelegations { assert!(!is_permissioned_signer(master), error::permission_denied(ENOT_MASTER_SIGNER)); let addr = signer::address_of(master); - let handle_bundles = &mut borrow_global_mut(addr).delegations; - assert!(handle_bundles.contains(&key), error::not_found(EHANDLE_EXISTENCE)); - let bundle = handle_bundles.remove(&key); - match (bundle) { + let delegations = &mut borrow_global_mut(addr).delegations; + assert!(delegations.contains(&key), error::not_found(EDELEGATION_EXISTENCE)); + let delegation = delegations.remove(&key); + match (delegation) { AccountDelegation::V1 { handle, rate_limiter: _ } => { permissioned_signer::destroy_storable_permissioned_handle(handle); } @@ -124,12 +132,12 @@ module aptos_framework::permissioned_delegation { count_rate: bool ): &StorablePermissionedHandle { if (exists(master)) { - let bundles = &mut borrow_global_mut(master).delegations; - if (bundles.contains(&key)) { - let delegation = bundles.remove(&key); + let delegations = &mut borrow_global_mut(master).delegations; + if (delegations.contains(&key)) { + let delegation = delegations.remove(&key); check_txn_rate(&mut delegation, count_rate); - bundles.add(key, delegation); - &bundles.borrow(&key).handle + delegations.add(key, delegation); + &delegations.borrow(&key).handle } else { abort error::permission_denied(EINVALID_SIGNATURE) } @@ -138,6 +146,12 @@ module aptos_framework::permissioned_delegation { } } + /// + spec module { + // TODO: fix verification + pragma verify = false; + } + #[test_only] use aptos_std::ed25519::{sign_arbitrary_bytes, generate_keys, validated_public_key_to_bytes, Signature, public_key_into_unvalidated