-
Notifications
You must be signed in to change notification settings - Fork 91
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: add ERC20 example * fix: broken cairo format check quickfix
- Loading branch information
Showing
7 changed files
with
253 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
target |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
[package] | ||
name = "erc20" | ||
version = "0.1.0" | ||
|
||
[dependencies] | ||
starknet = ">=2.3.0-rc0" | ||
|
||
[[target.starknet-contract]] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
mod token; | ||
|
||
#[cfg(test)] | ||
mod tests; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
mod tests { // TODO | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,215 @@ | ||
use starknet::ContractAddress; | ||
|
||
// ANCHOR: interface | ||
#[starknet::interface] | ||
trait IERC20<TContractState> { | ||
fn get_name(self: @TContractState) -> felt252; | ||
fn get_symbol(self: @TContractState) -> felt252; | ||
fn get_decimals(self: @TContractState) -> u8; | ||
fn get_total_supply(self: @TContractState) -> felt252; | ||
fn balance_of(self: @TContractState, account: ContractAddress) -> felt252; | ||
fn allowance( | ||
self: @TContractState, owner: ContractAddress, spender: ContractAddress | ||
) -> felt252; | ||
fn transfer(ref self: TContractState, recipient: ContractAddress, amount: felt252); | ||
fn transfer_from( | ||
ref self: TContractState, | ||
sender: ContractAddress, | ||
recipient: ContractAddress, | ||
amount: felt252 | ||
); | ||
fn approve(ref self: TContractState, spender: ContractAddress, amount: felt252); | ||
fn increase_allowance(ref self: TContractState, spender: ContractAddress, added_value: felt252); | ||
fn decrease_allowance( | ||
ref self: TContractState, spender: ContractAddress, subtracted_value: felt252 | ||
); | ||
} | ||
// ANCHOR_END: interface | ||
|
||
// ANCHOR: erc20 | ||
#[starknet::contract] | ||
mod erc20 { | ||
use zeroable::Zeroable; | ||
use starknet::get_caller_address; | ||
use starknet::contract_address_const; | ||
use starknet::ContractAddress; | ||
|
||
#[storage] | ||
struct Storage { | ||
name: felt252, | ||
symbol: felt252, | ||
decimals: u8, | ||
total_supply: felt252, | ||
balances: LegacyMap::<ContractAddress, felt252>, | ||
allowances: LegacyMap::<(ContractAddress, ContractAddress), felt252>, | ||
} | ||
|
||
#[event] | ||
#[derive(Drop, starknet::Event)] | ||
enum Event { | ||
Transfer: Transfer, | ||
Approval: Approval, | ||
} | ||
#[derive(Drop, starknet::Event)] | ||
struct Transfer { | ||
from: ContractAddress, | ||
to: ContractAddress, | ||
value: felt252, | ||
} | ||
#[derive(Drop, starknet::Event)] | ||
struct Approval { | ||
owner: ContractAddress, | ||
spender: ContractAddress, | ||
value: felt252, | ||
} | ||
|
||
mod Errors { | ||
const APPROVE_FROM_ZERO: felt252 = 'ERC20: approve from 0'; | ||
const APPROVE_TO_ZERO: felt252 = 'ERC20: approve to 0'; | ||
const TRANSFER_FROM_ZERO: felt252 = 'ERC20: transfer from 0'; | ||
const TRANSFER_TO_ZERO: felt252 = 'ERC20: transfer to 0'; | ||
const BURN_FROM_ZERO: felt252 = 'ERC20: burn from 0'; | ||
const MINT_TO_ZERO: felt252 = 'ERC20: mint to 0'; | ||
} | ||
|
||
#[constructor] | ||
fn constructor( | ||
ref self: ContractState, | ||
recipient: ContractAddress, | ||
name: felt252, | ||
decimals: u8, | ||
initial_supply: felt252, | ||
symbol: felt252 | ||
) { | ||
self.name.write(name); | ||
self.symbol.write(symbol); | ||
self.decimals.write(decimals); | ||
self.mint(recipient, initial_supply); | ||
} | ||
|
||
#[external(v0)] | ||
impl IERC20Impl of super::IERC20<ContractState> { | ||
fn get_name(self: @ContractState) -> felt252 { | ||
self.name.read() | ||
} | ||
|
||
fn get_symbol(self: @ContractState) -> felt252 { | ||
self.symbol.read() | ||
} | ||
|
||
fn get_decimals(self: @ContractState) -> u8 { | ||
self.decimals.read() | ||
} | ||
|
||
fn get_total_supply(self: @ContractState) -> felt252 { | ||
self.total_supply.read() | ||
} | ||
|
||
fn balance_of(self: @ContractState, account: ContractAddress) -> felt252 { | ||
self.balances.read(account) | ||
} | ||
|
||
fn allowance( | ||
self: @ContractState, owner: ContractAddress, spender: ContractAddress | ||
) -> felt252 { | ||
self.allowances.read((owner, spender)) | ||
} | ||
|
||
fn transfer(ref self: ContractState, recipient: ContractAddress, amount: felt252) { | ||
let sender = get_caller_address(); | ||
self._transfer(sender, recipient, amount); | ||
} | ||
|
||
fn transfer_from( | ||
ref self: ContractState, | ||
sender: ContractAddress, | ||
recipient: ContractAddress, | ||
amount: felt252 | ||
) { | ||
let caller = get_caller_address(); | ||
self.spend_allowance(sender, caller, amount); | ||
self._transfer(sender, recipient, amount); | ||
} | ||
|
||
fn approve(ref self: ContractState, spender: ContractAddress, amount: felt252) { | ||
let caller = get_caller_address(); | ||
self.approve_helper(caller, spender, amount); | ||
} | ||
|
||
fn increase_allowance( | ||
ref self: ContractState, spender: ContractAddress, added_value: felt252 | ||
) { | ||
let caller = get_caller_address(); | ||
self | ||
.approve_helper( | ||
caller, spender, self.allowances.read((caller, spender)) + added_value | ||
); | ||
} | ||
|
||
fn decrease_allowance( | ||
ref self: ContractState, spender: ContractAddress, subtracted_value: felt252 | ||
) { | ||
let caller = get_caller_address(); | ||
self | ||
.approve_helper( | ||
caller, spender, self.allowances.read((caller, spender)) - subtracted_value | ||
); | ||
} | ||
} | ||
|
||
#[generate_trait] | ||
impl InternalImpl of InternalTrait { | ||
fn _transfer( | ||
ref self: ContractState, | ||
sender: ContractAddress, | ||
recipient: ContractAddress, | ||
amount: felt252 | ||
) { | ||
assert(!sender.is_zero(), Errors::TRANSFER_FROM_ZERO); | ||
assert(!recipient.is_zero(), Errors::TRANSFER_TO_ZERO); | ||
self.balances.write(sender, self.balances.read(sender) - amount); | ||
self.balances.write(recipient, self.balances.read(recipient) + amount); | ||
self.emit(Transfer { from: sender, to: recipient, value: amount }); | ||
} | ||
|
||
fn spend_allowance( | ||
ref self: ContractState, | ||
owner: ContractAddress, | ||
spender: ContractAddress, | ||
amount: felt252 | ||
) { | ||
let allowance = self.allowances.read((owner, spender)); | ||
self.allowances.write((owner, spender), allowance - amount); | ||
} | ||
|
||
fn approve_helper( | ||
ref self: ContractState, | ||
owner: ContractAddress, | ||
spender: ContractAddress, | ||
amount: felt252 | ||
) { | ||
assert(!spender.is_zero(), Errors::APPROVE_TO_ZERO); | ||
self.allowances.write((owner, spender), amount); | ||
self.emit(Approval { owner, spender, value: amount }); | ||
} | ||
|
||
fn mint(ref self: ContractState, recipient: ContractAddress, amount: felt252) { | ||
assert(!recipient.is_zero(), Errors::MINT_TO_ZERO); | ||
let supply = self.total_supply.read() + amount; // What can go wrong here? | ||
self.total_supply.write(supply); | ||
let balance = self.balances.read(recipient) + amount; | ||
self.balances.write(recipient, amount); | ||
self | ||
.emit( | ||
Event::Transfer( | ||
Transfer { | ||
from: contract_address_const::<0>(), to: recipient, value: amount | ||
} | ||
) | ||
); | ||
} | ||
} | ||
} | ||
// ANCHOR_END: erc20 | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
# ERC20 Token | ||
|
||
Contracts that follow the [ERC20 Standard](https://eips.ethereum.org/EIPS/eip-20) are called ERC20 tokens. They are used to represent fungible assets. | ||
|
||
To create an ERC20 conctract, it must implement the following interface: | ||
|
||
```rust | ||
{{#include ../../listings/ch01-applications/erc20/src/token.cairo:interface}} | ||
``` | ||
|
||
In Starknet, function names should be written in *snake_case*. This is not the case in Solidity, where function names are written in *camelCase*. | ||
The Starknet ERC20 interface is therefore slightly different from the Solidity ERC20 interface. | ||
|
||
Here's an implementation of the ERC20 interface in Cairo: | ||
|
||
```rust | ||
{{#include ../../listings/ch01-applications/erc20/src/token.cairo:erc20}} | ||
``` | ||
|
||
Play with this contract in [Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/ch01-applications/erc20/src/token.cairo). | ||
|
||
There's several other implementations, such as the [Open Zeppelin](https://docs.openzeppelin.com/contracts-cairo/0.7.0/erc20) or the [Cairo By Example](https://cairo-by-example.com/examples/erc20/) ones. |