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

docs: src-5 implement #233

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
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
6 changes: 6 additions & 0 deletions listings/src5_interface/Scarb.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Code generated by scarb DO NOT EDIT.
version = 1

[[package]]
name = "src5_interface"
version = "0.1.0"
2 changes: 1 addition & 1 deletion listings/src5_interface/src/lib.cairo
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pub mod UserAccount;
pub mod user_account;

#[cfg(test)]
mod tests;
148 changes: 63 additions & 85 deletions listings/src5_interface/src/src5_interface.cairo
julio4 marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not valid cairo syntax, please refer to official documentation, or our counter example, and don't use AI generated content.

Original file line number Diff line number Diff line change
@@ -1,93 +1,71 @@
use openzeppelin::account::interface;
use openzeppelin::account::interface::ISRC5_ID;
use openzeppelin::introspection::src5::SRC5Component;
use starknet::{ContractAddress, get_caller_address, get_contract_address, contract_address_const};
use super::interface::ISRC5_ID;
use super::request::SocialRequest;
use super::transfer::Transfer;

/// @title User Account Contract
/// @notice This contract implements an interface for user accounts with SRC5 introspection.
/// @dev This contract utilizes OpenZeppelin and StarkNet libraries for SRC5 introspection.
#[starknet::interface]
pub trait IUserAccount<TContractState> {
/// @notice Retrieves the public key associated with the account.
/// @return The public key of the account.
fn get_public_key(self: @TContractState) -> u256;

/// @notice Handles a transfer request.
/// @param request The social request containing transfer details.
fn handle_transfer(ref self: TContractState, request: SocialRequest<Transfer>);

/// @notice Checks if a specific interface is supported by the contract.
/// @param interface_id The ID of the interface to check.
/// @return True if the interface is supported, false otherwise.
fn is_supported_interface(self: @TContractState, interface_id: felt252) -> bool;
use starknet::contract::ContractAddress;
use starknet::syscalls::call_contract_syscall;
use starknet::alloc::arrays::ArrayTrait;
use starknet::core::keccak::starknet_keccak;
// Define ISRC5 interface
trait ISRC5 {
fn supports_interface(interface_id: felt252) -> bool;
}

#[starknet::contract]
pub mod UserAccount {
use openzeppelin::introspection::src5::SRC5Component;
use super::interface::ISRC5_ID;
use super::request::{SocialRequest, Transfer};
use super::{IUserAccountDispatcher, IUserAccountDispatcherTrait};

component!(path: SRC5Component, storage: src5, event: SRC5Event);

#[abi(embed_v0)]
impl SRC5Impl = SRC5Component::SRC5Impl<ContractState>;
impl InternalImpl = SRC5Component::InternalImpl<ContractState>;

/// @dev Storage structure for the User Account contract.
#[storage]
struct Storage {
#[key]
public_key: u256,
#[substorage(v0)]
src5: SRC5Component::Storage,
}

/// @dev Events emitted by the User Account contract.
#[event]
#[derive(Drop, starknet::Event)]
enum Event {
/// @notice Emitted when a new account is created.
/// @param public_key The public key of the newly created account.
AccountCreated { public_key: u256 },
/// @notice Emitted for SRC5 events.
#[flat]
SRC5Event: SRC5Component::Event,
// Define example interfaces
trait IExample1 {
fn example_function1(param1: felt252) -> felt252;
}
trait IExample2 {
fn example_function2(param1: felt252, param2: felt252) -> felt252;
}
// Storage structure
struct Storage {
public_key: felt252,
src5: SRC5Component::Storage,
}
// Event definitions
enum Event {
AccountCreated: felt252,
SRC5Event: SRC5Component::Event,
}
// Contract implementation
@contract
namespace ExampleContract {
struct State {
storage: Storage,
}

/// @notice Constructor function for the User Account contract.
/// @param public_key The public key to be associated with the account.
// Constructor
#[constructor]
fn constructor(ref self: ContractState, public_key: u256) {
self.public_key.write(public_key);
self.emit(AccountCreated { public_key });
self.src5.register_interface(ISRC5_ID);
fn constructor(ref state: State, public_key: felt252) {
state.storage.public_key = public_key;
emit(Event::AccountCreated(public_key));
state.storage.src5.register_interface(ISRC5_ID);
}
// Implementation of ISRC5
#[external]
fn supports_interface(ref state: State, interface_id: felt252) -> bool {
state.storage.src5.supports_interface(interface_id)
}
// Implementation of IExample1
#[external]
fn example_function1(param1: felt252) -> felt252 {
// Your implementation here
return starknet_keccak(param1.to_bytes());
}
// Implementation of IExample2
#[external]
fn example_function2(param1: felt252, param2: felt252) -> felt252 {
// Your implementation here
return param1 + param2;
}
// Additional functions or internal logic can be added here
}
// ISRC5 trait implementation
impl ISRC5 for State {
fn supports_interface(ref self, interface_id: felt252) -> bool {
self.storage.src5.supports_interface(interface_id)
}
}


/// @dev Implementation of the IUserAccount interface.
#[abi(embed_v0)]
impl IUserAccountImpl for ContractState {
/// @notice Retrieves the public key associated with the account.
/// @return The public key of the account.
fn get_public_key(self: @ContractState) -> u256 {
self.public_key.read()
}

/// @notice Handles a transfer request.
/// @param request The social request containing transfer details.
fn handle_transfer(ref self: ContractState, request: SocialRequest<Transfer>) {
let erc20 = ERC20ABIDispatcher::at(request.content.token_address);
erc20.transfer(request.content.recipient_address, request.content.amount);
}

/// @notice Checks if a specific interface is supported by the contract.
/// @param interface_id The ID of the interface to check.
/// @return True if the interface is supported, false otherwise.
fn is_supported_interface(self: @ContractState, interface_id: felt252) -> bool {
self.src5.supports_interface(interface_id)
}
}
#[cfg(test)]
mod tests { // TODO
}
3 changes: 0 additions & 3 deletions listings/src5_interface/src/tests.cairo

This file was deleted.

18 changes: 18 additions & 0 deletions listings/src5_interface/src5_snippet.py
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrong format, please try to run the code before pushing it

Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from starkware.starknet.public.abi import starknet_keccak

signatures = [
'supports_interface(felt252)->E((),())',
'is_valid_signature(felt252,Array<felt252>)->E((),())',
'__execute__(Array<(ContractAddress,felt252,Array<felt252>)>)->Array<(@Array<felt252>)>',
'__validate__(Array<(ContractAddress,felt252,Array<felt252>)>)->felt252',
'__validate_declare__(felt252)->felt252'
]

def compute_interface_id():
interface_id = 0x0
for sig in signatures:
function_id = starknet_keccak(sig.encode())
interface_id ^= function_id
print('IAccount ID:', hex(interface_id))

compute_interface_id()
97 changes: 39 additions & 58 deletions src/src5 implementation.md
Original file line number Diff line number Diff line change
@@ -1,111 +1,92 @@
## SRC-5: A comprhensive Overview

SRC-5 is a standard for smart contract interface introspection in Starknet, inspired by the Ethereum ERC-165 standard. It provides a method for contracts to publish and detect the interfaces they implement, ensuring standardized interaction. It provides a method for contracts to publish and detect the interfaces they implement, ensuring standardized interaction. You can find more information and details refer to the [SRC-5 specification](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-5.md#simple-summary) and the [Ethereum ERC-165 standard](https://eips.ethereum.org/EIPS/eip-165).


## SRC-5: A comprehensive Overview

SRC-5 is a standard for smart contract interface introspection in Starknet, inspired by the [Ethereum ERC-165 standard](https://eips.ethereum.org/EIPS/eip-165). It provides a standardize method for contracts to publish and detect the interfaces they implement, ensuring consistent interaction. For more information, refer to the [SRC-5 specification](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-5.md#simple-summary).

### SRC-5 offers a standardized method to:

* Identify interfaces.
* Publish the interfaces a contract implements.
* Detect if a contract implements any given interface. (including SRC-5)


### Interface Definition
julio4 marked this conversation as resolved.
Show resolved Hide resolved

An interface is a set of function signatures with specific type parameters, represented as traits. These traits are meant to be implemented externally by compliant contracts.

Example:

```rust
trait IMyContract {

```rust
trait IMyContract {
fn foo(some: u256) -> felt252;
}
```
With Cairo 2.0, generic traits can represent a set of interfaces:
}
```

```rust
With Cairo, generic traits can represent a set of interfaces:

```rust
#[starknet::interface]
trait IMyContract<TContractState, TNumber> {
fn foo(self: @TContractState, some: TNumber) -> felt252;
}
```
fn foo(self: @TContractState, some: TNumber) -> felt252;
}
```

#### Extended Function Selector
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing linebreak

The function selector in Starknet is the starknet_keccak hash of the function name. The extended function selector, for SRC-5, is the starknet_keccak hash of the function signature:

fn_name(param1_type,param2_type,...)->output_type

For example,for a function with zero parameters and no return value:
The function selector in Starknet is the starknet_keccak hash of the function name. The extended function selector, for SRC-5, is the starknet_keccak hash of the function signature:

`fn_name(param1_type,param2_type,...)->output_type`

fn_name()
For example, for a function with zero parameters and no return value: `fn_name()`

Special Types:
```

* Tuples: (elem1_type,elem2_type,...)
* Structs: (field1_type,field2_type,...)
* Enums: E(variant1_type,variant2_type,...)
```

#### Interface Identification
An interface identifier is the XOR of all extended function selectors in the interface.

Example (Python code):

#### Interface Identification

from starkware.starknet.public.abi import starknet_keccak

signatures = [
'supports_interface(felt252)->E((),())',
'is_valid_signature(felt252,Array<felt252>)->E((),())',
'__execute__(Array<(ContractAddress,felt252,Array<felt252>)>)->Array<(@Array<felt252>)>',
'__validate__(Array<(ContractAddress,felt252,Array<felt252>)>)->felt252',
'__validate_declare__(felt252)->felt252'
]

def compute_interface_id():
interface_id = 0x0
for sig in signatures:
function_id = starknet_keccak(sig.encode())
interface_id ^= function_id
print('IAccount ID:', hex(interface_id))
An interface identifier is the XOR of all extended function selectors in the interface.

compute_interface_id()
This Python code computes the interface id:

For more details of the above code, refer to the [SRC-5 repository on GitHub](https://github.com/ericnordelo/src5-rs).
```rust
{{#rustdoc_include ../../../listings/src5_interface/src5_snippet.py}}
```

For more details refer to the [SRC-5 repository on GitHub](https://github.com/ericnordelo/src5-rs).

Publishing and Detecting Interfaces
### Publishing and Detecting Interfaces

To comply with SRC-5, a contract must implement the ISRC5 trait:

```rust
trait ISRC5 {
```rust
trait ISRC5 {
fn supports_interface(interface_id: felt252) -> bool;
}
```

The supports_interface function returns:
}
```

* true if the contract implements the given interface_id.
* false otherwise.
The supports_interface function returns a boolean that indicate if the contract implements a given interface.

The interface identifier for ISRC5 is

```0x3f918d17e5ee77373b56385708f855659a07f75997f365cf87748628532a055```.
```0x3f918d17e5ee77373b56385708f855659a07f75997f365cf87748628532a055```

#### Detecting SRC-5 Implementation
julio4 marked this conversation as resolved.
Show resolved Hide resolved

To check if a contract implements SRC-5:

* Call 'contract.supports_interface(0x3f918d17e5ee77373b56385708f855659a07f75997f365cf87748628532a055)'.
* Call `contract.supports_interface(0x3f918d17e5ee77373b56385708f855659a07f75997f365cf87748628532a055)`.
* If the call fails or returns false, the contract does not implement SRC-5.
* Otherwise, it implements SRC-5.

#### Detecting Any Given Interface
julio4 marked this conversation as resolved.
Show resolved Hide resolved

* Confirm if the contract implements SRC-5.
* If confirmed, call supports_interface(interface_id) to check for specific interfaces.
* If confirmed, `call supports_interface(interface_id)` to check for specific interfaces.
* If not, manually inspect the contract methods.

Below shows a contract implementing the ```ISRC5``` trait :
Below shows a contract implementing the `SRC5` to expose the `Isupports_interface(interface_id)`:

```rust
{{#rustdoc_include ../../../listings/src5_interface/src/src5_interface.cairo}}
Expand Down