From 9d0650685e91f08cacc92e660c2a09bef8e51107 Mon Sep 17 00:00:00 2001 From: Aaron Gao Date: Thu, 7 Nov 2024 15:37:03 -0600 Subject: [PATCH] [gas] permission --- .../doc/permissioned_delegation.md | 45 ++++-- .../doc/transaction_validation.md | 134 +++++++++++++++++- .../account/permissioned_delegation.move | 30 ++-- .../sources/transaction_validation.move | 58 +++++++- testsuite/smoke-test/src/lib.rs | 2 + .../smoke-test/src/permissioned_delegation.rs | 108 ++++++++++++++ 6 files changed, 352 insertions(+), 25 deletions(-) create mode 100644 testsuite/smoke-test/src/permissioned_delegation.rs diff --git a/aptos-move/framework/aptos-framework/doc/permissioned_delegation.md b/aptos-move/framework/aptos-framework/doc/permissioned_delegation.md index 18494ff74745c..310b47f3e9b6b 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 `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) @@ -165,11 +166,11 @@ - + -
const EHANDLE_EXISTENCE: u64 = 5;
+
const EDELEGATION_EXISTENCE: u64 = 5;
 
@@ -201,6 +202,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 `fetch_handle` @@ -258,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 }); @@ -291,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); } @@ -427,9 +452,9 @@ 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)) { - fetch_handle(bundles.borrow_mut(&key), count_rate) + let delegations = &mut borrow_global_mut<RegisteredDelegations>(master).delegations; + if (delegations.contains(&key)) { + fetch_handle(delegations.borrow_mut(&key), count_rate) } else { abort error::permission_denied(EINVALID_SIGNATURE) } diff --git a/aptos-move/framework/aptos-framework/doc/transaction_validation.md b/aptos-move/framework/aptos-framework/doc/transaction_validation.md index 90b0e01a7a56f..e80e944e3dd7d 100644 --- a/aptos-move/framework/aptos-framework/doc/transaction_validation.md +++ b/aptos-move/framework/aptos-framework/doc/transaction_validation.md @@ -6,7 +6,10 @@ - [Resource `TransactionValidation`](#0x1_transaction_validation_TransactionValidation) +- [Struct `GasPermission`](#0x1_transaction_validation_GasPermission) - [Constants](#@Constants_0) +- [Function `grant_gas_permission`](#0x1_transaction_validation_grant_gas_permission) +- [Function `revoke_gas_permission`](#0x1_transaction_validation_revoke_gas_permission) - [Function `initialize`](#0x1_transaction_validation_initialize) - [Function `prologue_common`](#0x1_transaction_validation_prologue_common) - [Function `script_prologue`](#0x1_transaction_validation_script_prologue) @@ -57,6 +60,7 @@ use 0x1::error; use 0x1::features; use 0x1::option; +use 0x1::permissioned_signer; use 0x1::signer; use 0x1::system_addresses; use 0x1::timestamp; @@ -123,6 +127,33 @@ correct chain-specific prologue and epilogue functions + + + + +## Struct `GasPermission` + + + +
struct GasPermission has copy, drop, store
+
+ + + +
+Fields + + +
+
+dummy_field: bool +
+
+ +
+
+ +
@@ -243,6 +274,76 @@ important to the semantics of the system. + + + + +
const PROLOGUE_PERMISSIONED_GAS_LIMIT_INSUFFICIENT: u64 = 1011;
+
+ + + + + +## Function `grant_gas_permission` + +Permission management + +Master signer grant permissioned signer ability to consume a given amount of gas in octas. + + +
public fun grant_gas_permission(master: &signer, permissioned: &signer, gas_amount: u64)
+
+ + + +
+Implementation + + +
public fun grant_gas_permission(
+    master: &signer,
+    permissioned: &signer,
+    gas_amount: u64
+) {
+    permissioned_signer::authorize_increase(
+        master,
+        permissioned,
+        (gas_amount as u256),
+        GasPermission {}
+    )
+}
+
+ + + +
+ + + +## Function `revoke_gas_permission` + +Removing permissions from permissioned signer. + + +
public fun revoke_gas_permission(permissioned: &signer)
+
+ + + +
+Implementation + + +
public fun revoke_gas_permission(permissioned: &signer) {
+    permissioned_signer::revoke_permission(permissioned, GasPermission {})
+}
+
+ + + +
+ ## Function `initialize` @@ -382,6 +483,14 @@ Only called during genesis to initialize system resources for this module. is_simulation, gas_payer_address )) { + assert!( + permissioned_signer::check_permission_capacity_above( + gas_payer, + (max_transaction_fee as u256), + GasPermission {} + ), + error::permission_denied(PROLOGUE_PERMISSIONED_GAS_LIMIT_INSUFFICIENT) + ); if (features::operations_default_to_fa_apt_store_enabled()) { assert!( aptos_account::is_fungible_balance_at_least(gas_payer_address, max_transaction_fee), @@ -960,12 +1069,23 @@ Called by the Adapter ); }; + let gas_payer_signer = &create_signer::create_signer(gas_payer); if (transaction_fee_amount > storage_fee_refunded) { let burn_amount = transaction_fee_amount - storage_fee_refunded; transaction_fee::burn_fee(gas_payer, burn_amount); + permissioned_signer::check_permission_consume( + gas_payer_signer, + (burn_amount as u256), + GasPermission {} + ); } else if (transaction_fee_amount < storage_fee_refunded) { let mint_amount = storage_fee_refunded - transaction_fee_amount; - transaction_fee::mint_and_refund(gas_payer, mint_amount) + transaction_fee::mint_and_refund(gas_payer, mint_amount); + permissioned_signer::increase_limit( + gas_payer_signer, + (mint_amount as u256), + GasPermission {} + ); }; }; @@ -1195,9 +1315,19 @@ If there is no fee_payer, fee_payer = sender if (transaction_fee_amount > storage_fee_refunded) { let burn_amount = transaction_fee_amount - storage_fee_refunded; transaction_fee::burn_fee(gas_payer_address, burn_amount); + permissioned_signer::check_permission_consume( + &gas_payer, + (burn_amount as u256), + GasPermission {} + ); } else if (transaction_fee_amount < storage_fee_refunded) { let mint_amount = storage_fee_refunded - transaction_fee_amount; - transaction_fee::mint_and_refund(gas_payer_address, mint_amount) + transaction_fee::mint_and_refund(gas_payer_address, mint_amount); + permissioned_signer::increase_limit( + &gas_payer, + (mint_amount as u256), + GasPermission {} + ); }; }; 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..5b6850023f9b1 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 } @@ -56,7 +64,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 }); @@ -69,10 +77,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); } @@ -125,9 +133,9 @@ module aptos_framework::permissioned_delegation { count_rate: bool ): &StorablePermissionedHandle { if (exists(master)) { - let bundles = &mut borrow_global_mut(master).delegations; - if (bundles.contains(&key)) { - fetch_handle(bundles.borrow_mut(&key), count_rate) + let delegations = &mut borrow_global_mut(master).delegations; + if (delegations.contains(&key)) { + fetch_handle(delegations.borrow_mut(&key), count_rate) } else { abort error::permission_denied(EINVALID_SIGNATURE) } diff --git a/aptos-move/framework/aptos-framework/sources/transaction_validation.move b/aptos-move/framework/aptos-framework/sources/transaction_validation.move index c70a191e1784f..7cef1df22ff48 100644 --- a/aptos-move/framework/aptos-framework/sources/transaction_validation.move +++ b/aptos-move/framework/aptos-framework/sources/transaction_validation.move @@ -14,6 +14,7 @@ module aptos_framework::transaction_validation { use aptos_framework::chain_id; use aptos_framework::coin; use aptos_framework::create_signer; + use aptos_framework::permissioned_signer; use aptos_framework::system_addresses; use aptos_framework::timestamp; use aptos_framework::transaction_fee; @@ -32,6 +33,8 @@ module aptos_framework::transaction_validation { user_epilogue_name: vector, } + struct GasPermission has copy, drop, store {} + /// MSB is used to indicate a gas payer tx const MAX_U64: u128 = 18446744073709551615; @@ -51,6 +54,28 @@ module aptos_framework::transaction_validation { const PROLOGUE_ESEQUENCE_NUMBER_TOO_BIG: u64 = 1008; const PROLOGUE_ESECONDARY_KEYS_ADDRESSES_COUNT_MISMATCH: u64 = 1009; const PROLOGUE_EFEE_PAYER_NOT_ENABLED: u64 = 1010; + const PROLOGUE_PERMISSIONED_GAS_LIMIT_INSUFFICIENT: u64 = 1011; + + /// Permission management + /// + /// Master signer grant permissioned signer ability to consume a given amount of gas in octas. + public fun grant_gas_permission( + master: &signer, + permissioned: &signer, + gas_amount: u64 + ) { + permissioned_signer::authorize_increase( + master, + permissioned, + (gas_amount as u256), + GasPermission {} + ) + } + + /// Removing permissions from permissioned signer. + public fun revoke_gas_permission(permissioned: &signer) { + permissioned_signer::revoke_permission(permissioned, GasPermission {}) + } /// Only called during genesis to initialize system resources for this module. public(friend) fun initialize( @@ -156,6 +181,14 @@ module aptos_framework::transaction_validation { is_simulation, gas_payer_address )) { + assert!( + permissioned_signer::check_permission_capacity_above( + gas_payer, + (max_transaction_fee as u256), + GasPermission {} + ), + error::permission_denied(PROLOGUE_PERMISSIONED_GAS_LIMIT_INSUFFICIENT) + ); if (features::operations_default_to_fa_apt_store_enabled()) { assert!( aptos_account::is_fungible_balance_at_least(gas_payer_address, max_transaction_fee), @@ -529,12 +562,23 @@ module aptos_framework::transaction_validation { ); }; + let gas_payer_signer = &create_signer::create_signer(gas_payer); if (transaction_fee_amount > storage_fee_refunded) { let burn_amount = transaction_fee_amount - storage_fee_refunded; transaction_fee::burn_fee(gas_payer, burn_amount); + permissioned_signer::check_permission_consume( + gas_payer_signer, + (burn_amount as u256), + GasPermission {} + ); } else if (transaction_fee_amount < storage_fee_refunded) { let mint_amount = storage_fee_refunded - transaction_fee_amount; - transaction_fee::mint_and_refund(gas_payer, mint_amount) + transaction_fee::mint_and_refund(gas_payer, mint_amount); + permissioned_signer::increase_limit( + gas_payer_signer, + (mint_amount as u256), + GasPermission {} + ); }; }; @@ -667,9 +711,19 @@ module aptos_framework::transaction_validation { if (transaction_fee_amount > storage_fee_refunded) { let burn_amount = transaction_fee_amount - storage_fee_refunded; transaction_fee::burn_fee(gas_payer_address, burn_amount); + permissioned_signer::check_permission_consume( + &gas_payer, + (burn_amount as u256), + GasPermission {} + ); } else if (transaction_fee_amount < storage_fee_refunded) { let mint_amount = storage_fee_refunded - transaction_fee_amount; - transaction_fee::mint_and_refund(gas_payer_address, mint_amount) + transaction_fee::mint_and_refund(gas_payer_address, mint_amount); + permissioned_signer::increase_limit( + &gas_payer, + (mint_amount as u256), + GasPermission {} + ); }; }; diff --git a/testsuite/smoke-test/src/lib.rs b/testsuite/smoke-test/src/lib.rs index 2df5effb1d7a3..29f3550bf6628 100644 --- a/testsuite/smoke-test/src/lib.rs +++ b/testsuite/smoke-test/src/lib.rs @@ -35,6 +35,8 @@ mod keyless; #[cfg(test)] mod network; #[cfg(test)] +mod permissioned_delegation; +#[cfg(test)] mod randomness; #[cfg(test)] mod rest_api; diff --git a/testsuite/smoke-test/src/permissioned_delegation.rs b/testsuite/smoke-test/src/permissioned_delegation.rs new file mode 100644 index 0000000000000..ce304667d49ea --- /dev/null +++ b/testsuite/smoke-test/src/permissioned_delegation.rs @@ -0,0 +1,108 @@ +// Copyright © Aptos Foundation +// SPDX-License-Identifier: Apache-2.0 + +use std::sync::Arc; +use crate::smoke_test_environment::SwarmBuilder; +use aptos_cached_packages::aptos_stdlib; +use aptos_forge::Swarm; +use aptos_types::function_info::FunctionInfo; +use aptos_crypto::SigningKey; +use move_core_types::account_address::AccountAddress; + +#[tokio::test(flavor = "multi_thread", worker_threads = 1)] +async fn test_permissioned_delegation() { + let (swarm, mut cli, _faucet) = SwarmBuilder::new_local(1) + .with_aptos() + .build_with_cli(0) + .await; + let mut info = swarm.aptos_public_info(); + + let mut account1 = info + .create_and_fund_user_account(100_000_000_000) + .await + .unwrap(); + let account2 = info.random_account(); + info.create_user_account(account2.public_key()) + .await + .unwrap(); + let account1_private_key = account1.private_key().clone(); + let account1_public_key = account1.public_key().clone(); + let idx = cli.add_account_to_cli(account1_private_key.clone()); + + // Setup permissions: 10 APT allowance, and 0.1 APT gas. + let script = format!( + r#" + script {{ + use std::string::utf8; + use aptos_std::ed25519; + use aptos_framework::coin; + use aptos_framework::permissioned_delegation; + use aptos_framework::primary_fungible_store; + use aptos_framework::transaction_validation; + use aptos_framework::account_abstraction; + fun main(sender: &signer) {{ + coin::migrate_to_fungible_store(sender); + let key = permissioned_delegation::gen_ed25519_key(ed25519::new_unvalidated_public_key_from_bytes(x"{}")); + let permissioned_signer = permissioned_delegation::add_permissioned_handle(sender, key, std::option::none(), {}); + primary_fungible_store::grant_apt_permission(sender, &permissioned_signer, 1000000000); // 10 apt + transaction_validation::grant_gas_permission(sender, &permissioned_signer, 100000000); // 1 apt because that is the max_gas + account_abstraction::add_dispatchable_authentication_function(sender, @aptos_framework, utf8(b"permissioned_delegation"), utf8(b"authenticate")); + }} + }} + "#, + hex::encode(account1_public_key.to_bytes()), + u64::max_value(), + ); + assert_eq!(Some(true), cli.run_script(idx, &script).await.unwrap().success); + account1.increment_sequence_number(); + + let func_info = FunctionInfo::new( + AccountAddress::ONE, + "permissioned_delegation".to_string(), + "authenticate".to_string(), + ); + account1.set_abstraction_auth(func_info, Arc::new(move |x: &[u8]| { + let mut authenticator = vec![]; + authenticator.extend(bcs::to_bytes(&account1_public_key.to_bytes().to_vec()).unwrap()); + authenticator.extend(bcs::to_bytes(&account1_private_key.sign_arbitrary_message(x).to_bytes().to_vec()).unwrap()); + authenticator + })); + + // Transfer 1 APT and 2 APT. + let transfer_txn = account1.sign_aa_transaction_with_transaction_builder( + vec![], + None, + info.transaction_factory() + .payload(aptos_stdlib::aptos_account_fungible_transfer_only(account2.address(), 100000000))); + info.client().submit_and_wait(&transfer_txn).await.unwrap(); + + // gas permission check failed. + let transfer_txn = account1.sign_aa_transaction_with_transaction_builder( + vec![], + None, + info.transaction_factory() + .payload(aptos_stdlib::aptos_account_fungible_transfer_only(account2.address(), 200000000))); + assert!(info.client().submit_and_wait(&transfer_txn).await.is_err()); + account1.decrement_sequence_number(); + + let transfer_txn = account1.sign_aa_transaction_with_transaction_builder( + vec![], + None, + info.transaction_factory() + .payload(aptos_stdlib::aptos_account_fungible_transfer_only(account2.address(), 200000000)).max_gas_amount(50000000)); + info.client().submit_and_wait(&transfer_txn).await.unwrap(); + + let transfer_txn = account1.sign_aa_transaction_with_transaction_builder( + vec![], + None, + info.transaction_factory() + .payload(aptos_stdlib::aptos_account_fungible_transfer_only(account2.address(), 700000001)).max_gas_amount(50000000)); + assert!(info.client().submit_and_wait(&transfer_txn).await.is_err()); + + let transfer_txn = account1.sign_aa_transaction_with_transaction_builder( + vec![], + None, + info.transaction_factory() + .payload(aptos_stdlib::aptos_account_fungible_transfer_only(account2.address(), 700000000)).max_gas_amount(50000000)); + info.client().submit_and_wait(&transfer_txn).await.unwrap(); +}