Skip to content
This repository has been archived by the owner on Jun 20, 2024. It is now read-only.

Commit

Permalink
📝 edit of accounts subchapter
Browse files Browse the repository at this point in the history
  • Loading branch information
omarespejel committed Oct 19, 2023
1 parent 1006a1b commit f1b3e21
Showing 1 changed file with 50 additions and 187 deletions.
237 changes: 50 additions & 187 deletions src/ch04-01-accounts.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,10 @@
# Account Contracts

Now that we know better the concept of AA, let’s actually code it in
Starknet.
With a clearer understanding of the AA concept, let's proceed to code it in Starknet.

## Account Contract Interface (SNIP-6)
## Account Contract Interface

Account contracts, while essentially being smart contracts,
differentiate themselves through unique methods. A smart contract gains
the status of an account contract when it implements the public interface as
described by SNIP-6 ([StarkNet Improvement Proposal-6: Standard Account
Interface](https://github.com/ericnordelo/SNIPs/blob/feat/standard-account/SNIPS/snip-6.md)).
This standard borrows from SRC-6 and SRC-5, which are akin to Ethereum’s
ERCs, setting application-level conventions and contract standards.
Account contracts, being a type of smart contracts, are distinguished by specific methods. A smart contract becomes an account contract when it follows the public interface outlined in SNIP-6 ([StarkNet Improvement Proposal-6: Standard Account Interface](https://github.com/ericnordelo/SNIPs/blob/feat/standard-account/SNIPS/snip-6.md)). This standard draws inspiration from SRC-6 and SRC-5, similar to Ethereum's ERCs, which establish application conventions and contract standards.


```rust
Expand Down Expand Up @@ -54,33 +47,13 @@ trait ISRC5 {

```

As seen in the proposal above, an account contract must implement
at least the methods `__execute__`, `__validate__` and `is_valid_signature`,
formulated in the `ISRC6` trait.
From the proposal, an account contract should have the `__execute__`, `__validate__`, and `is_valid_signature` methods from the `ISRC6` trait.

The provided functions serve these purposes:

The functions represented above serve the following purposes:

- `__validate__`: This function takes in the list of calls scheduled
for execution and validates them in line with the rules specified in
the contract. It returns a short string (e.g., VALID), as opposed to a
boolean, encapsulated within a `felt252` that signifies the validation outcome.
This is because in Cairo, a short string is simply the ASCII representation
of a single felt and not a real string. Which is why the return type is `felt252`.
If the signature verification fails, you can return literally any other felt that
is not the aforementioned short string. The number `0` is a common choice.

- `is_valid_signature`: This function is tasked with validating the
signature of a transaction. It receives a hash of the transaction
data and a signature, which may be validated against a public key or
through any other method as specified by the contract creator. It
returns a short string (e.g., VALID) encapsulated within a `felt252`
that signifies the validation outcome.

- `__execute__`: Post-validation, the `__execute__` function is
responsible for executing an array of contract calls (as `Call`
structs). It returns an array of `Span<felt252>` structs
representing the return values of the executed calls.
- `__validate__`: Validates a list of calls intended for execution based on the contract's rules. Instead of a boolean, it returns a short string like 'VALID' within a `felt252` to convey validation results. In Cairo, this short string is the ASCII representation of a single felt. If verification fails, any felt other than 'VALID' can be returned. Often, `0` is chosen.
- `is_valid_signature`: Confirms the authenticity of a transaction's signature. It takes a transaction data hash and a signature, and compares it against a public key or another method chosen by the contract's author. The result is a short 'VALID' string within a `felt252`.
- `__execute__`: After validation, `__execute__` carries out a series of contract calls (as `Call` structs). It gives back an array of `Span<felt252>` structs, showing the return values of those calls.

Moreover, the `SNIP-5` (Standard Interface Detection) trait needs to be
defined with a function called `supports_interface`. This function
Expand All @@ -93,19 +66,9 @@ interface ID and returning a boolean.
}
```

In summary, when a user sends an `invoke` transaction, the first thing the
protocol does is to call the `__validate__` method to authenticate the signer
associated with the account. Note that there are restrictions on what you
can do inside the `__validate__` method to protect the Sequencer against
Denial of Service (DoS) attacks [1]. If the signature verification is
successful, it returns VALID felt252 element. If it fails, return 0.


Once the protocol authenticates the signer, it then calls the function `__execute__`.
It passes as an argument an array of all the "calls", or operations, the user wants to
perform as a multicall. Each contract call defines a target smart contract address (`to`), a
method to call (the `selector`) and the arguments expected by the method (the `calldata`).
In essence, when a user dispatches an `invoke` transaction, the protocol initiates by invoking the `__validate__` method. This verifies the associated signer's authenticity. For security reasons, particularly to safeguard the Sequencer from Denial of Service (DoS) attacks [1], there are constraints on the operations within the `__validate__` method. If the signature is verified, the method yields a `'VALID'` `felt252` value. If not, it returns 0.

After the protocol verifies the signer, it proceeds to invoke the `__execute__` function, passing an array of all desired operations—referred to as "calls"—as an argument. Each of these calls specifies a target smart contract address (`to`), the method to be executed (`selector`), and the arguments this method requires (`calldata`).

```rust
struct Call {
Expand All @@ -117,37 +80,19 @@ struct Call {
trait ISRC6 {

....

fn __execute__(calls: Array<Call>) -> Array<Span<felt252>>;

....

}

```

The execution of each `Call` might result in a value being returned
from the target smart contract. This value could be a felt252, boolean, or a
complex data structure such as a struct or an array. The Starknet protocol
will then serialize this response using `Span<felt252>`. And since a `Span`
represents a snapshot of an Array [2], the `__execute__` method returns an array
of `Span<felt252>` elements, which represents a serialized response from each
call in the multicall.


The method `is_valid_signature` is not defined or used by the Starknet protocol.
It's instead an agreement between builders in the Starknet community as a way
to allow web3 apps to perform user authentication. Example, think of a user
trying to authenticate to an NFT marketplace using their wallet. The web app
will ask the user to sign a message, then call the `is_valid_signature` method
to verify that the connected wallet address belongs to the user.

Executing a `Call` may yield a return value from the target smart contract. Whether it's a felt252, boolean, or a more intricate data structure like a struct or array, Starknet protocol serializes the return using `Span<felt252>`. Since `Span` captures a segment of an Array [2], the `__execute__` function outputs an array of `Span<felt252>` elements. This array signifies the serialized feedback from every operation in the multicall.

To allow other smart contracts to know that your account contract adheres to
the SNIP-6 public interface, you should implement `supports_interface`
method from the `ISRC5` introspection standard trait, with SNIP-6's Interface
ID passed as a parameter.
The `is_valid_signature` method isn't mandated or employed by the Starknet protocol. Instead, it's a convention within the Starknet developer community. Its purpose is to facilitate user authentication in web3 applications. For instance, consider a user attempting to log into an NFT marketplace using their digital wallet. The web application prompts the user to sign a message, then it uses the `is_valid_signature` function to confirm the authenticity of the associated wallet address.

To ensure other smart contracts recognize the compliance of an account contract with the SNIP-6 public interface, developers should incorporate the `supports_interface` method from the `ISRC5` introspection trait. This method requires the Interface ID of SNIP-6 as its argument.

```rust
struct Call {
Expand All @@ -157,28 +102,17 @@ struct Call {
}

trait ISRC6 {

// __execute__, __validate__, is_valid_signature methods implemented here.
// Implementations for __execute__, __validate__, and is_valid_signature go here.
}


trait ISRC5 {

fn supports_interface(interface_id: felt252) -> bool;

}

}
```

The `interface_id` parameter is the combined hash of the trait's selectors as
defined by Ethereum's ERC165 [3]. You can calculate for yourself the id
by using the `src5-rs` utility [4], or trust that the id is
**1270010605630597976495846281167968799381097569185364931397797212080166453709.**


So far, we have created the basic structure for the account contract, as defined
by the SNIP-G Interface standard;
The `interface_id` corresponds to the aggregated hash of the trait's selectors, as detailed in Ethereum's ERC165 [3]. Developers can either compute the ID using the `src5-rs` utility [4] or rely on the pre-calculated ID: `1270010605630597976495846281167968799381097569185364931397797212080166453709`.

The fundamental structure for the account contract, aligning with the SNIP-G Interface standard, looks like this:

```rust
struct Call {
Expand All @@ -196,78 +130,45 @@ trait ISRC6 {
trait ISRC5 {
fn supports_interface(interface_id: felt252) -> bool;
}

```

## Expanding the Interface

##Additional Interface

The elements described above are foundational to defining an account
contract and sufficient to implement the SNIP-6 standard. Nevertheless,
there are additional components that can be incorporated to bolster the
account contract’s functionality.


For instance, the `__validate_declare__` function is added if the contract
is used to declare other contracts and pay the associated gas fees,
providing a mechanism to validate the contract declaration.
Additionally, to counterfactually deploy a smart contract one can include the
`__validate_deploy__` function.

While the components mentioned earlier lay the foundation for an account contract in alignment with the SNIP-6 standard, developers can introduce more features to enhance the contract's capabilities.

Counterfactual deployment is a mechanism to deploy an account contract without
relying on another account contract to pay for the related gas fees.
It's important if we don't want to associate a new account contract with the
address that deployed it, but instead have a new beginning.
For example, integrate the `__validate_declare__` function if the contract declares other contracts and handles the corresponding gas fees. This offers a way to authenticate the contract declaration. For those keen on counterfactual smart contract deployment, the `__validate_deploy__` function can be included.

Counterfactual deployment lets developers set up an account contract without depending on another account contract for gas fees. This method is valuable when there's no desire to link a new account contract with its deploying address, ensuring a fresh start.

The deployment process starts by calculating locally the would-be-address of
our account contract without actually deploying it yet. This is possible
to achieve using the Starkli [5] tool. Once we know the account contract's
address, we then send enough ETH to that address to cover the costs of
deploying our account contract.


Once the precalculated account address is funded, we can finally send a `deploy_account`
transaction to Starknet with the compiled code of our account contract.
The sequencer will deploy the account contract to the precalculated address
and pay itself gas fees with the ETH we sent there. There's no need to `declare`
an account contract before deploying it.


To allow tools like Starkli to easily integrate with our smart contract in the
future, its recommended to expose the `public_key` of the signer as a view
function as part of the public interface. With that in mind, the extended
account contract's interface is as follows;
This approach involves:
1. Locally determining the potential address of our account contract without actual deployment, feasible with the Starkli [5] tool.
2. Transferring sufficient ETH to the predicted address to cover the deployment costs.
3. Sending a `deploy_account` transaction to Starknet containing our contract's compiled code. The sequencer then activates the account contract at the estimated address, compensating its gas fees from the transferred ETH. No `declare` action is needed beforehand.

For better compatibility with tools like Starkli later on, expose the signer's `public_key` through a view function in the public interface. Below is the augmented account contract interface:

```rust
/// @title IAccount Additional account contract interface
/// @title IAccountAddon - Extended account contract interface
trait IAccountAddon {
/// @notice Assert whether a declare transaction is valid to be executed
/// @param class_hash The class hash of the smart contract to be declared
/// @return The string 'VALID' represented as felt when is valid
/// @notice Validates if a declare transaction can proceed
/// @param class_hash Hash of the smart contract under declaration
/// @return 'VALID' string as felt, if valid
fn __validate_declare__(class_hash: felt252) -> felt252;

/// @notice Assert whether counterfactual deployment is valid to be executed
/// @param class_hash The class hash of the account contract to be deployed
/// @param salt Account address randomizer
/// @param public_key The public key of the account signer
/// @return The string 'VALID' represented as felt when is valid
/// @notice Validates if counterfactual deployment can proceed
/// @param class_hash Hash of the account contract under deployment
/// @param salt Modifier for account address
/// @param public_key Account signer's public key
/// @return 'VALID' string as felt, if valid
fn __validate_deploy__(class_hash: felt252, salt: felt252, public_key: felt252) -> felt252;

/// @notice Exposes the signer's public key
/// @return The public key
/// @notice Fetches the signer's public key
/// @return Public key
fn public_key() -> felt252;
}
```

```


In summary, a fully fledged account contract should implement the **SNIP-5**, **SNIP-6**
and the **Addon** interface.


In conclusion, a comprehensive account contract incorporates the **SNIP-5**, **SNIP-6**, and the Addon interfaces.

```rust
// Cheat sheet
Expand Down Expand Up @@ -296,57 +197,19 @@ trait IAccountAddon {

```

## Recap

We've broken down the distinctions between account contracts and basic smart contracts, particularly focusing on the methods laid out in SNIP-6.

## Summary

We elucidated the unique aspects of account contracts and their
derivation from basic smart contracts by adhering to specific methods
outlined in SNIP-6.

We defined the `ISRC6` trait, detailing the critical functions, namely,
`__validate__`, `is_valid_signature`, and `__execute__`. These functions
carry out tasks such as transaction validation, signature verification,
and contract call execution, respectively. We further introduced the
`ISRC5` trait, emphasizing the `supports_interface` function for
verifying interface support in contracts.

Furthermore, we defined a single contract call using the `Call` struct,
explaining its fields— `to`, `selector`, and `calldata`. We also
discussed potential enhancements to account contracts using
`__validate_declare__` and `__validate_deploy__` functions..

In the next subchapter, we will implement a simple account contract and
learn how to deploy it on Starknet. This will provide a practical
understanding of how account contracts work and how to interact with
them.



## References:

- \[1\] Starknet Docs: Limitations on the validate function
<https://docs.starknet.io/documentation/architecture_and_concepts/Account_Abstraction/validate_and_execute/#validate_limitations>

- \[2\] Cairo Book: The span data type
<https://book.cairo-lang.org/ch02-06-common-collections.html#span>

- \[3\] ERC-165: Standard Interface Detection
<https://eips.ethereum.org/EIPS/eip-165>

- \[4\] Github: src5-rs
<https://github.com/ericnordelo/src5-rs>
- Introduced the `ISRC6` trait, spotlighting essential functions:
- `__validate__`: Validates transactions.
- `is_valid_signature`: Verifies signatures.
- `__execute__`: Executes contract calls.

- \[5\] Github: starkli
<https://github.com/xJonathanLEI/starkli>
- Discussed the `ISRC5` trait and highlighted the importance of the `supports_interface` function in confirming interface support.


The Book is a community-driven effort created for the community.
- Detailed the `Call` struct to represent a single contract call, explaining its components: `to`, `selector`, and `calldata`.

- If you’ve learned something, or not, please take a moment to provide
feedback through [this 3-question
survey](https://a.sprig.com/WTRtdlh2VUlja09lfnNpZDo4MTQyYTlmMy03NzdkLTQ0NDEtOTBiZC01ZjAyNDU0ZDgxMzU=).
- Touched on advanced features for account contracts, such as the `__validate_declare__` and `__validate_deploy__` functions.

- If you discover any errors or have additional suggestions, don’t
hesitate to open an [issue on our GitHub
repository](https://github.com/starknet-edu/starknetbook/issues).
Coming up, we'll craft a basic account contract and deploy it on Starknet, offering hands-on insight into their functionality and interactions.

0 comments on commit f1b3e21

Please sign in to comment.