Any contract that follows the ERC-20 standard is an ERC-20 token.
ERC-20 tokens provide functionalities to
Here is the interface for ERC-20.
1interface IERC20 {
2 function totalSupply() external view returns (uint256);
3 function balanceOf(address account) external view returns (uint256);
4 function transfer(address recipient, uint256 amount)
5 external
6 returns (bool);
7 function allowance(address owner, address spender)
8 external
9 view
10 returns (uint256);
11 function approve(address spender, uint256 amount) external returns (bool);
12 function transferFrom(address sender, address recipient, uint256 amount)
13 external
14 returns (bool);
15}
1interface IERC20 {
2 function totalSupply() external view returns (uint256);
3 function balanceOf(address account) external view returns (uint256);
4 function transfer(address recipient, uint256 amount)
5 external
6 returns (bool);
7 function allowance(address owner, address spender)
8 external
9 view
10 returns (uint256);
11 function approve(address spender, uint256 amount) external returns (bool);
12 function transferFrom(address sender, address recipient, uint256 amount)
13 external
14 returns (bool);
15}
Example implementation of an ERC-20 token contract written in Rust.
1//! Implementation of the ERC-20 standard
2//!
3//! The eponymous [`Erc20`] type provides all the standard methods,
4//! and is intended to be inherited by other contract types.
5//!
6//! You can configure the behavior of [`Erc20`] via the [`Erc20Params`] trait,
7//! which allows specifying the name, symbol, and decimals of the token.
8//!
9//! Note that this code is unaudited and not fit for production use.
10
11// Imported packages
12use alloc::string::String;
13use alloy_primitives::{Address, U256};
14use alloy_sol_types::sol;
15use core::marker::PhantomData;
16use stylus_sdk::{
17 evm,
18 msg,
19 prelude::*,
20};
21
22pub trait Erc20Params {
23 /// Immutable token name
24 const NAME: &'static str;
25
26 /// Immutable token symbol
27 const SYMBOL: &'static str;
28
29 /// Immutable token decimals
30 const DECIMALS: u8;
31}
32
33sol_storage! {
34 /// Erc20 implements all ERC-20 methods.
35 pub struct Erc20<T> {
36 /// Maps users to balances
37 mapping(address => uint256) balances;
38 /// Maps users to a mapping of each spender's allowance
39 mapping(address => mapping(address => uint256)) allowances;
40 /// The total supply of the token
41 uint256 total_supply;
42 /// Used to allow [`Erc20Params`]
43 PhantomData<T> phantom;
44 }
45}
46
47// Declare events and Solidity error types
48sol! {
49 event Transfer(address indexed from, address indexed to, uint256 value);
50 event Approval(address indexed owner, address indexed spender, uint256 value);
51
52 error InsufficientBalance(address from, uint256 have, uint256 want);
53 error InsufficientAllowance(address owner, address spender, uint256 have, uint256 want);
54}
55
56/// Represents the ways methods may fail.
57#[derive(SolidityError)]
58pub enum Erc20Error {
59 InsufficientBalance(InsufficientBalance),
60 InsufficientAllowance(InsufficientAllowance),
61}
62
63// These methods aren't exposed to other contracts
64// Methods marked as "pub" here are usable outside of the erc20 module (i.e. they're callable from lib.rs)
65// Note: modifying storage will become much prettier soon
66impl<T: Erc20Params> Erc20<T> {
67 /// Movement of funds between 2 accounts
68 /// (invoked by the external transfer() and transfer_from() functions )
69 pub fn _transfer(
70 &mut self,
71 from: Address,
72 to: Address,
73 value: U256,
74 ) -> Result<(), Erc20Error> {
75 // Decreasing sender balance
76 let mut sender_balance = self.balances.setter(from);
77 let old_sender_balance = sender_balance.get();
78 if old_sender_balance < value {
79 return Err(Erc20Error::InsufficientBalance(InsufficientBalance {
80 from,
81 have: old_sender_balance,
82 want: value,
83 }));
84 }
85 sender_balance.set(old_sender_balance - value);
86
87 // Increasing receiver balance
88 let mut to_balance = self.balances.setter(to);
89 let new_to_balance = to_balance.get() + value;
90 to_balance.set(new_to_balance);
91
92 // Emitting the transfer event
93 evm::log(Transfer { from, to, value });
94 Ok(())
95 }
96
97 /// Mints `value` tokens to `address`
98 pub fn mint(&mut self, address: Address, value: U256) -> Result<(), Erc20Error> {
99 // Increasing balance
100 let mut balance = self.balances.setter(address);
101 let new_balance = balance.get() + value;
102 balance.set(new_balance);
103
104 // Increasing total supply
105 self.total_supply.set(self.total_supply.get() + value);
106
107 // Emitting the transfer event
108 evm::log(Transfer {
109 from: Address::ZERO,
110 to: address,
111 value,
112 });
113
114 Ok(())
115 }
116
117 /// Burns `value` tokens from `address`
118 pub fn burn(&mut self, address: Address, value: U256) -> Result<(), Erc20Error> {
119 // Decreasing balance
120 let mut balance = self.balances.setter(address);
121 let old_balance = balance.get();
122 if old_balance < value {
123 return Err(Erc20Error::InsufficientBalance(InsufficientBalance {
124 from: address,
125 have: old_balance,
126 want: value,
127 }));
128 }
129 balance.set(old_balance - value);
130
131 // Decreasing the total supply
132 self.total_supply.set(self.total_supply.get() - value);
133
134 // Emitting the transfer event
135 evm::log(Transfer {
136 from: address,
137 to: Address::ZERO,
138 value,
139 });
140
141 Ok(())
142 }
143}
144
145// These methods are external to other contracts
146// Note: modifying storage will become much prettier soon
147#[public]
148impl<T: Erc20Params> Erc20<T> {
149 /// Immutable token name
150 pub fn name() -> String {
151 T::NAME.into()
152 }
153
154 /// Immutable token symbol
155 pub fn symbol() -> String {
156 T::SYMBOL.into()
157 }
158
159 /// Immutable token decimals
160 pub fn decimals() -> u8 {
161 T::DECIMALS
162 }
163
164 /// Total supply of tokens
165 pub fn total_supply(&self) -> U256 {
166 self.total_supply.get()
167 }
168
169 /// Balance of `address`
170 pub fn balance_of(&self, owner: Address) -> U256 {
171 self.balances.get(owner)
172 }
173
174 /// Transfers `value` tokens from msg::sender() to `to`
175 pub fn transfer(&mut self, to: Address, value: U256) -> Result<bool, Erc20Error> {
176 self._transfer(msg::sender(), to, value)?;
177 Ok(true)
178 }
179
180 /// Transfers `value` tokens from `from` to `to`
181 /// (msg::sender() must be able to spend at least `value` tokens from `from`)
182 pub fn transfer_from(
183 &mut self,
184 from: Address,
185 to: Address,
186 value: U256,
187 ) -> Result<bool, Erc20Error> {
188 // Check msg::sender() allowance
189 let mut sender_allowances = self.allowances.setter(from);
190 let mut allowance = sender_allowances.setter(msg::sender());
191 let old_allowance = allowance.get();
192 if old_allowance < value {
193 return Err(Erc20Error::InsufficientAllowance(InsufficientAllowance {
194 owner: from,
195 spender: msg::sender(),
196 have: old_allowance,
197 want: value,
198 }));
199 }
200
201 // Decreases allowance
202 allowance.set(old_allowance - value);
203
204 // Calls the internal transfer function
205 self._transfer(from, to, value)?;
206
207 Ok(true)
208 }
209
210 /// Approves the spenditure of `value` tokens of msg::sender() to `spender`
211 pub fn approve(&mut self, spender: Address, value: U256) -> bool {
212 self.allowances.setter(msg::sender()).insert(spender, value);
213 evm::log(Approval {
214 owner: msg::sender(),
215 spender,
216 value,
217 });
218 true
219 }
220
221 /// Returns the allowance of `spender` on `owner`'s tokens
222 pub fn allowance(&self, owner: Address, spender: Address) -> U256 {
223 self.allowances.getter(owner).get(spender)
224 }
225}
1//! Implementation of the ERC-20 standard
2//!
3//! The eponymous [`Erc20`] type provides all the standard methods,
4//! and is intended to be inherited by other contract types.
5//!
6//! You can configure the behavior of [`Erc20`] via the [`Erc20Params`] trait,
7//! which allows specifying the name, symbol, and decimals of the token.
8//!
9//! Note that this code is unaudited and not fit for production use.
10
11// Imported packages
12use alloc::string::String;
13use alloy_primitives::{Address, U256};
14use alloy_sol_types::sol;
15use core::marker::PhantomData;
16use stylus_sdk::{
17 evm,
18 msg,
19 prelude::*,
20};
21
22pub trait Erc20Params {
23 /// Immutable token name
24 const NAME: &'static str;
25
26 /// Immutable token symbol
27 const SYMBOL: &'static str;
28
29 /// Immutable token decimals
30 const DECIMALS: u8;
31}
32
33sol_storage! {
34 /// Erc20 implements all ERC-20 methods.
35 pub struct Erc20<T> {
36 /// Maps users to balances
37 mapping(address => uint256) balances;
38 /// Maps users to a mapping of each spender's allowance
39 mapping(address => mapping(address => uint256)) allowances;
40 /// The total supply of the token
41 uint256 total_supply;
42 /// Used to allow [`Erc20Params`]
43 PhantomData<T> phantom;
44 }
45}
46
47// Declare events and Solidity error types
48sol! {
49 event Transfer(address indexed from, address indexed to, uint256 value);
50 event Approval(address indexed owner, address indexed spender, uint256 value);
51
52 error InsufficientBalance(address from, uint256 have, uint256 want);
53 error InsufficientAllowance(address owner, address spender, uint256 have, uint256 want);
54}
55
56/// Represents the ways methods may fail.
57#[derive(SolidityError)]
58pub enum Erc20Error {
59 InsufficientBalance(InsufficientBalance),
60 InsufficientAllowance(InsufficientAllowance),
61}
62
63// These methods aren't exposed to other contracts
64// Methods marked as "pub" here are usable outside of the erc20 module (i.e. they're callable from lib.rs)
65// Note: modifying storage will become much prettier soon
66impl<T: Erc20Params> Erc20<T> {
67 /// Movement of funds between 2 accounts
68 /// (invoked by the external transfer() and transfer_from() functions )
69 pub fn _transfer(
70 &mut self,
71 from: Address,
72 to: Address,
73 value: U256,
74 ) -> Result<(), Erc20Error> {
75 // Decreasing sender balance
76 let mut sender_balance = self.balances.setter(from);
77 let old_sender_balance = sender_balance.get();
78 if old_sender_balance < value {
79 return Err(Erc20Error::InsufficientBalance(InsufficientBalance {
80 from,
81 have: old_sender_balance,
82 want: value,
83 }));
84 }
85 sender_balance.set(old_sender_balance - value);
86
87 // Increasing receiver balance
88 let mut to_balance = self.balances.setter(to);
89 let new_to_balance = to_balance.get() + value;
90 to_balance.set(new_to_balance);
91
92 // Emitting the transfer event
93 evm::log(Transfer { from, to, value });
94 Ok(())
95 }
96
97 /// Mints `value` tokens to `address`
98 pub fn mint(&mut self, address: Address, value: U256) -> Result<(), Erc20Error> {
99 // Increasing balance
100 let mut balance = self.balances.setter(address);
101 let new_balance = balance.get() + value;
102 balance.set(new_balance);
103
104 // Increasing total supply
105 self.total_supply.set(self.total_supply.get() + value);
106
107 // Emitting the transfer event
108 evm::log(Transfer {
109 from: Address::ZERO,
110 to: address,
111 value,
112 });
113
114 Ok(())
115 }
116
117 /// Burns `value` tokens from `address`
118 pub fn burn(&mut self, address: Address, value: U256) -> Result<(), Erc20Error> {
119 // Decreasing balance
120 let mut balance = self.balances.setter(address);
121 let old_balance = balance.get();
122 if old_balance < value {
123 return Err(Erc20Error::InsufficientBalance(InsufficientBalance {
124 from: address,
125 have: old_balance,
126 want: value,
127 }));
128 }
129 balance.set(old_balance - value);
130
131 // Decreasing the total supply
132 self.total_supply.set(self.total_supply.get() - value);
133
134 // Emitting the transfer event
135 evm::log(Transfer {
136 from: address,
137 to: Address::ZERO,
138 value,
139 });
140
141 Ok(())
142 }
143}
144
145// These methods are external to other contracts
146// Note: modifying storage will become much prettier soon
147#[public]
148impl<T: Erc20Params> Erc20<T> {
149 /// Immutable token name
150 pub fn name() -> String {
151 T::NAME.into()
152 }
153
154 /// Immutable token symbol
155 pub fn symbol() -> String {
156 T::SYMBOL.into()
157 }
158
159 /// Immutable token decimals
160 pub fn decimals() -> u8 {
161 T::DECIMALS
162 }
163
164 /// Total supply of tokens
165 pub fn total_supply(&self) -> U256 {
166 self.total_supply.get()
167 }
168
169 /// Balance of `address`
170 pub fn balance_of(&self, owner: Address) -> U256 {
171 self.balances.get(owner)
172 }
173
174 /// Transfers `value` tokens from msg::sender() to `to`
175 pub fn transfer(&mut self, to: Address, value: U256) -> Result<bool, Erc20Error> {
176 self._transfer(msg::sender(), to, value)?;
177 Ok(true)
178 }
179
180 /// Transfers `value` tokens from `from` to `to`
181 /// (msg::sender() must be able to spend at least `value` tokens from `from`)
182 pub fn transfer_from(
183 &mut self,
184 from: Address,
185 to: Address,
186 value: U256,
187 ) -> Result<bool, Erc20Error> {
188 // Check msg::sender() allowance
189 let mut sender_allowances = self.allowances.setter(from);
190 let mut allowance = sender_allowances.setter(msg::sender());
191 let old_allowance = allowance.get();
192 if old_allowance < value {
193 return Err(Erc20Error::InsufficientAllowance(InsufficientAllowance {
194 owner: from,
195 spender: msg::sender(),
196 have: old_allowance,
197 want: value,
198 }));
199 }
200
201 // Decreases allowance
202 allowance.set(old_allowance - value);
203
204 // Calls the internal transfer function
205 self._transfer(from, to, value)?;
206
207 Ok(true)
208 }
209
210 /// Approves the spenditure of `value` tokens of msg::sender() to `spender`
211 pub fn approve(&mut self, spender: Address, value: U256) -> bool {
212 self.allowances.setter(msg::sender()).insert(spender, value);
213 evm::log(Approval {
214 owner: msg::sender(),
215 spender,
216 value,
217 });
218 true
219 }
220
221 /// Returns the allowance of `spender` on `owner`'s tokens
222 pub fn allowance(&self, owner: Address, spender: Address) -> U256 {
223 self.allowances.getter(owner).get(spender)
224 }
225}
1// Only run this as a WASM if the export-abi feature is not set.
2#![cfg_attr(not(any(feature = "export-abi", test)), no_main)]
3extern crate alloc;
4
5// Modules and imports
6mod erc20;
7
8use alloy_primitives::{Address, U256};
9use stylus_sdk::{
10 msg,
11 prelude::*
12};
13use crate::erc20::{Erc20, Erc20Params, Erc20Error};
14
15/// Immutable definitions
16struct StylusTokenParams;
17impl Erc20Params for StylusTokenParams {
18 const NAME: &'static str = "StylusToken";
19 const SYMBOL: &'static str = "STK";
20 const DECIMALS: u8 = 18;
21}
22
23// Define the entrypoint as a Solidity storage object. The sol_storage! macro
24// will generate Rust-equivalent structs with all fields mapped to Solidity-equivalent
25// storage slots and types.
26sol_storage! {
27 #[entrypoint]
28 struct StylusToken {
29 // Allows erc20 to access StylusToken's storage and make calls
30 #[borrow]
31 Erc20<StylusTokenParams> erc20;
32 }
33}
34
35#[public]
36#[inherit(Erc20<StylusTokenParams>)]
37impl StylusToken {
38 /// Mints tokens
39 pub fn mint(&mut self, value: U256) -> Result<(), Erc20Error> {
40 self.erc20.mint(msg::sender(), value)?;
41 Ok(())
42 }
43
44 /// Mints tokens to another address
45 pub fn mint_to(&mut self, to: Address, value: U256) -> Result<(), Erc20Error> {
46 self.erc20.mint(to, value)?;
47 Ok(())
48 }
49
50 /// Burns tokens
51 pub fn burn(&mut self, value: U256) -> Result<(), Erc20Error> {
52 self.erc20.burn(msg::sender(), value)?;
53 Ok(())
54 }
55}
1// Only run this as a WASM if the export-abi feature is not set.
2#![cfg_attr(not(any(feature = "export-abi", test)), no_main)]
3extern crate alloc;
4
5// Modules and imports
6mod erc20;
7
8use alloy_primitives::{Address, U256};
9use stylus_sdk::{
10 msg,
11 prelude::*
12};
13use crate::erc20::{Erc20, Erc20Params, Erc20Error};
14
15/// Immutable definitions
16struct StylusTokenParams;
17impl Erc20Params for StylusTokenParams {
18 const NAME: &'static str = "StylusToken";
19 const SYMBOL: &'static str = "STK";
20 const DECIMALS: u8 = 18;
21}
22
23// Define the entrypoint as a Solidity storage object. The sol_storage! macro
24// will generate Rust-equivalent structs with all fields mapped to Solidity-equivalent
25// storage slots and types.
26sol_storage! {
27 #[entrypoint]
28 struct StylusToken {
29 // Allows erc20 to access StylusToken's storage and make calls
30 #[borrow]
31 Erc20<StylusTokenParams> erc20;
32 }
33}
34
35#[public]
36#[inherit(Erc20<StylusTokenParams>)]
37impl StylusToken {
38 /// Mints tokens
39 pub fn mint(&mut self, value: U256) -> Result<(), Erc20Error> {
40 self.erc20.mint(msg::sender(), value)?;
41 Ok(())
42 }
43
44 /// Mints tokens to another address
45 pub fn mint_to(&mut self, to: Address, value: U256) -> Result<(), Erc20Error> {
46 self.erc20.mint(to, value)?;
47 Ok(())
48 }
49
50 /// Burns tokens
51 pub fn burn(&mut self, value: U256) -> Result<(), Erc20Error> {
52 self.erc20.burn(msg::sender(), value)?;
53 Ok(())
54 }
55}
1[package]
2name = "stylus_erc20_example"
3version = "0.1.7"
4edition = "2021"
5license = "MIT OR Apache-2.0"
6keywords = ["arbitrum", "ethereum", "stylus", "alloy"]
7
8[dependencies]
9alloy-primitives = "=0.7.6"
10alloy-sol-types = "=0.7.6"
11mini-alloc = "0.4.2"
12stylus-sdk = "0.6.0"
13hex = "0.4.3"
14
15[dev-dependencies]
16tokio = { version = "1.12.0", features = ["full"] }
17ethers = "2.0"
18eyre = "0.6.8"
19
20[features]
21export-abi = ["stylus-sdk/export-abi"]
22
23[lib]
24crate-type = ["lib", "cdylib"]
25
26[profile.release]
27codegen-units = 1
28strip = true
29lto = true
30panic = "abort"
31opt-level = "s"
1[package]
2name = "stylus_erc20_example"
3version = "0.1.7"
4edition = "2021"
5license = "MIT OR Apache-2.0"
6keywords = ["arbitrum", "ethereum", "stylus", "alloy"]
7
8[dependencies]
9alloy-primitives = "=0.7.6"
10alloy-sol-types = "=0.7.6"
11mini-alloc = "0.4.2"
12stylus-sdk = "0.6.0"
13hex = "0.4.3"
14
15[dev-dependencies]
16tokio = { version = "1.12.0", features = ["full"] }
17ethers = "2.0"
18eyre = "0.6.8"
19
20[features]
21export-abi = ["stylus-sdk/export-abi"]
22
23[lib]
24crate-type = ["lib", "cdylib"]
25
26[profile.release]
27codegen-units = 1
28strip = true
29lto = true
30panic = "abort"
31opt-level = "s"