diff --git a/aptos-move/framework/aptos-framework/doc/overview.md b/aptos-move/framework/aptos-framework/doc/overview.md index c0b9a253ee628..ef46811fd8a4f 100644 --- a/aptos-move/framework/aptos-framework/doc/overview.md +++ b/aptos-move/framework/aptos-framework/doc/overview.md @@ -49,6 +49,7 @@ This is the reference documentation of the Aptos framework. - [`0x1::object`](object.md#0x1_object) - [`0x1::object_code_deployment`](object_code_deployment.md#0x1_object_code_deployment) - [`0x1::optional_aggregator`](optional_aggregator.md#0x1_optional_aggregator) +- [`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) @@ -67,6 +68,7 @@ This is the reference documentation of the Aptos framework. - [`0x1::storage_gas`](storage_gas.md#0x1_storage_gas) - [`0x1::system_addresses`](system_addresses.md#0x1_system_addresses) - [`0x1::timestamp`](timestamp.md#0x1_timestamp) +- [`0x1::token_bucket`](token_bucket.md#0x1_token_bucket) - [`0x1::transaction_context`](transaction_context.md#0x1_transaction_context) - [`0x1::transaction_fee`](transaction_fee.md#0x1_transaction_fee) - [`0x1::transaction_validation`](transaction_validation.md#0x1_transaction_validation) diff --git a/aptos-move/framework/aptos-framework/doc/permissioned_delegation.md b/aptos-move/framework/aptos-framework/doc/permissioned_delegation.md index 2edb6975559be..571c8d6624127 100644 --- a/aptos-move/framework/aptos-framework/doc/permissioned_delegation.md +++ b/aptos-move/framework/aptos-framework/doc/permissioned_delegation.md @@ -5,27 +5,76 @@ +- [Enum `HandleBundle`](#0x1_permissioned_delegation_HandleBundle) - [Resource `Delegation`](#0x1_permissioned_delegation_Delegation) - [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::signer;
use 0x1::table;
+use 0x1::token_bucket;
+
+
+## Enum `HandleBundle`
+
+
+
+enum HandleBundle has store
+
+
+
+
+
+Variants
+
+
+
+V1
+
+
+
+Fields
+
+
+
+-
+
handle: permissioned_signer::StorablePermissionedHandle
+
+-
+
+
+-
+
bucket: option::Option<token_bucket::Bucket>
+
+-
+
+
+
+
+
+
+
+
+
+
+
## Resource `Delegation`
@@ -43,7 +92,7 @@
-
-
handles: table::Table<ed25519::UnvalidatedPublicKey, permissioned_signer::StorablePermissionedHandle>
+handle_bundles: table::Table<ed25519::UnvalidatedPublicKey, permissioned_delegation::HandleBundle>
-
@@ -103,13 +152,50 @@
+
+
+
+
+
const ERATE_LIMITED: u64 = 6;
+
+
+
+
+
+
+## Function `fetch_handle`
+
+
+
+fun fetch_handle(bundle: &mut permissioned_delegation::HandleBundle, check_rate_limit: bool): &permissioned_signer::StorablePermissionedHandle
+
+
+
+
+
+Implementation
+
+
+inline fun fetch_handle(bundle: &mut HandleBundle, check_rate_limit: bool): &StorablePermissionedHandle {
+ let token_bucket = &mut bundle.bucket;
+ if (check_rate_limit && token_bucket.is_some()) {
+ assert!(token_bucket::request(token_bucket.borrow_mut(), 1), std::error::permission_denied(ERATE_LIMITED));
+ };
+ &bundle.handle
+}
+
+
+
+
+
+
## Function `add_permissioned_handle`
-public fun add_permissioned_handle(master: &signer, key: vector<u8>, expiration_time: u64): signer
+public fun add_permissioned_handle(master: &signer, key: vector<u8>, max_txn_per_minute: option::Option<u64>, expiration_time: u64): signer
@@ -121,6 +207,7 @@
public fun add_permissioned_handle(
master: &signer,
key: vector<u8>,
+ max_txn_per_minute: Option<u64>,
expiration_time: u64,
): signer acquires Delegation {
assert!(!is_permissioned_signer(master), error::permission_denied(ENOT_MASTER_SIGNER));
@@ -128,14 +215,15 @@
let pubkey = ed25519::new_unvalidated_public_key_from_bytes(key);
if (!exists<Delegation>(addr)) {
move_to(master, Delegation {
- handles: table::new()
+ handle_bundles: table::new()
});
};
- let handles = &mut borrow_global_mut<Delegation>(addr).handles;
- assert!(!table::contains(handles, pubkey), error::already_exists(EHANDLE_EXISTENCE));
+ let handles = &mut borrow_global_mut<Delegation>(addr).handle_bundles;
+ assert!(!handles.contains(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);
+ let bucket = max_txn_per_minute.map(|capacity|token_bucket::initialize_bucket(capacity));
+ let permissioned_signer = permissioned_signer::signer_from_storable_permissioned_handle(&handle);
+ handles.add(pubkey, HandleBundle::V1 { bucket, handle });
permissioned_signer
}
@@ -166,9 +254,14 @@
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));
+ let handle_bundles = &mut borrow_global_mut<Delegation>(addr).handle_bundles;
+ assert!(handle_bundles.contains(pubkey), error::not_found(EHANDLE_EXISTENCE));
+ let bundle = handle_bundles.remove(pubkey);
+ match (bundle) {
+ HandleBundle::V1 { handle, bucket: _ } => {
+ permissioned_signer::destroy_storable_permissioned_handle(handle);
+ }
+ };
}
@@ -198,51 +291,8 @@
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)
-}
-
-
-
-
-
-
-
-
-## Function `remove_permissioned_handle_by_delegate`
-
-
-
-public fun remove_permissioned_handle_by_delegate(master: address, signature: vector<u8>): permissioned_signer::StorablePermissionedHandle
-
-
-
-
-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)
+ let handle = get_storable_permissioned_handle(addr, pubkey, false);
+ permissioned_signer::signer_from_storable_permissioned_handle(handle)
}
@@ -268,9 +318,8 @@
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))
+ let handle = get_storable_permissioned_handle(master, pubkey, false);
+ permissioned_signer::permissions_storage_address(handle)
}
@@ -285,7 +334,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
@@ -294,13 +343,9 @@ Authorization function for account abstraction.
Implementation
-public fun authenticate(
- account: signer,
- transaction_hash: vector<u8>,
- signature: vector<u8>
-): signer acquires Delegation {
+public fun authenticate(account: signer, abstraction_auth_data: AbstractionAuthData): signer acquires Delegation {
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 +356,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, public_key, true);
+ permissioned_signer::signer_from_storable_permissioned_handle(handle)
}
@@ -323,13 +369,13 @@ Authorization function for account abstraction.
fun get_permissioned_signer(master: address, pubkey: ed25519::UnvalidatedPublicKey): signer
+fun get_storable_permissioned_handle(master: address, pubkey: ed25519::UnvalidatedPublicKey, count_rate: bool): &permissioned_signer::StorablePermissionedHandle
@@ -338,12 +384,15 @@ Authorization function for account abstraction.
Implementation
-inline fun get_permissioned_signer(master: address, pubkey: UnvalidatedPublicKey): signer {
+inline fun get_storable_permissioned_handle(
+ master: address,
+ pubkey: UnvalidatedPublicKey,
+ count_rate: bool
+): &StorablePermissionedHandle {
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
+ let bundles = &mut borrow_global_mut<Delegation>(master).handle_bundles;
+ if (bundles.contains(pubkey)) {
+ fetch_handle(bundles.borrow_mut(pubkey), count_rate)
} else {
abort error::permission_denied(EINVALID_SIGNATURE)
}
diff --git a/aptos-move/framework/aptos-framework/sources/account.move b/aptos-move/framework/aptos-framework/sources/account.move
index 6f39af5439c09..c435c37a91695 100644
--- a/aptos-move/framework/aptos-framework/sources/account.move
+++ b/aptos-move/framework/aptos-framework/sources/account.move
@@ -1620,7 +1620,6 @@ module aptos_framework::account {
create_account_unchecked(addr);
register_coin(addr);
- let eventhandle = &borrow_global(addr).coin_register_events;
let event = CoinRegister { account: addr, type_info: type_info::type_of() };
let events = event::emitted_events();
diff --git a/aptos-move/framework/aptos-framework/sources/account_abstraction/permissioned_delegation.move b/aptos-move/framework/aptos-framework/sources/account_abstraction/permissioned_delegation.move
new file mode 100644
index 0000000000000..f4e354d85c4a5
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/sources/account_abstraction/permissioned_delegation.move
@@ -0,0 +1,190 @@
+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;
+ use aptos_framework::auth_data::AbstractionAuthData;
+ use aptos_framework::bcs_stream;
+ use aptos_framework::bcs_stream::deserialize_u8;
+ use aptos_framework::permissioned_signer::{Self, is_permissioned_signer, StorablePermissionedHandle};
+ use aptos_framework::token_bucket;
+ #[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 HandleBundle has store {
+ V1 { handle: StorablePermissionedHandle, bucket: Option }
+ }
+
+ struct Delegation has key {
+ handle_bundles: Table
+ }
+
+ inline fun fetch_handle(bundle: &mut HandleBundle, check_rate_limit: bool): &StorablePermissionedHandle {
+ let token_bucket = &mut bundle.bucket;
+ if (check_rate_limit && token_bucket.is_some()) {
+ assert!(token_bucket::request(token_bucket.borrow_mut(), 1), std::error::permission_denied(ERATE_LIMITED));
+ };
+ &bundle.handle
+ }
+
+ public fun add_permissioned_handle(
+ master: &signer,
+ key: vector,
+ max_txn_per_minute: Option,
+ 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(addr)) {
+ move_to(master, Delegation {
+ handle_bundles: table::new()
+ });
+ };
+ let handles = &mut borrow_global_mut(addr).handle_bundles;
+ assert!(!handles.contains(pubkey), error::already_exists(EHANDLE_EXISTENCE));
+ let handle = permissioned_signer::create_storable_permissioned_handle(master, expiration_time);
+ let bucket = max_txn_per_minute.map(|capacity|token_bucket::initialize_bucket(capacity));
+ let permissioned_signer = permissioned_signer::signer_from_storable_permissioned_handle(&handle);
+ handles.add(pubkey, HandleBundle::V1 { bucket, handle });
+ permissioned_signer
+ }
+
+ public fun remove_permissioned_handle(
+ master: &signer,
+ key: vector,
+ ) 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);
+ let handle_bundles = &mut borrow_global_mut(addr).handle_bundles;
+ assert!(handle_bundles.contains(pubkey), error::not_found(EHANDLE_EXISTENCE));
+ let bundle = handle_bundles.remove(pubkey);
+ match (bundle) {
+ HandleBundle::V1 { handle, bucket: _ } => {
+ permissioned_signer::destroy_storable_permissioned_handle(handle);
+ }
+ };
+ }
+
+ public fun permissioned_signer_by_key(
+ master: &signer,
+ key: vector,
+ ): 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);
+ let handle = get_storable_permissioned_handle(addr, pubkey, false);
+ permissioned_signer::signer_from_storable_permissioned_handle(handle)
+ }
+
+ #[view]
+ public fun handle_address_by_key(master: address, key: vector): address acquires Delegation {
+ let pubkey = ed25519::new_unvalidated_public_key_from_bytes(key);
+ let handle = get_storable_permissioned_handle(master, pubkey, false);
+ permissioned_signer::permissions_storage_address(handle)
+ }
+
+ /// Authorization function for account abstraction.
+ public fun authenticate(account: signer, abstraction_auth_data: AbstractionAuthData): signer acquires Delegation {
+ 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, public_key, true);
+ permissioned_signer::signer_from_storable_permissioned_handle(handle)
+ }
+
+ inline fun get_storable_permissioned_handle(
+ master: address,
+ pubkey: UnvalidatedPublicKey,
+ count_rate: bool
+ ): &StorablePermissionedHandle {
+ if (exists(master)) {
+ let bundles = &mut borrow_global_mut(master).handle_bundles;
+ if (bundles.contains(pubkey)) {
+ fetch_handle(bundles.borrow_mut(pubkey), 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};
+ #[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 Delegation {
+ 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 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, pubkey_bytes, option::none(), 60);
+ let permissioned_signer = authenticate(account, auth_data);
+ assert!(is_permissioned_signer(&permissioned_signer), 2);
+ remove_permissioned_handle(&account_copy, pubkey_bytes);
+ }
+
+ #[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 Delegation {
+ 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 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, pubkey_bytes, option::some(1), 60);
+ authenticate(account, auth_data);
+ authenticate(account_copy, auth_data);
+ remove_permissioned_handle(&account_copy_2, pubkey_bytes);
+ }
+}
diff --git a/aptos-move/framework/aptos-framework/sources/account_abstraction/token_bucket.move b/aptos-move/framework/aptos-framework/sources/account_abstraction/token_bucket.move
new file mode 100644
index 0000000000000..19877ba439708
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/sources/account_abstraction/token_bucket.move
@@ -0,0 +1,172 @@
+module aptos_framework::token_bucket {
+ use aptos_std::math64;
+ use aptos_framework::timestamp;
+
+ // Struct to represent a Token Bucket that refills every minute
+ struct Bucket has key, store, copy, drop {
+ // Maximum number of transactions allowed per minute
+ capacity: u64,
+ // Current number of transactions remaining in this minute
+ tokens: u64,
+ // Transactions added per minute
+ refill_rate_per_minute: u64,
+ // Last time the bucket was refilled (in seconds)
+ last_refill_timestamp: u64,
+ // Time accumulated that hasn't yet added up to a full token
+ fractional_time_accumulated: u64,
+ }
+
+ // Public entry function to initialize a Token Bucket for transactions per minute
+ 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
+ }
+
+ // Public function to request a transaction from the bucket
+ 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 to refill the transactions in the bucket based on time passed (in minutes)
+ 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;
+ }
+
+ #[test(aptos_framework = @0x1)]
+ fun test_initialize_bucket(aptos_framework: &signer) {
+ timestamp::set_time_has_started_for_testing(aptos_framework);
+ let bucket = initialize_bucket(10);
+ assert!(bucket.capacity == 10, 100);
+ assert!(bucket.tokens == 10, 101);
+ assert!(bucket.refill_rate_per_minute == 10, 102);
+ }
+
+ #[test(aptos_framework = @0x1)]
+ fun test_request_success(aptos_framework: &signer) {
+ timestamp::set_time_has_started_for_testing(aptos_framework);
+ let bucket = initialize_bucket(10);
+ let success = request(&mut bucket, 5);
+ assert!(success, 200); // Should succeed since 5 <= 10
+ assert!(bucket.tokens == 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_bucket(10);
+ let success = request(&mut bucket, 15);
+ assert!(!success, 300); // Should fail since 15 > 10
+ assert!(bucket.tokens == 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_bucket(10);
+
+ // Simulate a passage of 30 seconds
+ timestamp::update_global_time_for_test_secs(timestamp::now_seconds() + 30);
+
+ // Refill the bucket
+ refill(&mut bucket);
+
+ // Should have refilled 5 tokens (half of the capacity)
+ assert!(bucket.tokens == 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.tokens == 5, 402); // Remaining tokens should be 5
+
+ // Simulate another passage of 30 seconds
+ timestamp::update_global_time_for_test_secs(timestamp::now_seconds() + 30);
+
+ // Refill again
+ refill(&mut bucket);
+
+ // Should refill 5 tokens, bringing back to full
+ assert!(bucket.tokens == 10, 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_bucket(10);
+
+ // 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.tokens == 10, 500); // No token will be added since it rounds down
+ assert!(bucket.fractional_time_accumulated == 10, 501); // Accumulate the 10 seconds of fractional time
+
+ // 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);
+
+ // Should refill 10 tokens (full minute passed)
+ assert!(bucket.tokens == 10, 502); // Should be full now
+ assert!(bucket.fractional_time_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_bucket(10);
+
+ // Request 8 tokens
+ let success = request(&mut bucket, 8);
+ assert!(success, 600); // Should succeed
+ assert!(bucket.tokens == 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.tokens == 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.tokens == 10, 603); // Should be full again
+ }
+}
diff --git a/aptos-move/framework/aptos-framework/sources/aptos_account.move b/aptos-move/framework/aptos-framework/sources/aptos_account.move
index 2a4276fd6dffc..46e6c8f280604 100644
--- a/aptos-move/framework/aptos-framework/sources/aptos_account.move
+++ b/aptos-move/framework/aptos-framework/sources/aptos_account.move
@@ -322,7 +322,6 @@ module aptos_framework::aptos_account {
use aptos_framework::permissioned_signer;
let bob = from_bcs::to_address(x"0000000000000000000000000000000000000000000000000000000000000b0b");
- let carol = from_bcs::to_address(x"00000000000000000000000000000000000000000000000000000000000ca501");
let (burn_cap, mint_cap) = aptos_framework::aptos_coin::initialize_for_test(core);
create_account(signer::address_of(alice));