diff --git a/aptos-move/framework/aptos-framework/doc/transaction_validation.md b/aptos-move/framework/aptos-framework/doc/transaction_validation.md
index 90b0e01a7a56f..77c61310c5150 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)
@@ -28,6 +31,8 @@
- [Specification](#@Specification_1)
- [High-level Requirements](#high-level-req)
- [Module-level Specification](#module-level-spec)
+ - [Function `grant_gas_permission`](#@Specification_1_grant_gas_permission)
+ - [Function `revoke_gas_permission`](#@Specification_1_revoke_gas_permission)
- [Function `initialize`](#@Specification_1_initialize)
- [Function `prologue_common`](#@Specification_1_prologue_common)
- [Function `script_prologue`](#@Specification_1_script_prologue)
@@ -57,6 +62,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 +129,33 @@ correct chain-specific prologue and epilogue functions
+
+
+
+
+## Struct `GasPermission`
+
+
+
+
struct GasPermission has copy, drop, store
+
+
+
+
+
+Fields
+
+
+
+-
+
dummy_field: bool
+
+-
+
+
+
+
+
@@ -243,6 +276,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 +485,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),
@@ -965,7 +1076,7 @@ Called by the Adapter
transaction_fee::burn_fee(gas_payer, burn_amount);
} 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);
};
};
@@ -1195,9 +1306,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 {}
+ );
};
};
@@ -1267,6 +1388,38 @@ If there is no fee_payer, fee_payer = sender
+
+
+### Function `grant_gas_permission`
+
+
+public fun grant_gas_permission(master: &signer, permissioned: &signer, gas_amount: u64)
+
+
+
+
+
+pragma aborts_if_is_partial;
+
+
+
+
+
+
+### Function `revoke_gas_permission`
+
+
+public fun revoke_gas_permission(permissioned: &signer)
+
+
+
+
+
+pragma aborts_if_is_partial;
+
+
+
+
### Function `initialize`
@@ -1287,58 +1440,6 @@ Aborts if TransactionValidation already exists.
-Create a schema to reuse some code.
-Give some constraints that may abort according to the conditions.
-
-
-
-
-
-schema PrologueCommonAbortsIf {
- sender: &signer;
- gas_payer: &signer;
- txn_sequence_number: u64;
- txn_authentication_key: Option<vector<u8>>;
- txn_gas_price: u64;
- txn_max_gas_units: u64;
- txn_expiration_time: u64;
- chain_id: u8;
- aborts_if !exists<CurrentTimeMicroseconds>(@aptos_framework);
- aborts_if !(timestamp::now_seconds() < txn_expiration_time);
- aborts_if !exists<ChainId>(@aptos_framework);
- aborts_if !(chain_id::get() == chain_id);
- let transaction_sender = signer::address_of(sender);
- let gas_payer_addr = signer::address_of(gas_payer);
- aborts_if (
- !features::spec_is_enabled(features::SPONSORED_AUTOMATIC_ACCOUNT_CREATION)
- || account::exists_at(transaction_sender)
- || transaction_sender == gas_payer_addr
- || txn_sequence_number > 0
- ) && (
- !(txn_sequence_number >= global<Account>(transaction_sender).sequence_number)
- || !(option::spec_is_none(txn_authentication_key) || option::spec_borrow(
- txn_authentication_key
- ) == global<Account>(transaction_sender).authentication_key)
- || !account::exists_at(transaction_sender)
- || !(txn_sequence_number == global<Account>(transaction_sender).sequence_number)
- );
- aborts_if features::spec_is_enabled(features::SPONSORED_AUTOMATIC_ACCOUNT_CREATION)
- && transaction_sender != gas_payer_addr
- && txn_sequence_number == 0
- && !account::exists_at(transaction_sender)
- && (option::spec_is_none(txn_authentication_key) || option::spec_borrow(
- txn_authentication_key
- ) != bcs::to_bytes(transaction_sender));
- aborts_if !(txn_sequence_number < (1u64 << 63));
- let max_transaction_fee = txn_gas_price * txn_max_gas_units;
- aborts_if max_transaction_fee > MAX_U64;
- aborts_if !exists<CoinStore<AptosCoin>>(gas_payer_addr);
- // This enforces high-level requirement 1:
- aborts_if !(global<CoinStore<AptosCoin>>(gas_payer_addr).coin.value >= max_transaction_fee);
-}
-
-
-
diff --git a/aptos-move/framework/aptos-framework/sources/transaction_validation.move b/aptos-move/framework/aptos-framework/sources/transaction_validation.move
index c70a191e1784f..87c06a9113431 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),
@@ -534,7 +567,7 @@ module aptos_framework::transaction_validation {
transaction_fee::burn_fee(gas_payer, burn_amount);
} 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);
};
};
@@ -667,9 +700,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/aptos-move/framework/aptos-framework/sources/transaction_validation.spec.move b/aptos-move/framework/aptos-framework/sources/transaction_validation.spec.move
index 461be606cba87..ec657e448c3d1 100644
--- a/aptos-move/framework/aptos-framework/sources/transaction_validation.spec.move
+++ b/aptos-move/framework/aptos-framework/sources/transaction_validation.spec.move
@@ -28,6 +28,18 @@ spec aptos_framework::transaction_validation {
pragma aborts_if_is_strict;
}
+ spec grant_gas_permission(
+ master: &signer,
+ permissioned: &signer,
+ gas_amount: u64
+ ) {
+ pragma aborts_if_is_partial;
+ }
+
+ spec revoke_gas_permission(permissioned: &signer) {
+ pragma aborts_if_is_partial;
+ }
+
/// Ensure caller is `aptos_framework`.
/// Aborts if TransactionValidation already exists.
spec initialize(
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..84cb7ca68e917
--- /dev/null
+++ b/testsuite/smoke-test/src/permissioned_delegation.rs
@@ -0,0 +1,145 @@
+// Copyright © Aptos Foundation
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::smoke_test_environment::SwarmBuilder;
+use aptos_cached_packages::aptos_stdlib;
+use aptos_crypto::SigningKey;
+use aptos_forge::Swarm;
+use aptos_types::function_info::FunctionInfo;
+use move_core_types::account_address::AccountAddress;
+use std::sync::Arc;
+
+#[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();
+}