diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc index d78f8a2bd..380822723 100644 --- a/docs/modules/ROOT/nav.adoc +++ b/docs/modules/ROOT/nav.adoc @@ -1,7 +1,8 @@ * xref:index.adoc[Overview] -* xref:wizard.adoc[Wizard] -* xref:extensibility.adoc[Extensibility] -* xref:proxies.adoc[Proxies and Upgrades] +//* xref:wizard.adoc[Wizard] +//* xref:extensibility.adoc[Extensibility] +//* xref:proxies.adoc[Proxies and Upgrades] +* xref:interfaces.adoc[Interfaces and Dispatchers] * xref:accounts.adoc[Accounts] ** xref:/guides/deployment.adoc[Counterfactual deployments] @@ -9,14 +10,16 @@ * xref:access.adoc[Access Control] -* Tokens -** xref:erc20.adoc[ERC20] -** xref:erc721.adoc[ERC721] -** xref:erc1155.adoc[ERC1155] +//* xref:access.adoc[Access Control] -* xref:security.adoc[Security] -* xref:introspection.adoc[Introspection] -* xref:udc.adoc[Universal Deployer Contract] -* xref:utilities.adoc[Utilities] +//* Tokens +//** xref:erc20.adoc[ERC20] +//** xref:erc721.adoc[ERC721] +//** xref:erc1155.adoc[ERC1155] + +//* xref:security.adoc[Security] +//* xref:introspection.adoc[Introspection] +//* xref:udc.adoc[Universal Deployer Contract] +//* xref:utilities.adoc[Utilities] * xref:contracts::index.adoc[Contracts for Solidity] diff --git a/docs/modules/ROOT/pages/interfaces.adoc b/docs/modules/ROOT/pages/interfaces.adoc new file mode 100644 index 000000000..44125f15c --- /dev/null +++ b/docs/modules/ROOT/pages/interfaces.adoc @@ -0,0 +1,185 @@ +:great-interface-migration: link:https://community.starknet.io/t/the-great-interface-migration/92107[Great Interface Migration] + += Interfaces and Dispatchers + +This section describes the interfaces OpenZeppelin Contracts for Cairo offer, and explains the design choices behind them. + +Interfaces can be found in the module tree under the `interface` submodule, such as `token::erc20::interface`. For example: + +```javascript +use openzeppelin::token::erc20::interface::IERC20; +``` + +or + +```javascript +use openzeppelin::token::erc20::dual20::DualCaseERC20; +``` + +NOTE: For simplicity, we'll use ERC20 as example but the same concepts apply to other modules. + +== Interface traits +The library offers three types of traits to implement or interact with contracts: + +=== Standard traits + +These are associated with a predefined interface such as a standard. +This includes only the functions defined in the interface, and is the standard way to interact with a compliant contract. + +```javascript +#[starknet::interface] +trait IERC20 { + fn name(self: @TState) -> felt252; + fn symbol(self: @TState) -> felt252; + fn decimals(self: @TState) -> u8; + fn total_supply(self: @TState) -> u256; + fn balance_of(self: @TState, account: ContractAddress) -> u256; + fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256; + fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool; + fn transfer_from( + ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 + ) -> bool; + fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool; +} +``` + +=== ABI traits + +They describe a contract's complete interface. This is useful to interface with a preset contract offered by this library, such as the ERC20 preset that includes non-standard functions like `increase_allowance`. + +```javascript +#[starknet::interface] +trait ERC20ABI { + fn name(self: @TState) -> felt252; + fn symbol(self: @TState) -> felt252; + fn decimals(self: @TState) -> u8; + fn total_supply(self: @TState) -> u256; + fn balance_of(self: @TState, account: ContractAddress) -> u256; + fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256; + fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool; + fn transfer_from( + ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 + ) -> bool; + fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool; + fn increase_allowance(ref self: TState, spender: ContractAddress, added_value: u256) -> bool; + fn decrease_allowance( + ref self: TState, spender: ContractAddress, subtracted_value: u256 + ) -> bool; +} +``` + +=== Dispatcher traits +This is a utility trait to interface with contracts whose interface is unknown. Read more in the xref:#dualcase_dispatchers[DualCase Dispatchers] section. + +```javascript +#[derive(Copy, Drop)] +struct DualCaseERC20 { + contract_address: ContractAddress +} + +trait DualCaseERC20Trait { + fn name(self: @DualCaseERC20) -> felt252; + fn symbol(self: @DualCaseERC20) -> felt252; + fn decimals(self: @DualCaseERC20) -> u8; + fn total_supply(self: @DualCaseERC20) -> u256; + fn balance_of(self: @DualCaseERC20, account: ContractAddress) -> u256; + fn allowance(self: @DualCaseERC20, owner: ContractAddress, spender: ContractAddress) -> u256; + fn transfer(self: @DualCaseERC20, recipient: ContractAddress, amount: u256) -> bool; + fn transfer_from( + self: @DualCaseERC20, sender: ContractAddress, recipient: ContractAddress, amount: u256 + ) -> bool; + fn approve(self: @DualCaseERC20, spender: ContractAddress, amount: u256) -> bool; +} +``` + +== Dual interfaces + +Following the {great-interface-migration} plan, we added `snake_case` functions to all of our preexisting `camelCase` contracts with the goal of eventually dropping support for the latter. + +In short, we offer two types of interfaces and utilities to handle them: + +1. `camelCase` interfaces, which are the ones we've been using so far. +2. `snake_case` interfaces, which are the ones we're migrating to. + +This means that currently most of our contracts implement _dual interfaces_. For example, the ERC20 preset contract exposes `transferFrom`, `transfer_from`, `balanceOf`, `balance_of`, etc. + +NOTE: Dual interfaces are available for all external functions present in previous versions of OpenZeppelin Contracts for Cairo (https://github.com/OpenZeppelin/cairo-contracts/releases/tag/v0.6.1[v0.6.1] and below). + +=== `IERC20` + +The default version of the ERC20 interface trait exposes `snake_case` functions: + +```javascript +#[starknet::interface] +trait IERC20 { + fn name(self: @TState) -> felt252; + fn symbol(self: @TState) -> felt252; + fn decimals(self: @TState) -> u8; + fn total_supply(self: @TState) -> u256; + fn balance_of(self: @TState, account: ContractAddress) -> u256; + fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256; + fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool; + fn transfer_from( + ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 + ) -> bool; + fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool; +} +``` + +=== `IERC20Camel` + +On top of that, we also offer a `camelCase` version of the same interface: + +```javascript +#[starknet::interface] +trait IERC20Camel { + fn name(self: @TState) -> felt252; + fn symbol(self: @TState) -> felt252; + fn decimals(self: @TState) -> u8; + fn totalSupply(self: @TState) -> u256; + fn balanceOf(self: @TState, account: ContractAddress) -> u256; + fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256; + fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool; + fn transferFrom( + ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 + ) -> bool; + fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool; +} +``` + +== `DualCase` dispatchers + +WARNING: `DualCase` dispatchers won't work on live chains (`mainnet` or testnets) until they implement panic handling in their runtime. Dispatchers work fine in testing environments. + +In order to ease this transition, OpenZeppelin Contracts for Cairo offer what we call `DualCase` dispatchers such as `DualCaseERC721` or `DualCaseAccount`. + +These modules wrap a target contract with a compatibility layer to expose a `snake_case` interface no matter what casing the underlying contract uses. +This way, an AMM wouldn't have problems integrating tokens independently of their interface. + +For example: + +```javascript +let token = DualCaseERC20 { contract_address: target }; +token.transfer_from(OWNER(), RECIPIENT(), VALUE); +``` + +This is done by simply executing the `snake_case` version of the function (e.g. `transfer_from`) and falling back to the `camelCase` one (e.g. `transferFrom`) in case it reverts with `ENTRYPOINT_NOT_FOUND`, like this: + +```javascript +fn try_selector_with_fallback( + target: ContractAddress, snake_selector: felt252, camel_selector: felt252, args: Span +) -> SyscallResult> { + match call_contract_syscall(target, snake_selector, args) { + Result::Ok(ret) => Result::Ok(ret), + Result::Err(errors) => { + if *errors.at(0) == 'ENTRYPOINT_NOT_FOUND' { + return call_contract_syscall(target, camel_selector, args); + } else { + Result::Err(errors) + } + } + } +} +``` + +Trying the `snake_case` interface first renders `camelCase` calls a bit more expensive since a failed `snake_case` call will always happen before. This is a design choice to incentivize casing adoption/transition as per the {great-interface-migration}.