Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[framework] introduce private entry function for creating multisig account #14645

Merged
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions api/src/tests/multisig_transactions_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,66 @@ async fn test_multisig_transaction_with_payload_succeeds() {
assert_eq!(0, context.get_apt_balance(multisig_account).await);
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_multisig_transaction_with_existing_account() {
let mut context = new_test_context(current_function_name!());
let multisig_account = &mut context.create_account().await;
let owner_account_1 = &mut context.create_account().await;
let owner_account_2 = &mut context.create_account().await;
let owner_account_3 = &mut context.create_account().await;
let owners = vec![
owner_account_1.address(),
owner_account_2.address(),
owner_account_3.address(),
];
context
.create_multisig_account_with_existing_account(multisig_account, owners.clone(), 2, 1000)
.await;
assert_owners(&context, multisig_account.address(), owners).await;
assert_signature_threshold(&context, multisig_account.address(), 2).await;

let multisig_payload = construct_multisig_txn_transfer_payload(owner_account_1.address(), 1000);
context
.create_multisig_transaction(
owner_account_1,
multisig_account.address(),
multisig_payload.clone(),
)
.await;
// Owner 2 approves and owner 3 rejects. There are still 2 approvals total (owners 1 and 2) so
// the transaction can still be executed.
context
.approve_multisig_transaction(owner_account_2, multisig_account.address(), 1)
.await;
context
.reject_multisig_transaction(owner_account_3, multisig_account.address(), 1)
.await;

let org_multisig_balance = context.get_apt_balance(multisig_account.address()).await;
let org_owner_1_balance = context.get_apt_balance(owner_account_1.address()).await;

context
.execute_multisig_transaction(owner_account_2, multisig_account.address(), 202)
.await;

// The multisig tx that transfers away 1000 APT should have succeeded.
assert_multisig_tx_executed(
&mut context,
multisig_account.address(),
multisig_payload,
1,
)
.await;
assert_eq!(
org_multisig_balance - 1000,
context.get_apt_balance(multisig_account.address()).await
);
assert_eq!(
org_owner_1_balance + 1000,
context.get_apt_balance(owner_account_1.address()).await
);
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_multisig_transaction_to_update_owners() {
let mut context = new_test_context(current_function_name!());
Expand Down
20 changes: 20 additions & 0 deletions api/test-context/src/test_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,26 @@ impl TestContext {
multisig_address
}

pub async fn create_multisig_account_with_existing_account(
&mut self,
account: &mut LocalAccount,
owners: Vec<AccountAddress>,
signatures_required: u64,
initial_balance: u64,
) {
let factory = self.transaction_factory();
let txn = account.sign_with_transaction_builder(
factory
.create_multisig_account_with_existing_account(owners, signatures_required)
.expiration_timestamp_secs(u64::MAX),
);
self.commit_block(&vec![
txn,
self.account_transfer_to(account, account.address(), initial_balance),
])
.await;
}

pub async fn create_multisig_transaction(
&mut self,
owner: &mut LocalAccount,
Expand Down
2 changes: 1 addition & 1 deletion aptos-move/framework/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ The overall structure of the Aptos Framework is as follows:
├── aptos-token # Sources, testing and generated documentation for Aptos token component
├── aptos-stdlib # Sources, testing and generated documentation for Aptos stdlib component
├── move-stdlib # Sources, testing and generated documentation for Move stdlib component
├── cached-packages # Tooling to generate SDK from mvoe sources.
├── cached-packages # Tooling to generate SDK from move sources.
├── src # Compilation and generation of information from Move source files in the Aptos Framework. Not designed to be used as a Rust library
├── releases # Move release bundles
└── tests
Expand Down
101 changes: 101 additions & 0 deletions aptos-move/framework/aptos-framework/doc/multisig_account.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,9 @@ and implement the governance voting logic on top.
- [Function `next_sequence_number`](#0x1_multisig_account_next_sequence_number)
- [Function `vote`](#0x1_multisig_account_vote)
- [Function `available_transaction_queue_capacity`](#0x1_multisig_account_available_transaction_queue_capacity)
- [Function `create_with_existing_account_call`](#0x1_multisig_account_create_with_existing_account_call)
- [Function `create_with_existing_account`](#0x1_multisig_account_create_with_existing_account)
- [Function `create_with_existing_account_and_revoke_auth_key_call`](#0x1_multisig_account_create_with_existing_account_and_revoke_auth_key_call)
- [Function `create_with_existing_account_and_revoke_auth_key`](#0x1_multisig_account_create_with_existing_account_and_revoke_auth_key)
- [Function `create`](#0x1_multisig_account_create)
- [Function `create_with_owners`](#0x1_multisig_account_create_with_owners)
Expand Down Expand Up @@ -1932,6 +1934,50 @@ Return a bool tuple indicating whether an owner has voted and if so, whether the



</details>

<a id="0x1_multisig_account_create_with_existing_account_call"></a>

## Function `create_with_existing_account_call`

Private entry function that creates a new multisig account on top of an existing account.

This offers a migration path for an existing account with any type of auth key.

Note that this does not revoke auth key-based control over the account. Owners should separately rotate the auth
key after they are fully migrated to the new multisig account. Alternatively, they can call
create_with_existing_account_and_revoke_auth_key_call instead.


<pre><code>entry <b>fun</b> <a href="multisig_account.md#0x1_multisig_account_create_with_existing_account_call">create_with_existing_account_call</a>(<a href="multisig_account.md#0x1_multisig_account">multisig_account</a>: &<a href="../../aptos-stdlib/../move-stdlib/doc/signer.md#0x1_signer">signer</a>, owners: <a href="../../aptos-stdlib/../move-stdlib/doc/vector.md#0x1_vector">vector</a>&lt;<b>address</b>&gt;, num_signatures_required: u64, metadata_keys: <a href="../../aptos-stdlib/../move-stdlib/doc/vector.md#0x1_vector">vector</a>&lt;<a href="../../aptos-stdlib/../move-stdlib/doc/string.md#0x1_string_String">string::String</a>&gt;, metadata_values: <a href="../../aptos-stdlib/../move-stdlib/doc/vector.md#0x1_vector">vector</a>&lt;<a href="../../aptos-stdlib/../move-stdlib/doc/vector.md#0x1_vector">vector</a>&lt;u8&gt;&gt;)
</code></pre>



<details>
<summary>Implementation</summary>


<pre><code>entry <b>fun</b> <a href="multisig_account.md#0x1_multisig_account_create_with_existing_account_call">create_with_existing_account_call</a>(
<a href="multisig_account.md#0x1_multisig_account">multisig_account</a>: &<a href="../../aptos-stdlib/../move-stdlib/doc/signer.md#0x1_signer">signer</a>,
owners: <a href="../../aptos-stdlib/../move-stdlib/doc/vector.md#0x1_vector">vector</a>&lt;<b>address</b>&gt;,
num_signatures_required: u64,
metadata_keys: <a href="../../aptos-stdlib/../move-stdlib/doc/vector.md#0x1_vector">vector</a>&lt;String&gt;,
metadata_values: <a href="../../aptos-stdlib/../move-stdlib/doc/vector.md#0x1_vector">vector</a>&lt;<a href="../../aptos-stdlib/../move-stdlib/doc/vector.md#0x1_vector">vector</a>&lt;u8&gt;&gt;,
) <b>acquires</b> <a href="multisig_account.md#0x1_multisig_account_MultisigAccount">MultisigAccount</a> {
<a href="multisig_account.md#0x1_multisig_account_create_with_owners_internal">create_with_owners_internal</a>(
<a href="multisig_account.md#0x1_multisig_account">multisig_account</a>,
owners,
num_signatures_required,
<a href="../../aptos-stdlib/../move-stdlib/doc/option.md#0x1_option_none">option::none</a>&lt;SignerCapability&gt;(),
metadata_keys,
metadata_values,
);
}
</code></pre>



</details>

<a id="0x1_multisig_account_create_with_existing_account"></a>
Expand Down Expand Up @@ -2002,6 +2048,61 @@ create_with_existing_account_and_revoke_auth_key instead.



</details>

<a id="0x1_multisig_account_create_with_existing_account_and_revoke_auth_key_call"></a>

## Function `create_with_existing_account_and_revoke_auth_key_call`

Private entry function that creates a new multisig account on top of an existing account and immediately rotate
the origin auth key to 0x0.

Note: If the original account is a resource account, this does not revoke all control over it as if any
SignerCapability of the resource account still exists, it can still be used to generate the signer for the
account.


<pre><code>entry <b>fun</b> <a href="multisig_account.md#0x1_multisig_account_create_with_existing_account_and_revoke_auth_key_call">create_with_existing_account_and_revoke_auth_key_call</a>(<a href="multisig_account.md#0x1_multisig_account">multisig_account</a>: &<a href="../../aptos-stdlib/../move-stdlib/doc/signer.md#0x1_signer">signer</a>, owners: <a href="../../aptos-stdlib/../move-stdlib/doc/vector.md#0x1_vector">vector</a>&lt;<b>address</b>&gt;, num_signatures_required: u64, metadata_keys: <a href="../../aptos-stdlib/../move-stdlib/doc/vector.md#0x1_vector">vector</a>&lt;<a href="../../aptos-stdlib/../move-stdlib/doc/string.md#0x1_string_String">string::String</a>&gt;, metadata_values: <a href="../../aptos-stdlib/../move-stdlib/doc/vector.md#0x1_vector">vector</a>&lt;<a href="../../aptos-stdlib/../move-stdlib/doc/vector.md#0x1_vector">vector</a>&lt;u8&gt;&gt;)
</code></pre>



<details>
<summary>Implementation</summary>


<pre><code>entry <b>fun</b> <a href="multisig_account.md#0x1_multisig_account_create_with_existing_account_and_revoke_auth_key_call">create_with_existing_account_and_revoke_auth_key_call</a>(
<a href="multisig_account.md#0x1_multisig_account">multisig_account</a>: &<a href="../../aptos-stdlib/../move-stdlib/doc/signer.md#0x1_signer">signer</a>,
owners: <a href="../../aptos-stdlib/../move-stdlib/doc/vector.md#0x1_vector">vector</a>&lt;<b>address</b>&gt;,
num_signatures_required: u64,
metadata_keys: <a href="../../aptos-stdlib/../move-stdlib/doc/vector.md#0x1_vector">vector</a>&lt;String&gt;,
metadata_values:<a href="../../aptos-stdlib/../move-stdlib/doc/vector.md#0x1_vector">vector</a>&lt;<a href="../../aptos-stdlib/../move-stdlib/doc/vector.md#0x1_vector">vector</a>&lt;u8&gt;&gt;,
) <b>acquires</b> <a href="multisig_account.md#0x1_multisig_account_MultisigAccount">MultisigAccount</a> {
<a href="multisig_account.md#0x1_multisig_account_create_with_owners_internal">create_with_owners_internal</a>(
<a href="multisig_account.md#0x1_multisig_account">multisig_account</a>,
owners,
num_signatures_required,
<a href="../../aptos-stdlib/../move-stdlib/doc/option.md#0x1_option_none">option::none</a>&lt;SignerCapability&gt;(),
metadata_keys,
metadata_values,
);

// Rotate the <a href="account.md#0x1_account">account</a>'s auth key <b>to</b> 0x0, which effectively revokes control via auth key.
<b>let</b> multisig_address = address_of(<a href="multisig_account.md#0x1_multisig_account">multisig_account</a>);
<a href="account.md#0x1_account_rotate_authentication_key_internal">account::rotate_authentication_key_internal</a>(<a href="multisig_account.md#0x1_multisig_account">multisig_account</a>, <a href="multisig_account.md#0x1_multisig_account_ZERO_AUTH_KEY">ZERO_AUTH_KEY</a>);
// This also needs <b>to</b> revoke <a href="../../aptos-stdlib/doc/any.md#0x1_any">any</a> <a href="../../aptos-stdlib/../move-stdlib/doc/signer.md#0x1_signer">signer</a> capability or rotation capability that <b>exists</b> for the <a href="account.md#0x1_account">account</a> <b>to</b>
// completely remove all access <b>to</b> the <a href="account.md#0x1_account">account</a>.
<b>if</b> (<a href="account.md#0x1_account_is_signer_capability_offered">account::is_signer_capability_offered</a>(multisig_address)) {
<a href="account.md#0x1_account_revoke_any_signer_capability">account::revoke_any_signer_capability</a>(<a href="multisig_account.md#0x1_multisig_account">multisig_account</a>);
};
<b>if</b> (<a href="account.md#0x1_account_is_rotation_capability_offered">account::is_rotation_capability_offered</a>(multisig_address)) {
<a href="account.md#0x1_account_revoke_any_rotation_capability">account::revoke_any_rotation_capability</a>(<a href="multisig_account.md#0x1_multisig_account">multisig_account</a>);
};
}
</code></pre>



</details>

<a id="0x1_multisig_account_create_with_existing_account_and_revoke_auth_key"></a>
Expand Down
107 changes: 107 additions & 0 deletions aptos-move/framework/aptos-framework/sources/multisig_account.move
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,30 @@ module aptos_framework::multisig_account {

////////////////////////// Multisig account creation functions ///////////////////////////////

/// Private entry function that creates a new multisig account on top of an existing account.
///
/// This offers a migration path for an existing account with any type of auth key.
///
/// Note that this does not revoke auth key-based control over the account. Owners should separately rotate the auth
/// key after they are fully migrated to the new multisig account. Alternatively, they can call
/// create_with_existing_account_and_revoke_auth_key_call instead.
entry fun create_with_existing_account_call(
multisig_account: &signer,
owners: vector<address>,
num_signatures_required: u64,
metadata_keys: vector<String>,
metadata_values: vector<vector<u8>>,
) acquires MultisigAccount {
create_with_owners_internal(
multisig_account,
owners,
num_signatures_required,
option::none<SignerCapability>(),
metadata_keys,
metadata_values,
);
}

/// Creates a new multisig account on top of an existing account.
///
/// This offers a migration path for an existing account with a multi-ed25519 auth key (native multisig account).
Expand Down Expand Up @@ -547,6 +571,41 @@ module aptos_framework::multisig_account {
);
}

/// Private entry function that creates a new multisig account on top of an existing account and immediately rotate
/// the origin auth key to 0x0.
///
/// Note: If the original account is a resource account, this does not revoke all control over it as if any
/// SignerCapability of the resource account still exists, it can still be used to generate the signer for the
/// account.
entry fun create_with_existing_account_and_revoke_auth_key_call(
multisig_account: &signer,
owners: vector<address>,
num_signatures_required: u64,
metadata_keys: vector<String>,
metadata_values:vector<vector<u8>>,
) acquires MultisigAccount {
create_with_owners_internal(
multisig_account,
owners,
num_signatures_required,
option::none<SignerCapability>(),
metadata_keys,
metadata_values,
);

// Rotate the account's auth key to 0x0, which effectively revokes control via auth key.
let multisig_address = address_of(multisig_account);
account::rotate_authentication_key_internal(multisig_account, ZERO_AUTH_KEY);
// This also needs to revoke any signer capability or rotation capability that exists for the account to
// completely remove all access to the account.
if (account::is_signer_capability_offered(multisig_address)) {
account::revoke_any_signer_capability(multisig_account);
};
if (account::is_rotation_capability_offered(multisig_address)) {
account::revoke_any_rotation_capability(multisig_account);
};
}

/// Creates a new multisig account on top of an existing account and immediately rotate the origin auth key to 0x0.
///
/// Note: If the original account is a resource account, this does not revoke all control over it as if any
Expand Down Expand Up @@ -1688,6 +1747,26 @@ module aptos_framework::multisig_account {
);
}

#[test]
public entry fun test_create_multisig_account_on_top_of_existing_with_signer()
acquires MultisigAccount {
setup();

let multisig_address = @0xabc;
create_account(multisig_address);

let expected_owners = vector[@0x123, @0x124, @0x125];
create_with_existing_account_call(
&create_signer(multisig_address),
expected_owners,
2,
vector[],
vector[],
);
assert_multisig_account_exists(multisig_address);
assert!(owners(multisig_address) == expected_owners, 0);
}

#[test]
public entry fun test_create_multisig_account_on_top_of_existing_multi_ed25519_account()
acquires MultisigAccount {
Expand Down Expand Up @@ -1721,6 +1800,34 @@ module aptos_framework::multisig_account {
assert!(owners(multisig_address) == expected_owners, 0);
}

#[test]
public entry fun test_create_multisig_account_on_top_of_existing_and_revoke_auth_key_with_signer()
acquires MultisigAccount {
setup();

let multisig_address = @0xabc;
create_account(multisig_address);

// Create both a signer capability and rotation capability offers
account::set_rotation_capability_offer(multisig_address, @0x123);
account::set_signer_capability_offer(multisig_address, @0x123);

let expected_owners = vector[@0x123, @0x124, @0x125];
create_with_existing_account_and_revoke_auth_key_call(
&create_signer(multisig_address),
expected_owners,
2,
vector[],
vector[],
);
assert_multisig_account_exists(multisig_address);
assert!(owners(multisig_address) == expected_owners, 0);
assert!(account::get_authentication_key(multisig_address) == ZERO_AUTH_KEY, 1);
// Verify that all capability offers have been wiped.
assert!(!account::is_rotation_capability_offered(multisig_address), 2);
assert!(!account::is_signer_capability_offered(multisig_address), 3);
}

#[test]
public entry fun test_create_multisig_account_on_top_of_existing_multi_ed25519_account_and_revoke_auth_key()
acquires MultisigAccount {
Expand Down
Loading
Loading