diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc
index 342abe67c..e58a9ac08 100644
--- a/docs/modules/ROOT/nav.adoc
+++ b/docs/modules/ROOT/nav.adoc
@@ -12,8 +12,10 @@
* xref:access.adoc[Access Control]
** xref:/api/access.adoc[API Reference]
-// * Tokens
-// ** xref:erc20.adoc[ERC20]
+* Tokens
+** xref:erc20.adoc[ERC20]
+*** xref:/guides/erc20-supply.adoc[Creating Supply]
+*** xref:/api/erc20.adoc[API Reference]
// ** xref:erc721.adoc[ERC721]
// ** xref:erc1155.adoc[ERC1155]
diff --git a/docs/modules/ROOT/pages/api/erc20.adoc b/docs/modules/ROOT/pages/api/erc20.adoc
new file mode 100644
index 000000000..caa455dfa
--- /dev/null
+++ b/docs/modules/ROOT/pages/api/erc20.adoc
@@ -0,0 +1,463 @@
+:github-icon: pass:[]
+:eip20: https://eips.ethereum.org/EIPS/eip-20[EIP-20]
+:erc20-guide: xref:erc20.adoc[ERC20 guide]
+:casing-discussion: https://github.com/OpenZeppelin/cairo-contracts/discussions/34[here]
+
+= ERC20
+
+Reference of interfaces and utilities related to ERC20 contracts.
+
+TIP: For an overview of ERC20, read our {erc20-guide}.
+
+== Core
+
+[.contract]
+[[IERC20]]
+=== `++IERC20++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/cairo-2/src/token/erc20/interface.cairo#L6-L19[{github-icon},role=heading-link]
+
+[.hljs-theme-dark]
+```javascript
+use openzeppelin::token::erc20::interface::IERC20;
+```
+
+Interface of the IERC20 standard as defined in {eip20}.
+
+[.contract-index]
+.Functions
+--
+* xref:#IERC20-name[`++name()++`]
+* xref:#IERC20-symbol[`++symbol()++`]
+* xref:#IERC20-decimals[`++decimals()++`]
+* xref:#IERC20-total_supply[`++total_supply()++`]
+* xref:#IERC20-balance_of[`++balance_of()++`]
+* xref:#IERC20-allowance[`++allowance(owner, spender)++`]
+* xref:#IERC20-transfer[`++transfer(recipient, amount)++`]
+* xref:#IERC20-transfer_from[`++transfer_from(sender, recipient, amount)++`]
+* xref:#IERC20-approve[`++approve(spender, amount)++`]
+--
+
+[.contract-index]
+.Events
+--
+* xref:#IERC20-Transfer[`++Transfer(from, to, value)++`]
+* xref:#IERC20-Approval[`++Approval(owner, spender, value)++`]
+--
+
+[#IERC20-Functions]
+==== Functions
+
+[.contract-item]
+[[IERC20-name]]
+==== `[.contract-item-name]#++name++#++() → felt252++` [.item-kind]#external#
+
+Returns the name of the token.
+
+[.contract-item]
+[[IERC20-symbol]]
+==== `[.contract-item-name]#++symbol++#++() → felt252++` [.item-kind]#external#
+
+Returns the ticker symbol of the token.
+
+[.contract-item]
+[[IERC20-decimals]]
+==== `[.contract-item-name]#++decimals++#++() → u8++` [.item-kind]#external#
+
+Returns the number of decimals the token uses - e.g. `8` means to divide the token amount by `100000000` to get its user-readable representation.
+
+For example, if `decimals` equals `2`, a balance of `505` tokens should
+be displayed to a user as `5.05` (`505 / 10 ** 2`).
+
+Tokens usually opt for a value of `18`, imitating the relationship between
+Ether and Wei. This is the default value returned by this function, unless
+a custom implementation is used.
+
+NOTE: This information is only used for _display_ purposes: it in
+no way affects any of the arithmetic of the contract.
+
+[.contract-item]
+[[IERC20-total_supply]]
+==== `[.contract-item-name]#++total_supply++#++() → u256++` [.item-kind]#external#
+
+Returns the amount of tokens in existence.
+
+[.contract-item]
+[[IERC20-balance_of]]
+==== `[.contract-item-name]#++balance_of++#++(account: ContractAddress) → u256++` [.item-kind]#external#
+
+Returns the amount of tokens owned by `account`.
+
+[.contract-item]
+[[IERC20-allowance]]
+==== `[.contract-item-name]#++allowance++#++(owner: ContractAddress, spender: ContractAddress) → u256++` [.item-kind]#external#
+
+Returns the remaining number of tokens that `spender` is allowed to spend on behalf of `owner` through <>. This is zero by default.
+
+This value changes when <> or <> are called.
+
+[.contract-item]
+[[IERC20-transfer]]
+==== `[.contract-item-name]#++transfer++#++(recipient: ContractAddress, amount: u256) → bool++` [.item-kind]#external#
+
+Moves `amount` tokens from the caller's token balance to `to`.
+Returns `true` on success, reverts otherwise.
+
+Emits a <> event.
+
+[.contract-item]
+[[IERC20-transfer_from]]
+==== `[.contract-item-name]#++transfer_from++#++(sender: ContractAddress, recipient: ContractAddress, amount: u256) → bool++` [.item-kind]#external#
+
+Moves `amount` tokens from `sender` to `recipient` using the allowance mechanism.
+`amount` is then deducted from the caller's allowance.
+Returns `true` on success, reverts otherwise.
+
+Emits a <> event.
+
+[.contract-item]
+[[IERC20-approve]]
+==== `[.contract-item-name]#++approve++#++(spender: ContractAddress, amount: u256) → bool++` [.item-kind]#external#
+
+Sets `amount` as the allowance of `spender` over the caller's tokens.
+Returns `true` on success, reverts otherwise.
+
+Emits an <> event.
+
+[#IERC20-Events]
+==== Events
+
+[.contract-item]
+[[IERC20-Transfer]]
+==== `[.contract-item-name]#++Transfer++#++(from: ContractAddress, to: ContractAddress, value: u256)++` [.item-kind]#event#
+
+Emitted when `value` tokens are moved from one address (`from`) to another (`to`).
+
+Note that `value` may be zero.
+
+[.contract-item]
+[[IERC20-Approval]]
+==== `[.contract-item-name]#++Approval++#++(owner: ContractAddress, spender: ContractAddress, value: u256)++` [.item-kind]#event#
+
+Emitted when the allowance of a `spender` for an `owner` is set.
+`value` is the new allowance.
+
+[.contract]
+[[ERC20]]
+=== `++ERC20++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/cairo-2/src/token/erc20/erc20.cairo[{github-icon},role=heading-link]
+
+[.hljs-theme-dark]
+```javascript
+use openzeppelin::token::erc20::ERC20;
+```
+
+Implementation of the <> interface.
+
+[.contract-index]
+.Constructor
+--
+* xref:#ERC20-constructor[`++constructor(self, name, symbol, initial_supply, recipient)++`]
+--
+
+[.contract-index]
+.External functions
+--
+.ERC20Impl
+* xref:#ERC20-name[`++name(self)++`]
+* xref:#ERC20-symbol[`++symbol(self)++`]
+* xref:#ERC20-decimals[`++decimals(self)++`]
+* xref:#ERC20-total_supply[`++total_supply(self)++`]
+* xref:#ERC20-balance_of[`++balance_of(self, account)++`]
+* xref:#ERC20-allowance[`++allowance(self, owner, spender)++`]
+* xref:#ERC20-transfer[`++transfer(self, recipient, amount)++`]
+* xref:#ERC20-transfer_from[`++transfer_from(self, sender, recipient, amount)++`]
+* xref:#ERC20-approve[`++approve(self, spender, amount)++`]
+
+.Non-standard
+* xref:#ERC20-increase_allowance[`++increase_allowance(self, spender, added_value)++`]
+* xref:#ERC20-decrease_allowance[`++decrease_allowance(self, spender, subtracted_value)++`]
+
+.ERC20CamelOnlyImpl
+* xref:#ERC20-totalSupply[`++totalSupply(self)++`]
+* xref:#ERC20-balanceOf[`++balanceOf(self, account)++`]
+* xref:#ERC20-transferFrom[`++transferFrom(self, sender, recipient, amount)++`]
+* xref:#ERC20-increaseAllowance[`++increaseAllowance(self, spender, addedValue)++`]
+* xref:#ERC20-decreaseAllowance[`++decreaseAllowance(self, spender, subtractedValue)++`]
+--
+
+[.contract-index]
+.Internal functions
+--
+
+.InternalImpl
+* xref:#ERC20-initializer[`++initializer(self, name, symbol)++`]
+* xref:#ERC20-_transfer[`++_transfer(self, sender, recipient, amount)++`]
+* xref:#ERC20-_approve[`++_approve(self, owner, spender, amount)++`]
+* xref:#ERC20-_mint[`++_mint(self, recipient, amount)++`]
+* xref:#ERC20-_burn[`++_burn(self, account, amount)++`]
+* xref:#ERC20-_increase_allowance[`++_increase_allowance(self, spender, added_value)++`]
+* xref:#ERC20-_decrease_allowance[`++_decrease_allowance(self, spender, subtracted_value)++`]
+* xref:#ERC20-_spend_allowance[`++_spend_allowance(self, owner, spender, amount)++`]
+
+--
+
+[.contract-index]
+.Events
+--
+* xref:#ERC20-Transfer[`++Transfer(from, to, value)++`]
+* xref:#ERC20-Approval[`++Approval(owner, spender, value)++`]
+--
+
+[#ERC20-Constructor]
+==== Constructor
+
+[.contract-item]
+[[ERC20-constructor]]
+==== `[.contract-item-name]#++constructor++#++(ref self: ContractState, name: felt252, symbol: felt252, initial_supply: u256, recipient: ContractAddress)++` [.item-kind]#constructor#
+
+Sets both the token name and symbol and mints `initial_supply` to `recipient`.
+Note that the token name and symbol are immutable once set through the constructor.
+
+[#ERC20-External-functions]
+==== External functions
+
+[.contract-item]
+[[ERC20-name]]
+==== `[.contract-item-name]#++name++#++(@self: ContractState) → felt252++` [.item-kind]#external#
+
+See <>.
+
+[.contract-item]
+[[ERC20-symbol]]
+==== `[.contract-item-name]#++symbol++#++(@self: ContractState) → felt252++` [.item-kind]#external#
+
+See <>.
+
+[.contract-item]
+[[ERC20-decimals]]
+==== `[.contract-item-name]#++decimals++#++(@self: ContractState) → u8++` [.item-kind]#external#
+
+See <>.
+
+[.contract-item]
+[[ERC20-total_supply]]
+==== `[.contract-item-name]#++total_supply++#++(@self: ContractState) → u256++` [.item-kind]#external#
+
+See <>.
+
+[.contract-item]
+[[ERC20-balance_of]]
+==== `[.contract-item-name]#++balance_of++#++(@self: ContractState, account: ContractAddress) → u256++` [.item-kind]#external#
+
+See <>.
+
+[.contract-item]
+[[ERC20-allowance]]
+==== `[.contract-item-name]#++allowance++#++(@self: ContractState, owner: ContractAddress, spender: ContractAddress) → u256++` [.item-kind]#external#
+
+See <>.
+
+[.contract-item]
+[[ERC20-transfer]]
+==== `[.contract-item-name]#++transfer++#++(ref self: ContractState, recipient: ContractAddress, amount: u256) → bool++` [.item-kind]#external#
+
+See <>.
+
+Requirements:
+
+- `recipient` cannot be the zero address.
+- The caller must have a balance of at least `amount`.
+
+[.contract-item]
+[[ERC20-transfer_from]]
+==== `[.contract-item-name]#++transfer_from++#++(ref self: ContractState, sender: ContractAddress, recipient: ContractAddress, amount: u256) → bool++` [.item-kind]#external#
+
+See <>.
+
+Requirements:
+
+- `sender` cannot be the zero address.
+- `sender` must have a balance of at least `amount`.
+- `recipient` cannot be the zero address.
+- The caller must have allowance for ``sender``'s tokens of at least `amount`.
+
+[.contract-item]
+[[ERC20-approve]]
+==== `[.contract-item-name]#++approve++#++(ref self: ContractState, spender: ContractAddress, amount: u256) → bool++` [.item-kind]#external#
+
+See <>.
+
+Requirements:
+
+- `spender` cannot be the zero address.
+
+[.contract-item]
+[[ERC20-increase_allowance]]
+==== `[.contract-item-name]#++increase_allowance++#++(ref self: ContractState, spender: ContractAddress, added_value: u256) → bool++` [.item-kind]#external#
+
+Increases the allowance granted from the caller to `spender` by `added_value`
+Returns `true` on success, reverts otherwise.
+
+Emits an <> event.
+
+Requirements:
+
+- `spender` cannot be the zero address.
+
+[.contract-item]
+[[ERC20-decrease_allowance]]
+==== `[.contract-item-name]#++decrease_allowance++#++(ref self: ContractState, spender: ContractAddress, subtracted_value: u256) → bool++` [.item-kind]#external#
+
+Decreases the allowance granted from the caller to `spender` by `subtracted_value`
+Returns `true` on success.
+
+Emits an <> event.
+
+Requirements:
+
+- `spender` cannot be the zero address.
+- `spender` must have allowance for the caller of at least `subtracted_value`.
+
+[.contract-item]
+[[ERC20-totalSupply]]
+==== `[.contract-item-name]#++totalSupply++#++(self: @ContractState) → u256++` [.item-kind]#external#
+
+See <>.
+
+Supports the Cairo v0 convention of writing external methods in camelCase as discussed {casing-discussion}.
+
+[.contract-item]
+[[ERC20-balanceOf]]
+==== `[.contract-item-name]#++balanceOf++#++(self: @ContractState, account: ContractAddress) → u256++` [.item-kind]#external#
+
+See <>.
+
+Supports the Cairo v0 convention of writing external methods in camelCase as discussed {casing-discussion}.
+
+[.contract-item]
+[[ERC20-transferFrom]]
+==== `[.contract-item-name]#++transferFrom++#++(ref self: ContractState, sender: ContractAddress, recipient: ContractAddress) → bool++` [.item-kind]#external#
+
+See <>.
+
+Supports the Cairo v0 convention of writing external methods in camelCase as discussed {casing-discussion}.
+
+[.contract-item]
+[[ERC20-increaseAllowance]]
+==== `[.contract-item-name]#++increaseAllowance++#++(ref self: ContractState, spender: ContractAddress, addedValue: u256) → bool++` [.item-kind]#external#
+
+See <>.
+
+Supports the Cairo v0 convention of writing external methods in camelCase as discussed {casing-discussion}.
+
+[.contract-item]
+[[ERC20-decreaseAllowance]]
+==== `[.contract-item-name]#++decreaseAllowance++#++(ref self: ContractState, spender: ContractAddress, subtractedValue: u256) → bool++` [.item-kind]#external#
+
+See <>.
+
+Supports the Cairo v0 convention of writing external methods in camelCase as discussed {casing-discussion}.
+
+[#ERC20-Internal-functions]
+==== Internal functions
+
+[.contract-item]
+[[ERC20-initializer]]
+==== `[.contract-item-name]#++initializer++#++(ref self: ContractState, name: felt252, symbol: felt252)++` [.item-kind]#internal#
+
+Initializes the contract by setting the token name and symbol.
+This should be used inside of the contract's constructor.
+
+[.contract-item]
+[[ERC20-_transfer]]
+==== `[.contract-item-name]#++_transfer++#++(ref self: ContractState, sender: ContractAddress, recipient: ContractAddress, amount: u256)++` [.item-kind]#internal#
+
+Moves `amount` of tokens from `from` to `to`.
+
+This internal function does not check for access permissions but can be useful as a building block, for example to implement automatic token fees, slashing mechanisms, etc.
+
+Emits a <> event.
+
+Requirements:
+
+- `from` cannot be the zero address.
+- `to` cannot be the zero address.
+- `from` must have a balance of at least `amount`.
+
+[.contract-item]
+[[ERC20-_approve]]
+==== `[.contract-item-name]#++_approve++#++(ref self: ContractState, owner: ContractAddress, spender: ContractAddress, amount: u256)++` [.item-kind]#internal#
+
+Sets `amount` as the allowance of `spender` over ``owner``'s tokens.
+
+This internal function does not check for access permissions but can be useful as a building block, for example to implement automatic allowances on behalf of other addresses.
+
+Emits an <> event.
+
+Requirements:
+
+- `owner` cannot be the zero address.
+- `spender` cannot be the zero address.
+
+[.contract-item]
+[[ERC20-_mint]]
+==== `[.contract-item-name]#++_mint++#++(ref self: ContractState, recipient: ContractAddress, amount: u256)++` [.item-kind]#internal#
+
+Creates an `amount` number of tokens and assigns them to `recipient`.
+
+Emits a <> event with `from` being the zero address.
+
+Requirements:
+
+- `recipient` cannot be the zero address.
+
+[.contract-item]
+[[ERC20-_burn]]
+==== `[.contract-item-name]#++_burn++#++(ref self: ContractState, account: ContractAddress, amount: u256)++` [.item-kind]#internal#
+
+Destroys `amount` number of tokens from `account`.
+
+Emits a <> event with `to` set to the zero address.
+
+Requirements:
+
+- `account` cannot be the zero address.
+
+[.contract-item]
+[[ERC20-_increase_allowance]]
+==== `[.contract-item-name]#++_increase_allowance++#++(ref self: ContractState, spender: ContractAddress, added_value: u256)++` [.item-kind]#internal#
+
+Increases the allowance granted from the caller to `spender` by `added_value`
+
+Emits an <> event.
+
+[.contract-item]
+[[ERC20-_decrease_allowance]]
+==== `[.contract-item-name]#++_decrease_allowance++#++(ref self: ContractState, spender: ContractAddress, subtracted_value: u256)++` [.item-kind]#internal#
+
+Decreases the allowance granted from the caller to `spender` by `subtracted_value`
+
+Emits an <> event.
+
+[.contract-item]
+[[ERC20-_spend_allowance]]
+==== `[.contract-item-name]#++_spend_allowance++#++(ref self: ContractState, owner: ContractAddress, spender: ContractAddress, amount: u256)++` [.item-kind]#internal#
+
+Updates ``owner``'s allowance for `spender` based on spent `amount`.
+
+This internal function does not update the allowance value in the case of infinite allowance.
+
+Possibly emits an <> event.
+
+[#ERC20-Events]
+==== Events
+
+[.contract-item]
+[[ERC20-Transfer]]
+==== `[.contract-item-name]#++Transfer++#++(from: ContractAddress, to: ContractAddress, value: u256)++` [.item-kind]#event#
+
+See <>.
+
+[.contract-item]
+[[ERC20-Approval]]
+==== `[.contract-item-name]#++Approval++#++(owner: ContractAddress, spender: ContractAddress, value: u256)++` [.item-kind]#event#
+
+See <>.
diff --git a/docs/modules/ROOT/pages/erc20.adoc b/docs/modules/ROOT/pages/erc20.adoc
index e5a75da19..f2fcac284 100644
--- a/docs/modules/ROOT/pages/erc20.adoc
+++ b/docs/modules/ROOT/pages/erc20.adoc
@@ -1,420 +1,216 @@
= ERC20
-The ERC20 token standard is a specification for https://docs.openzeppelin.com/contracts/4.x/tokens#different-kinds-of-tokens[fungible tokens], a type of token where all the units are exactly equal to each other.
-The `ERC20.cairo` contract implements an approximation of https://eips.ethereum.org/EIPS/eip-20[EIP-20] in Cairo for StarkNet.
-
-== Table of Contents
-
-* <>
- ** <>
-* <>
-* <>
-* <>
- ** <>
- ** <>
- ** <>
- ** <>
-* <>
- ** <>
- *** <>
- *** <>
- *** <>
- *** <>
- *** <>
- *** <>
- *** <>
- *** <>
- *** <>
- ** <>
- *** <>
- *** <>
+:fungible-tokens: https://docs.openzeppelin.com/contracts/4.x/tokens#different-kinds-of-tokens[fungible tokens]
+:eip20: https://eips.ethereum.org/EIPS/eip-20[EIP-20]
+:version: https://github.com/OpenZeppelin/cairo-contracts/releases/tag/v0.7.0[Contracts v0.7.0]
+:custom-decimals: xref:/erc20.adoc#customizing_decimals[Customizing decimals]
-== Interface
-
-[,cairo]
-----
-@contract_interface
-namespace IERC20 {
- func name() -> (name: felt) {
- }
-
- func symbol() -> (symbol: felt) {
- }
+The ERC20 token standard is a specification for {fungible-tokens}, a type of token where all the units are exactly equal to each other.
+The `token::erc20::ERC20` contract implements an approximation of {eip20} in Cairo for Starknet.
- func decimals() -> (decimals: felt) {
- }
+WARNING: Prior to {version}, ERC20 contracts store and read `decimals` from storage; however, this implementation returns a static `18`.
+If upgrading an older ERC20 contract that has a decimals value other than `18`, the upgraded contract *must* use a custom `decimals` implementation.
+See the {custom-decimals} guide.
- func totalSupply() -> (totalSupply: Uint256) {
- }
-
- func balanceOf(account: felt) -> (balance: Uint256) {
- }
-
- func allowance(owner: felt, spender: felt) -> (remaining: Uint256) {
- }
-
- func transfer(recipient: felt, amount: Uint256) -> (success: felt) {
- }
-
- func transferFrom(sender: felt, recipient: felt, amount: Uint256) -> (success: felt) {
- }
+== Interface
- func approve(spender: felt, amount: Uint256) -> (success: felt) {
- }
+:dual-interfaces: xref:/interfaces.adoc#dual_interfaces[Dual interfaces]
+:erc20-supply: xref:/guides/erc20-supply.adoc[Creating ERC20 Supply]
+
+The following interface represents the full ABI of the Contracts for Cairo `ERC20` preset.
+The interface includes the `IERC20` standard interface as well as non-standard functions like `increase_allowance`.
+To support older deployments of ERC20, as mentioned in {dual-interfaces}, `ERC20` also includes the previously defined functions written in camelCase.
+
+[,javascript]
+----
+trait ERC20ABI {
+ // IERC20
+ fn name() -> felt252;
+ fn symbol() -> felt252;
+ fn decimals() -> u8;
+ fn total_supply() -> u256;
+ fn balance_of(account: ContractAddress) -> u256;
+ fn allowance(owner: ContractAddress, spender: ContractAddress) -> u256;
+ fn transfer(recipient: ContractAddress, amount: u256) -> bool;
+ fn transfer_from(
+ sender: ContractAddress, recipient: ContractAddress, amount: u256
+ ) -> bool;
+ fn approve(spender: ContractAddress, amount: u256) -> bool;
+
+ // Non-standard
+ fn increase_allowance(spender: ContractAddress, added_value: u256) -> bool;
+ fn decrease_allowance(spender: ContractAddress, subtracted_value: u256) -> bool;
+
+ // Camel case compatibility
+ fn totalSupply() -> u256;
+ fn balanceOf(account: ContractAddress) -> u256;
+ fn transferFrom(
+ sender: ContractAddress, recipient: ContractAddress, amount: u256
+ ) -> bool;
+ fn increaseAllowance(spender: ContractAddress, addedValue: u256) -> bool;
+ fn decreaseAllowance(spender: ContractAddress, subtractedValue: u256) -> bool;
}
----
=== ERC20 compatibility
-Although StarkNet is not EVM compatible, this implementation aims to be as close as possible to the ERC20 standard, in the following ways:
-
-* It uses Cairo's `uint256` instead of `felt`.
-* It returns `TRUE` as success.
-* It accepts a `felt` argument for `decimals` in the constructor calldata with a max value of 2{caret}8 (imitating `uint8` type).
-* It makes use of Cairo's short strings to simulate `name` and `symbol`.
+:cairo-selectors: https://github.com/starkware-libs/cairo/blob/7dd34f6c57b7baf5cd5a30c15e00af39cb26f7e1/crates/cairo-lang-starknet/src/contract.rs#L39-L48[Cairo]
+:solidity-selectors: https://solidity-by-example.org/function-selector/[Solidity]
+:dual-interface: xref:/interfaces.adoc#dual_interfaces[dual interface]
-But some differences can still be found, such as:
+Although Starknet is not EVM compatible, this implementation aims to be as close as possible to the ERC20 standard.
+Some notable differences, however, can still be found, such as:
-* `transfer`, `transferFrom` and `approve` will never return anything different from `TRUE` because they will revert on any error.
-* Function selectors are calculated differently between https://github.com/starkware-libs/cairo-lang/blob/7712b21fc3b1cb02321a58d0c0579f5370147a8b/src/starkware/starknet/public/abi.py#L25[Cairo] and https://solidity-by-example.org/function-selector/[Solidity].
+* Cairo short strings are used to simulate `name` and `symbol` because Cairo does not yet include native string support.
+* The contract offers a {dual-interface} which supports both snake_case and camelCase methods, as opposed to just camelCase in Solidity.
+* `transfer`, `transfer_from` and `approve` will never return anything different from `true` because they will revert on any error.
+* Function selectors are calculated differently between {cairo-selectors} and {solidity-selectors}.
== Usage
-Use cases go from medium of exchange currency to voting rights, staking, and more.
+:components: https://community.starknet.io/t/cairo-1-contract-syntax-is-evolving/94794#extensibility-and-components-11[Components]
+:erc20-supply: xref:/guides/erc20-supply.adoc[Creating ERC20 Supply]
-Considering that the constructor method looks like this:
+WARNING: The following example uses `unsafe_new_contract_state` to access another contract's state.
+Although this is useful to use them as modules, it's considered unsafe because storage members could clash among used contracts if not reviewed carefully.
+Extensibility will be revisited after {components} are introduced.
-[,cairo]
-----
-func constructor(
- name: felt, // Token name as Cairo short string
- symbol: felt, // Token symbol as Cairo short string
- decimals: felt // Token decimals (usually 18)
- initial_supply: Uint256, // Amount to be minted
- recipient: felt // Address where to send initial supply to
-) {
-}
-----
-
-To create a token you need to deploy it like this:
-
-[,python]
-----
-erc20 = await starknet.deploy(
- "openzeppelin/token/erc20/presets/ERC20.cairo",
- constructor_calldata=[
- str_to_felt("Token"), # name
- str_to_felt("TKN"), # symbol
- 18, # decimals
- (1000, 0), # initial supply
- account.contract_address # recipient
- ]
-)
-----
-
-As most StarkNet contracts, it expects to be called by another contract and it identifies it through `get_caller_address` (analogous to Solidity's `this.address`).
-This is why we need an Account contract to interact with it.
-For example:
+Using Contracts for Cairo, constructing an ERC20 contract requires setting up the constructor and exposing the ERC20 interface.
+Here's what that looks like:
-[,python]
+[,javascript]
----
-signer = MockSigner(PRIVATE_KEY)
-amount = uint(100)
+#[starknet::contract]
+mod MyToken {
+ use starknet::ContractAddress;
+ use openzeppelin::token::erc20::ERC20;
-account = await starknet.deploy(
- "contracts/Account.cairo",
- constructor_calldata=[signer.public_key]
-)
+ #[storage]
+ struct Storage {}
-await signer.send_transaction(account, erc20.contract_address, 'transfer', [recipient_address, *amount])
-----
+ #[constructor]
+ fn constructor(
+ self: @ContractState,
+ initial_supply: u256,
+ recipient: ContractAddress
+ ) {
+ let name = 'MyToken';
+ let symbol = 'MTK';
-== Extensibility
+ let mut unsafe_state = ERC20::unsafe_new_contract_state();
+ ERC20::InternalImpl::initializer(ref unsafe_state, name, symbol);
+ ERC20::InternalImpl::_mint(ref unsafe_state, recipient, initial_supply);
+ }
-ERC20 contracts can be extended by following the xref:extensibility.adoc#the_pattern[extensibility pattern].
-The basic idea behind integrating the pattern is to import the requisite ERC20 methods from the ERC20 library and incorporate the extended logic thereafter.
-For example, let's say you wanted to implement a pausing mechanism.
-The contract should first import the ERC20 methods and the extended logic from the https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.6.1/src/openzeppelin/security/pausable/library.cairo[Pausable library] i.e. `Pausable.pause`, `Pausable.unpause`.
-Next, the contract should expose the methods with the extended logic therein like this:
+ #[external(v0)]
+ impl MyTokenImpl of IERC20 {
+ fn name(self: @ContractState) -> felt252 {
+ let mut unsafe_state = ERC20::unsafe_new_contract_state();
+ ERC20::ERC20Impl::name(@unsafe_state)
+ }
-[,cairo]
-----
-@external
-func transfer{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
- recipient: felt, amount: Uint256
-) -> (success: felt) {
- Pausable.assert_not_paused(); // imported extended logic
- return ERC20.transfer(recipient, amount); // imported library method
+ (...)
+ }
}
----
-Note that extensibility does not have to be only library-based like in the above example.
-For instance, an ERC20 contract with a pausing mechanism can define the pausing methods directly in the contract or even import the `pausable` methods from the library and tailor them further.
-
-Some other ways to extend ERC20 contracts may include:
-
-* Implementing a minting mechanism.
-* Creating a timelock.
-* Adding roles such as owner or minter.
-
-For full examples of the extensibility pattern being used in ERC20 contracts, see <>.
-
-== Presets
-
-The following contract presets are ready to deploy and can be used as-is for quick prototyping and testing.
-Each preset mints an initial supply which is especially necessary for presets that do not expose a `mint` method.
+In order for the `MyToken` contract to extend the ERC20 contract, it utilizes the `unsafe_new_contract_state`.
+The unsafe contract state allows access to ERC20's implementations.
+With this access, the constructor first calls the initializer to set the token name and symbol.
+The constructor then calls `_mint` to create a fixed supply of tokens.
-=== ERC20 (basic)
+TIP: For a more complete guide on ERC20 token mechanisms, see {erc20-supply}.
-The https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.6.1/src/openzeppelin/token/erc20/presets/ERC20.cairo[`ERC20`] preset offers a quick and easy setup for deploying a basic ERC20 token.
+In the external implementation, notice that `MyTokenImpl` is an implementation of `IERC20`.
+This ensures that the external implementation will include all of the methods defined in the interface.
-=== ERC20Mintable
+=== Customizing decimals
-The https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.6.1/src/openzeppelin/token/erc20/presets/ERC20Mintable.cairo[`ERC20Mintable`] preset allows the contract owner to mint new tokens.
+:floating-point: https://en.wikipedia.org//wiki/Floating-point_arithmetic[floating-point numbers]
+:eip-discussion: https://github.com/ethereum/EIPs/issues/724[EIP discussion]
-=== ERC20Pausable
+Cairo, like Solidity, does not support {floating-point}.
+To get around this limitation, ERC20 offers a `decimals` field which communicates to outside interfaces (wallets, exchanges, etc.) how the token should be displayed.
+For instance, suppose a token had a `decimals` value of `3` and the total token supply was `1234`.
+An outside interface would display the token supply as `1.234`.
+In the actual contract, however, the supply would still be the integer `1234`.
+In other words, *the decimals field in no way changes the actual arithmetic* because all operations are still performed on integers.
-The https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.6.1/src/openzeppelin/token/erc20/presets/ERC20Pausable.cairo[`ERC20Pausable`] preset allows the contract owner to pause/unpause all state-modifying methods i.e.
-`transfer`, `approve`, etc.
-This preset proves useful for scenarios such as preventing trades until the end of an evaluation period and having an emergency switch for freezing all token transfers in the event of a large bug.
+Most contracts use `18` decimals and this was even proposed to be compulsory (see the {eip-discussion}).
+The Contracts for Cairo ERC20 implementation of `decimals` returns `18` by default to save on gas fees.
+For those who want an ERC20 token with a configurable number of decimals, the following guide shows two ways to achieve this.
-=== ERC20Upgradeable
+==== The static approach
-The https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.6.1/src/openzeppelin/token/erc20/presets/ERC20Upgradeable.cairo[`ERC20Upgradeable`] preset allows the contract owner to upgrade a contract by deploying a new ERC20 implementation contract while also maintaining the contract's state.
-This preset proves useful for scenarios such as eliminating bugs and adding new features.
-For more on upgradeability, see xref:proxies.adoc#contract_upgrades[Contract upgrades].
-
-== API Specification
-
-=== Methods
+The simplest way to customize `decimals` consists of returning the target value when exposing the `decimals` method.
+For example:
-[,cairo]
+[,javascript]
----
-func name() -> (name: felt) {
-}
-
-func symbol() -> (symbol: felt) {
-}
-
-func decimals() -> (decimals: felt) {
-}
-
-func totalSupply() -> (totalSupply: Uint256) {
-}
-
-func balanceOf(account: felt) -> (balance: Uint256) {
-}
-
-func allowance(owner: felt, spender: felt) -> (remaining: Uint256) {
-}
-
-func transfer(recipient: felt, amount: Uint256) -> (success: felt) {
-}
-
-func transferFrom(sender: felt, recipient: felt, amount: Uint256) -> (success: felt) {
-}
+#[external(v0)]
+impl MyTokenImpl of IERC20 {
+ fn decimals(self: @ContractState) -> u8 {
+ // Change the `3` below to the desired number of decimals
+ 3
+ }
-func approve(spender: felt, amount: Uint256) -> (success: felt) {
+ (...)
}
-----
-
-==== `name`
-
-Returns the name of the token.
-
-Parameters: None.
-
-Returns:
-
-[,cairo]
-----
-name: felt
-----
-
-==== `symbol`
-
-Returns the ticker symbol of the token.
-
-Parameters: None.
-
-Returns:
-
-[,cairo]
-----
-symbol: felt
-----
-
-==== `decimals`
-
-Returns the number of decimals the token uses - e.g.
-`8` means to divide the token amount by `100000000` to get its user representation.
-
-Parameters: None.
-Returns:
-
-[,cairo]
-----
-decimals: felt
-----
-
-==== `totalSupply`
-
-Returns the amount of tokens in existence.
-
-Parameters: None.
-
-Returns:
-
-[,cairo]
-----
-totalSupply: Uint256
-----
-
-==== `balanceOf`
-
-Returns the amount of tokens owned by `account`.
-
-Parameters:
-
-[,cairo]
-----
-account: felt
-----
-
-Returns:
-
-[,cairo]
-----
-balance: Uint256
-----
-
-==== `allowance`
-
-Returns the remaining number of tokens that `spender` will be allowed to spend on behalf of `owner` through `transferFrom`.
-This is zero by default.
-
-This value changes when `approve` or `transferFrom` are called.
-
-Parameters:
-
-[,cairo]
-----
-owner: felt
-spender: felt
-----
-
-Returns:
-
-[,cairo]
-----
-remaining: Uint256
-----
-
-==== `transfer`
-
-Moves `amount` tokens from the caller's account to `recipient`.
-It returns `1` representing a bool if it succeeds.
-
-Emits a <> event.
-
-Parameters:
-
-[,cairo]
-----
-recipient: felt
-amount: Uint256
----
-Returns:
+==== The storage approach
-[,cairo]
-----
-success: felt
-----
-
-==== `transferFrom`
-
-Moves `amount` tokens from `sender` to `recipient` using the allowance mechanism.
-`amount` is then deducted from the caller's allowance.
-It returns `1` representing a bool if it succeeds.
-
-Emits a <> event.
+For more complex scenarios, such as a factory deploying multiple tokens with differing values for decimals, a flexible solution might be appropriate.
-Parameters:
-
-[,cairo]
-----
-sender: felt
-recipient: felt
-amount: Uint256
+[,javascript]
----
+#[starknet::contract]
+mod MyToken {
+ use starknet::ContractAddress;
+ use openzeppelin::token::erc20::ERC20;
-Returns:
-
-[,cairo]
-----
-success: felt
-----
-
-==== `approve`
-
-Sets `amount` as the allowance of `spender` over the caller's tokens.
-It returns `1` representing a bool if it succeeds.
+ #[storage]
+ struct Storage {
+ // The decimals value is stored locally
+ _decimals: u8,
+ }
-Emits an <> event.
+ #[constructor]
+ fn constructor(
+ ref self: ContractState,
+ decimals: u8
+ ) {
+ // Call the internal function that writes decimals to storage
+ self._set_decimals(decimals);
-Parameters:
+ // Initialize ERC20
+ let name = 'MyToken';
+ let symbol = 'MTK';
-[,cairo]
-----
-spender: felt
-amount: Uint256
-----
-
-Returns:
-
-[,cairo]
-----
-success: felt
-----
-
-=== Events
+ let mut unsafe_state = ERC20::unsafe_new_contract_state();
+ ERC20::InternalImpl::initializer(ref unsafe_state, name, symbol);
+ }
-[,cairo]
-----
-func Transfer(from_: felt, to: felt, value: Uint256) {
-}
+ /// This is a standalone function for brevity.
+ /// It's recommended to create an implementation of IERC20
+ /// to ensure that the contract exposes the entire ERC20 interface.
+ /// See the previous example.
+ #[external(v0)]
+ fn decimals(self: @ContractState) -> u8 {
+ self._decimals.read()
+ }
-func Approval(owner: felt, spender: felt, value: Uint256) {
+ #[generate_trait]
+ impl InternalImpl of InternalTrait {
+ fn _set_decimals(ref self: ContractState, decimals: u8) {
+ self._decimals.write(decimals);
+ }
+ }
}
----
-==== `Transfer (event)`
-
-Emitted when `value` tokens are moved from one account (`from_`) to another (`to`).
-
-Note that `value` may be zero.
-
-Parameters:
-
-[,cairo]
-----
-from_: felt
-to: felt
-value: Uint256
-----
-
-==== `Approval (event)`
-
-Emitted when the allowance of a `spender` for an `owner` is set by a call to <>.
-`value` is the new allowance.
-
-Parameters:
-
-[,cairo]
-----
-owner: felt
-spender: felt
-value: Uint256
-----
+This contract expects a `decimals` argument in the constructor and uses an internal function to write the decimals to storage.
+Note that the `_decimals` state variable must be stored in the local contract's storage because this variable does not exist in the Contracts for Cairo library.
+It's important to include the correct logic in the exposed `decimals` method and to NOT use the Contracts for Cairo `decimals` implementation in this specific case.
+The library's `decimals` implementation does not read from storage and will return `18`.
diff --git a/docs/modules/ROOT/pages/guides/erc20-supply.adoc b/docs/modules/ROOT/pages/guides/erc20-supply.adoc
new file mode 100644
index 000000000..ad8a51139
--- /dev/null
+++ b/docs/modules/ROOT/pages/guides/erc20-supply.adoc
@@ -0,0 +1,138 @@
+= Creating ERC20 Supply
+
+:eip-20: https://eips.ethereum.org/EIPS/eip-20[EIP20]
+
+The standard interface implemented by tokens built on Starknet comes from the popular token standard on Ethereum called ERC20.
+{eip-20}, from which ERC20 contracts are derived, does not specify how tokens are created.
+This guide will go over strategies for creating both a fixed and dynamic token supply.
+
+== Fixed Supply
+
+Let's say we want to create a token named `MyToken` with a fixed token supply.
+We can achieve this by setting the token supply in the constructor which will execute upon deployment.
+
+[,javascript]
+----
+#[starknet::contract]
+mod MyToken {
+ use starknet::ContractAddress;
+ use openzeppelin::token::erc20::ERC20;
+
+ #[storage]
+ struct Storage {}
+
+ #[constructor]
+ fn constructor(
+ self: @ContractState,
+ initial_supply: u256,
+ recipient: ContractAddress
+ ) {
+ let name = 'MyToken';
+ let symbol = 'MTK';
+
+ let mut unsafe_state = ERC20::unsafe_new_contract_state();
+ ERC20::InternalImpl::initializer(ref unsafe_state, name, symbol);
+ ERC20::InternalImpl::_mint(ref unsafe_state, recipient, initial_supply);
+ }
+}
+----
+
+In the constructor, we're first grabbing the ERC20 state and calling the ERC20 initializer to set the token name and symbol.
+Next, we're calling the internal `_mint` function which creates `initial_supply` of tokens and allocates them to `recipient`.
+Since the internal `_mint` is not exposed in our contract, it will not be possible to create any more tokens.
+In other words, we've implemented a fixed token supply!
+
+== Dynamic Supply
+
+:access-control: xref:/access.adoc[Access Control]
+
+ERC20 contracts with a dynamic supply include a mechanism for creating or destroying tokens.
+Let's make a few changes to the almighty `MyToken` contract and create a minting mechanism.
+
+[,javascript]
+----
+#[starknet::contract]
+mod MyToken {
+ use starknet::ContractAddress;
+ use openzeppelin::token::erc20::ERC20;
+
+ #[storage]
+ struct Storage {}
+
+ #[constructor]
+ fn constructor(self: @ContractState) {
+ let name = 'MyToken';
+ let symbol = 'MTK';
+
+ let mut unsafe_state = ERC20::unsafe_new_contract_state();
+ ERC20::InternalImpl::initializer(ref unsafe_state, name, symbol);
+ }
+
+ #[external(v0)]
+ fn mint(
+ self: @ContractState,
+ recipient: ContractAddress,
+ amount: u256
+ ) {
+ // This function is NOT protected which means
+ // ANYONE can mint tokens
+ let mut unsafe_state = ERC20::unsafe_new_contract_state();
+ ERC20::InternalImpl::_mint(ref unsafe_state, recipient, amount);
+ }
+}
+----
+
+The exposed `mint` above will create `amount` tokens and allocate them to `recipient`.
+We now have our minting mechanism!
+
+There is, however, a big problem.
+`mint` does not include any restrictions on who can call this function.
+For the sake of good practices, let's implement a simple permissioning mechanism with `Ownable`.
+
+[,javascript]
+----
+#[starknet::contract]
+mod MyToken {
+ use starknet::ContractAddress;
+ use openzeppelin::access::ownable::Ownable;
+ use openzeppelin::token::erc20::ERC20;
+
+ #[storage]
+ struct Storage {}
+
+ #[constructor]
+ fn constructor(self: @ContractState, owner: ContractAddress) {
+ // Set contract owner
+ let mut unsafe_ownable = Ownable::unsafe_new_contract_state();
+ Ownable::InternalImpl::initializer(ref unsafe_ownable, owner);
+
+ // Initialize ERC20
+ let name = 'MyToken';
+ let symbol = 'MTK';
+
+ let mut unsafe_erc20 = ERC20::unsafe_new_contract_state();
+ ERC20::InternalImpl::initializer(ref unsafe_erc20, name, symbol);
+ }
+
+ #[external(v0)]
+ fn mint(
+ self: @ContractState,
+ recipient: ContractAddress,
+ amount: u256
+ ) {
+ // Set permissions with Ownable
+ let unsafe_ownable = Ownable::unsafe_new_contract_state();
+ Ownable::InternalImpl::assert_only_owner(@unsafe_ownable);
+
+ // Mint tokens if called by the contract owner
+ let mut unsafe_erc20 = ERC20::unsafe_new_contract_state();
+ ERC20::InternalImpl::_mint(ref unsafe_erc20, recipient, amount);
+ }
+}
+----
+
+In the constructor, we pass the owner address to set the owner of the `MyToken` contract.
+The `mint` function includes `assert_only_owner` which will ensure that only the contract owner can call this function.
+Now, we have a protected ERC20 minting mechanism to create a dynamic token supply.
+
+TIP: For a more thorough explanation of permission mechanisms, see {access-control}.
diff --git a/src/token/erc20/erc20.cairo b/src/token/erc20/erc20.cairo
index ee1204d36..fa66fde44 100644
--- a/src/token/erc20/erc20.cairo
+++ b/src/token/erc20/erc20.cairo
@@ -1,6 +1,12 @@
-// SPDX-License-Identifier: MIT
-// OpenZeppelin Contracts for Cairo v0.7.0 (token/erc20/erc20.cairo)
-
+//! SPDX-License-Identifier: MIT
+//! OpenZeppelin Contracts for Cairo v0.7.0 (token/erc20/erc20.cairo)
+//!
+//! # ERC20 Contract and Implementation
+//!
+//! This ERC20 contract includes both a library and a basic preset implementation.
+//! The library is agnostic regarding how tokens are created; however,
+//! the preset implementation sets the initial supply in the constructor.
+//! A derived contract can use [_mint](_mint) to create a different supply mechanism.
#[starknet::contract]
mod ERC20 {
use integer::BoundedInt;
@@ -25,6 +31,7 @@ mod ERC20 {
Approval: Approval,
}
+ /// Emitted when tokens are moved from address `from` to address `to`.
#[derive(Drop, starknet::Event)]
struct Transfer {
#[key]
@@ -34,6 +41,8 @@ mod ERC20 {
value: u256
}
+ /// Emitted when the allowance of a `spender` for an `owner` is set by a call
+ /// to [approve](approve). `value` is the new allowance.
#[derive(Drop, starknet::Event)]
struct Approval {
#[key]
@@ -52,6 +61,8 @@ mod ERC20 {
const MINT_TO_ZERO: felt252 = 'ERC20: mint to 0';
}
+ /// Initializes the state of the ERC20 contract. This includes setting the
+ /// initial supply of tokens as well as the recipient of the initial supply.
#[constructor]
fn constructor(
ref self: ContractState,
@@ -70,38 +81,53 @@ mod ERC20 {
#[external(v0)]
impl ERC20Impl of IERC20 {
+ /// Returns the name of the token.
fn name(self: @ContractState) -> felt252 {
self.ERC20_name.read()
}
+ /// Returns the ticker symbol of the token, usually a shorter version of the name.
fn symbol(self: @ContractState) -> felt252 {
self.ERC20_symbol.read()
}
+ /// Returns the number of decimals used to get its user representation.
fn decimals(self: @ContractState) -> u8 {
18
}
+ /// Returns the value of tokens in existence.
fn total_supply(self: @ContractState) -> u256 {
self.ERC20_total_supply.read()
}
+ /// Returns the amount of tokens owned by `account`.
fn balance_of(self: @ContractState, account: ContractAddress) -> u256 {
self.ERC20_balances.read(account)
}
+ /// Returns the remaining number of tokens that `spender` is
+ /// allowed to spend on behalf of `owner` through [transfer_from](transfer_from).
+ /// This is zero by default.
+ /// This value changes when [approve](approve) or [transfer_from](transfer_from)
+ /// are called.
fn allowance(
self: @ContractState, owner: ContractAddress, spender: ContractAddress
) -> u256 {
self.ERC20_allowances.read((owner, spender))
}
+ /// Moves `amount` tokens from the caller's token balance to `to`.
+ /// Emits a [Transfer](Transfer) event.
fn transfer(ref self: ContractState, recipient: ContractAddress, amount: u256) -> bool {
let sender = get_caller_address();
self._transfer(sender, recipient, amount);
true
}
+ /// Moves `amount` tokens from `from` to `to` using the allowance mechanism.
+ /// `amount` is then deducted from the caller's allowance.
+ /// Emits a [Transfer](Transfer) event.
fn transfer_from(
ref self: ContractState,
sender: ContractAddress,
@@ -114,6 +140,7 @@ mod ERC20 {
true
}
+ /// Sets `amount` as the allowance of `spender` over the caller’s tokens.
fn approve(ref self: ContractState, spender: ContractAddress, amount: u256) -> bool {
let caller = get_caller_address();
self._approve(caller, spender, amount);
@@ -121,16 +148,40 @@ mod ERC20 {
}
}
+ /// Increases the allowance granted from the caller to `spender` by `added_value`.
+ /// Emits an [Approval](Approval) event indicating the updated allowance.
+ #[external(v0)]
+ fn increase_allowance(
+ ref self: ContractState, spender: ContractAddress, added_value: u256
+ ) -> bool {
+ self._increase_allowance(spender, added_value)
+ }
+
+ /// Decreases the allowance granted from the caller to `spender` by `subtracted_value`.
+ /// Emits an [Approval](Approval) event indicating the updated allowance.
+ #[external(v0)]
+ fn decrease_allowance(
+ ref self: ContractState, spender: ContractAddress, subtracted_value: u256
+ ) -> bool {
+ self._decrease_allowance(spender, subtracted_value)
+ }
+
#[external(v0)]
impl ERC20CamelOnlyImpl of IERC20CamelOnly {
+ /// Camel case support.
+ /// See [total_supply](total-supply).
fn totalSupply(self: @ContractState) -> u256 {
ERC20Impl::total_supply(self)
}
+ /// Camel case support.
+ /// See [balance_of](balance_of).
fn balanceOf(self: @ContractState, account: ContractAddress) -> u256 {
ERC20Impl::balance_of(self, account)
}
+ /// Camel case support.
+ /// See [transfer_from](transfer_from).
fn transferFrom(
ref self: ContractState,
sender: ContractAddress,
@@ -141,13 +192,8 @@ mod ERC20 {
}
}
- #[external(v0)]
- fn increase_allowance(
- ref self: ContractState, spender: ContractAddress, added_value: u256
- ) -> bool {
- self._increase_allowance(spender, added_value)
- }
-
+ /// Camel case support.
+ /// See [increase_allowance](increase_allowance).
#[external(v0)]
fn increaseAllowance(
ref self: ContractState, spender: ContractAddress, addedValue: u256
@@ -155,13 +201,8 @@ mod ERC20 {
increase_allowance(ref self, spender, addedValue)
}
- #[external(v0)]
- fn decrease_allowance(
- ref self: ContractState, spender: ContractAddress, subtracted_value: u256
- ) -> bool {
- self._decrease_allowance(spender, subtracted_value)
- }
-
+ /// Camel case support.
+ /// See [decrease_allowance](decrease_allowance).
#[external(v0)]
fn decreaseAllowance(
ref self: ContractState, spender: ContractAddress, subtractedValue: u256
@@ -175,11 +216,60 @@ mod ERC20 {
#[generate_trait]
impl InternalImpl of InternalTrait {
+ /// Initializes the contract by setting the token name and symbol.
+ /// To prevent reinitialization, this should only be used inside of a contract's constructor.
fn initializer(ref self: ContractState, name: felt252, symbol: felt252) {
self.ERC20_name.write(name);
self.ERC20_symbol.write(symbol);
}
+ /// Internal method that moves an `amount` of tokens from `from` to `to`.
+ /// Emits a [Transfer](Transfer) event.
+ fn _transfer(
+ ref self: ContractState,
+ sender: ContractAddress,
+ recipient: ContractAddress,
+ amount: u256
+ ) {
+ assert(!sender.is_zero(), Errors::TRANSFER_FROM_ZERO);
+ assert(!recipient.is_zero(), Errors::TRANSFER_TO_ZERO);
+ self.ERC20_balances.write(sender, self.ERC20_balances.read(sender) - amount);
+ self.ERC20_balances.write(recipient, self.ERC20_balances.read(recipient) + amount);
+ self.emit(Transfer { from: sender, to: recipient, value: amount });
+ }
+
+ /// Internal method that sets `amount` as the allowance of `spender` over the
+ /// `owner`s tokens.
+ /// Emits an [Approval](Approval) event.
+ fn _approve(
+ ref self: ContractState, owner: ContractAddress, spender: ContractAddress, amount: u256
+ ) {
+ assert(!owner.is_zero(), Errors::APPROVE_FROM_ZERO);
+ assert(!spender.is_zero(), Errors::APPROVE_TO_ZERO);
+ self.ERC20_allowances.write((owner, spender), amount);
+ self.emit(Approval { owner, spender, value: amount });
+ }
+
+ /// Creates a `value` amount of tokens and assigns them to `account`.
+ /// Emits a [Transfer](Transfer) event with `from` set to the zero address.
+ fn _mint(ref self: ContractState, recipient: ContractAddress, amount: u256) {
+ assert(!recipient.is_zero(), Errors::MINT_TO_ZERO);
+ self.ERC20_total_supply.write(self.ERC20_total_supply.read() + amount);
+ self.ERC20_balances.write(recipient, self.ERC20_balances.read(recipient) + amount);
+ self.emit(Transfer { from: Zeroable::zero(), to: recipient, value: amount });
+ }
+
+ /// Destroys a `value` amount of tokens from `account`.
+ /// Emits a [Transfer](Transfer) event with `to` set to the zero address.
+ fn _burn(ref self: ContractState, account: ContractAddress, amount: u256) {
+ assert(!account.is_zero(), Errors::BURN_FROM_ZERO);
+ self.ERC20_total_supply.write(self.ERC20_total_supply.read() - amount);
+ self.ERC20_balances.write(account, self.ERC20_balances.read(account) - amount);
+ self.emit(Transfer { from: account, to: Zeroable::zero(), value: amount });
+ }
+
+ /// Internal method for the external [increase_allowance](increase_allowance).
+ /// Emits an [Approval](Approval) event indicating the updated allowance.
fn _increase_allowance(
ref self: ContractState, spender: ContractAddress, added_value: u256
) -> bool {
@@ -191,6 +281,8 @@ mod ERC20 {
true
}
+ /// Internal method for the external [decrease_allowance](decrease_allowance).
+ /// Emits an [Approval](Approval) event indicating the updated allowance.
fn _decrease_allowance(
ref self: ContractState, spender: ContractAddress, subtracted_value: u256
) -> bool {
@@ -204,42 +296,9 @@ mod ERC20 {
true
}
- fn _mint(ref self: ContractState, recipient: ContractAddress, amount: u256) {
- assert(!recipient.is_zero(), Errors::MINT_TO_ZERO);
- self.ERC20_total_supply.write(self.ERC20_total_supply.read() + amount);
- self.ERC20_balances.write(recipient, self.ERC20_balances.read(recipient) + amount);
- self.emit(Transfer { from: Zeroable::zero(), to: recipient, value: amount });
- }
-
- fn _burn(ref self: ContractState, account: ContractAddress, amount: u256) {
- assert(!account.is_zero(), Errors::BURN_FROM_ZERO);
- self.ERC20_total_supply.write(self.ERC20_total_supply.read() - amount);
- self.ERC20_balances.write(account, self.ERC20_balances.read(account) - amount);
- self.emit(Transfer { from: account, to: Zeroable::zero(), value: amount });
- }
-
- fn _approve(
- ref self: ContractState, owner: ContractAddress, spender: ContractAddress, amount: u256
- ) {
- assert(!owner.is_zero(), Errors::APPROVE_FROM_ZERO);
- assert(!spender.is_zero(), Errors::APPROVE_TO_ZERO);
- self.ERC20_allowances.write((owner, spender), amount);
- self.emit(Approval { owner, spender, value: amount });
- }
-
- fn _transfer(
- ref self: ContractState,
- sender: ContractAddress,
- recipient: ContractAddress,
- amount: u256
- ) {
- assert(!sender.is_zero(), Errors::TRANSFER_FROM_ZERO);
- assert(!recipient.is_zero(), Errors::TRANSFER_TO_ZERO);
- self.ERC20_balances.write(sender, self.ERC20_balances.read(sender) - amount);
- self.ERC20_balances.write(recipient, self.ERC20_balances.read(recipient) + amount);
- self.emit(Transfer { from: sender, to: recipient, value: amount });
- }
-
+ /// Updates `owner`s allowance for `spender` based on spent `amount`.
+ /// Does not update the allowance value in case of infinite allowance.
+ /// Possibly emits an [Approval](Approval) event.
fn _spend_allowance(
ref self: ContractState, owner: ContractAddress, spender: ContractAddress, amount: u256
) {