Open JackyDisc opened 1 year ago
@sblackshear @kchalkias
Thanks for the writeup! I'm still reading things through, but at a first glance for these abstractions around validation/prologues/epilogues, you should be able to achieve that with programmable transactions #7790
You can make a module and an object that represents the account. Then set up entry functions for validation prologue and epilogue.
You can enforce the state transition of validation
then prologue
then epilogue
with capabilities. Changing your examples a bit
module 0xexample::aa_example {
struct ValidationReceipt { /* some useful validation metadata */ ... }
struct PrologueReceipt { /* some useful object metadata */ ... }
entry fun validate_tx(ctx: &mut TxContext): ValidationReceipt {
...
}
entry fun take_object<T: key + store>(validation: &ValidationReceipt, id: ID, ctx: mut &TxContext): (T, PrologueReceipt) {
...
}
entry fun return_object<T: key + store>(t: T, receipt, PrologueReceipt, ctx: mut &TxContext) {
...
}
This doesn't sending objects to the account, but I'll write something up about that in a bit.
Similar but totally different.
The core of the account abstraction is to create a new account type that do transaction signature verification through code, instead of private key / public key. The new account type is orthogonal to transaction kinds, and execute all existing transaction types (including programmable transaction kind when implemented).
An exemplary use case of account abstraction is multi-sig (The existing multi-account schema cannot fulfill user needs as mentioned in 3.2). But with account abstraction, a multi-sig account can be created, which does not belong to any single account, and go fast path consensus. And the application can define customized threshold & role based permission control, owner change e.t.c. This will give full flexibility
I am aware SUI has many internal discussions regarding the extensive use cases of wallet user-experience, and have many ongoing proposals. Adding more support on node infra is straight-forward but not scalable since a framework is not supposed to handle the same level of complexity as in applications. I would recommend account abstraction as the key framework that allows application defining logics for account execution, offering a solution for work-around in absence of dynamic function dispatch in SUI move. All applications can define customized rules and enable following features to be built in a decentralized way on top of AA: Multi-signature, MFA, role based permission control, granularized access control, account recovery, general purpose DAO toolings, pre-approve transactions, e.t.c.
- The core of the account abstraction is to create a new account type that do transaction signature verification through code, instead of private key / public key. The new account type is orthogonal to transaction kinds, and execute all existing transaction types (including programmable transaction kind when implemented).
It's a neat idea! But a bit tricky to get right. We could add more signature schemes over time, but being able to do signature verification through code means that the code has to be metered. And to be metered it means you need a certificate from the validators. In other words, you can't really pay to do the verification until you've already verified that you have some SUI to pay for gas. And you need a certificate to do that.
In other words, I don't see a way to allow for arbitrary code to be executed safely during signing/certificate generation. The only way I see is to have an object that anyone can access (a shared object), and then do the verification in the Move layer, which brings us to...
- An exemplary use case of account abstraction is multi-sig (The existing multi-account schema cannot fulfill user needs as mentioned in 3.2). But with account abstraction, a multi-sig account can be created, which does not belong to any single account, and go fast path consensus. And the application can define customized threshold & role based permission control, owner change e.t.c. This will give full flexibility
We can't get into the single-writer path with arbitrary code being executed for signature verification. This was the point of my example above, you can implement this behavior with programmable transactions on top of shared objects.
I am aware SUI has many internal discussions regarding the extensive use cases of wallet user-experience, and have many ongoing proposals. Adding more support on node infra is straight-forward but not scalable since a framework is not supposed to handle the same level of complexity as in applications. I would recommend account abstraction as the key framework that allows application defining logics for account execution, offering a solution for work-around in absence of dynamic function dispatch in SUI move. All applications can define customized rules and enable following features to be built in a decentralized way on top of AA: Multi-signature, MFA, role based permission control, granularized access control, account recovery, general purpose DAO toolings, pre-approve transactions, e.t.c.
Programmable transactions + capabilities (as showing in the example with things like ValidationReceipt
) can let you do adhoc things without having to publish new code.
Though to simulate this all with shared objects, you will need the ability to send to objects instead of an address, which is something we are also working on.
In other words, I don't see a way to allow for arbitrary code to be executed safely during signing/certificate generation. The only way I see is to have an object that anyone can access (a shared object), and then do the verification in the Move layer, which brings us to...
Exactly! The validation definitely need to be metered to prevent DDoS on both node level and gas level. As I mentioned in 2.3.1 gas DDoS attack, the signing/certificate validation is validated in AA code validate_tx
, which has a hard limit in gas used in this function. The limit shall have the same magnitude as a signing schema verification. It's supposed to only do light signature verifications, no heavy lifting.
We can't get into the single-writer path with arbitrary code being executed for signature verification. This was the point of my example above, you can implement this behavior with programmable transactions on top of shared objects.
100% agree. single-writer path is not possible under the current framework. This is why I am proposing this feature request. Account abstraction allows implementation without shared objects and can fit into single-writer path. Basically, the shared object of the multi-sig is abstracted to a single account. Please check 2.2.3.
Programmable transactions + capabilities (as showing in the example with things like ValidationReceipt) can let you do adhoc things without having to publish new code. Though to simulate this all with shared objects, you will need the ability to send to objects instead of an address, which is something we are also working on.
Yes, programmable transactions is very very powerful, and it can also be used as a work around for dynamic function dispatch to some extend. These are all great! But it is different from the problem AA is trying to solve.
So I think these two features are orthogonal and is trying to solve different problems, although some functionality can be possibly implemented in either ways :)
Here is my understanding of the programmable transactions for a scenario where three users A, B, C want to swap some coin owned by mutual fund of A, B, C.
With programmable transaction kind:
The biggest issue with this solution is that, B & C can only approve step 3.a for fund withdraw. But step 3.b & 3.c is only proposed by A and cannot be verified by B and C because of the ad-hoc nature of the programmable transaction.
Do you think this is the right way to use programmable transaction?
On the other hand, account abstraction is able to implement in a more straight forward way:
If the transaction to be executed is single writer friendly, the whole process can go with single-writer path.
Do you guys have any concerns regarding executing the move code in abstracted account? Please let me know any concerns you may have. Thanks!
I know the account abstraction is not a trivial feature. But the gains could be potentially even bigger compared to the difficulties to implement it :)
Here is my understanding of the programmable transactions for a scenario where three users A, B, C want to swap some coin owned by mutual fund of A, B, C.
With programmable transaction kind:
- A, B, and C Create the shared move object multi-sig via smart contract, and put some assets in the multi-sig.
- A collect signature from B and C to send some amount of coin from multi-sig to A
- A programmable transaction is submitted to do the coin swap, including steps: a. withdraw coin from multi-sig to A's account. b. A account execute the coin swap. c. A return the swapped coin to multi-sig account.
The biggest issue with this solution is that, B & C can only approve step 3.a for fund withdraw. But step 3.b & 3.c is only proposed by A and cannot be verified by B and C because of the ad-hoc nature of the programmable transaction.
Do you think this is the right way to use programmable transaction?
On the other hand, account abstraction is able to implement in a more straight forward way:
- A, B, C create an abstracted account as a multi-sig with its own address.
- User A propose a transaction to swap a the coin.
- User B, C approve the transaction.
- The abstracted account execute the swap.
If the transaction to be executed is single writer friendly, the whole process can go with single-writer path.
Do you guys have any concerns regarding executing the move code in abstracted account? Please let me know any concerns you may have. Thanks!
With the exception of the single writer path, there is nothing in account abstraction as proposed that cannot be implemented with programmable transactions + send to object (which I know I have not described here but bear with me).
You can implement exactly the AA way with shared objects.
struct Account has key { id: UID, /* signature information */, objects: Bag<ObjectID> }
Send to object would expose something that would allow people to call transfer
where instead of passing in an address
they pass in a ObjectID
for the Account
I have some concerns for step 2.
This could be done either by creating a new shared object that B and C can come and sign.
AFAIC, one of the biggest limitation in this step is absence of dynamic function dispatch. If the multi-sig functionality is limited to swap or a certain number of entry functions, the described solution will work. But what if the multi-sig owners want to execute an arbitrary kind of entry functions (E.g. Stake, lend, swap, GameFi, or some protocol governance entry function calls)? Is there any work around other than the multi-sig smart contract writing adapters for each smart contracts to interact with?
Coordinating off chain and signing a programmable transaction that does this all lock-step.
Swap is a stateless function, and the process might create some problem when the application is stateful. E.g. A certain smart contract is recording the account address for future airdrops. From what we have in the programmable transaction, user will need to withdraw the assets to some account, and it's the account holding the asset that execute the transaction, not the shared object. Thus the beneficiary is account A instead of the shared multi-sig object. This also applies to most account based games where the account is the beneficiary for consuming some in-app assets, and shared account is not possible in this case.
How do you think programmable transaction can solve these issues?
@tnowacki Adding one more concerns for the programmable transaction: Currently an object cannot cannot be the recipient of the transfer::transfer
function. The dynamic_object_field
can partially solve the issue, but there will be some friction when other wallet is transferring some object to the multi-sig. How do you think this issue can be resolved?
@JackyDisc I think this is solved and an object can be the owner of an object now.
1. Motivation
We propose the following account abstraction model to enable general purpose smart contract wallet.
Compared with EOA, smart contract wallet can support numerous use cases such as account recovery, granulated permission control, role based multi-account management, pre-approve transactions, meta transactions, multi-factor authentication, e.t.c. We believe this will fundamentally change user experience with wallets, help adoption of applications on Sui.
Few examples of smart contract wallet capabilities:
However, a general purpose smart contract wallet cannot be implemented in the current SUI MOVE implementation because:
TxContext
can only be constructed for EOA accounts.Goals and scope
Here we propose the framework of account abstraction (Abbr. AA in following) that enables:
2 Design
2.1 Abstracted account
2.1.1 Definition
TxContext
is constructed for AA in rust.2.1.2
AbstractedAccountData
AbstractedAccountData
.AbstractedAccountData
is owned by the abstracted account, and is not transferrable.AbstractedAccountData
points to an immutable object with dataMovePackage
which contains the compiled code of the AA account.Move object
AbstractedAccountData
has the following data structure.Why do we expect a one-to-one mapping from AA to
AbstractedAccountData
?For EOA account, the validation of tx execution is defined by crypto signature verification with public key / private key, which is a one-on-one mapping to the address. For abstracted account, the validation is only defined by the AA code execution along with the object owned by AA. Thus the
AbstractedAccountData
object can be the only dependancy for the AA address.2.1.3 AA public key & address
**Public key**
Since an abstracted account is uniquely defined by the the special object
AbstractedAccountData
, the public key and address of the AA can be dependant on the object ID ofAbstractedAccountData
. Public key can be defined as the concatenation of:AbtractedAccountData
.**Address**
The address of the AA is the hash of the public key, just as other accounts.
2.1.4 AA creation & upgrade
Both AA creation and upgrade can be done through a native function call. The native function call can be written in a SUI framework move file (e.g.
abstracted_account.move
)**AA Creation**
Behind the native function call, the following logic will be executed:
aaPackageID
is validated against the AA function signatures (2.2.1).AbstractedAccountData
is created with an object ID derived fromTxContext
.AbstractedAccountData
is set to the AA address as computed from 2.1.3.****AA code upgrade****
AA code can only be upgraded by the abstracted account himself.
newAAPackageID
is validated against the AA function signatures (2.2.1).AbstractedAccountData
is updated with new package id and version.2.2 Transaction execution
2.2.1 AA package signature
Within the AA move package, some pre-defined function name and signatures must be implemented. The signatures are defined by the system and is called during transaction execution from abstracted accounts.
validate_tx
: Customized validation rule being executed at transaction mem pool.AAObjects
is a list of objects that contains data for validation.ValidateContext
contains datainput
andtxPayload
which is constructed in rust to provide more context for transaction validation.validate_tx
will be kicked out of mem pool, and does not consume gas fee.tx_exec_prologue
(Optional for AA) : Logic being executed before the execution of the transaction payload.tx_exec_prologue
will consume gas in abstracted account whether fails or not.PrologueContext
provides additional message from transaction arguments for extra validation.post_tx_exec
(Optional for AA): Logic being executed after the execution of transaction payload.post_tx_exec
will consume gas in abstracted account whether fails or not.PostExecContext
is constructed in rust, and contains information about the transaction execution result (Events are put intoexecEventBag
)AAObjects: vector<UID>
for AA processing.2.2.2 Transaction inputs
Abstracted accounts is able to execute any
TransactionKind
as other EOA accounts. The different is that EOA accounts use signatures for validation, and AA use some customized inputs instead of the signature field for validation.Here is the full list of data need to be included in the “signature” field for AA transactions:
validate_tx
.tx_exec_prologue
(if the function exists in AA code).post_tx_exec
(if the function exists in AA code).ValidateContext.input
andPrologueContext.input
.Thus the “signature” field for AA transactions can be defined as:
2.2.3 Transaction execution
Thus we have the full process of transaction exeuction process for abstracted account.
AbstractedAccountData
(See 2.1.3)ValidateContext
, and execute the AA functionvalidate_tx
tx_exec_prologue
is executed withPrologueContext
.post_tx_exec
is executed withPostExecContext
2.3 Discussion
2.3.1 gas DDoS attacks
One issue of the AA is about DDoS attack on gas. Since the transaction sent by AA will consume gas fee from AA account, it’s possible that some malicious account send overwhelmingly large number of transactions that drains the gas fee from AA account.
The solution to this issue is to introduce the function:
validate_tx
as in 2.1.1.validate_tx
shall be a light-weighted validation function with a hard gas cap that does not consume gas fee if validation fails. The operation is supposed to consume gas fee which have the same magnitude as a single signature validation of EOA accounts. Thus, the issue of DDoS gas attack is resolved byvalidate_tx
.Also executing the
validate_tx
in the tx mempool prevents the DDoS attack from node level - It shall be not much more computation/resource resource consumed than normal transaction execution for signature verification.2.3.2 Flexibility & Horizontal scale
The framework of AA is designed for horizontal scalability as SUI object system. The transaction code (AA code and transaction execution) have the flexibility to execute the transaction in fast path or global BFT consensus.
Thus, the AA design will inherit the full flexibility as SUI move system.
3 Extensive use cases
3.1 Smart contract wallet
As mentioned in motivation, one use case of the account abstraction is to implement the smart contract wallet that will provide a bunch of new features in addition to EOA accounts. The following features can be fully supported for abstracted smart contract wallet:
3.2 Multi-sig wallet
For multi-signature wallet, although there is a native support of multi-account signer schema (https://github.com/MystenLabs/sui/issues/7187), it is not able to fulfill the full need for smart contract multi-sig, since:
Multi-sig with account abstraction is able to implement all features with horizontal scale.
3.3 DAO management tools
DAO governance & management can be implemented with account abstraction. The DAO tooling can take user held tokens as votes to vote on a specific proposal, and the proposal can be executed as the payload of the abstracted account transaction.
3.4 Sponsored gas fee (paymaster)
Sponsored gas is also able to be implemented with abstracted account. By adding some additional pre-defined functionality in AA code, an account can use another account to pay for gas fee. This will help reduce the friction of mass adoption of SUI network. The feature will be requested in another issue.
3.5 Some other use cases
Account abstraction can be the core feature that support the decentralized implementation of the following features:
Disclaimer:
We have been working on the design internally with careful considerations, sharing it here to get community feedbacks and suggestions.
Would love to explore the options with you and the opportunities it will bring.
References: