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();
+}