Skip to content

Commit 2de85a6

Browse files
authoredApr 1, 2023
docs: add erc20 token transfer example (gakonst#2295)
1 parent 6e648d5 commit 2de85a6

File tree

8 files changed

+628
-0
lines changed

8 files changed

+628
-0
lines changed
 

‎examples/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
- [ ] Transaction receipt
7272
- [ ] Transaction status
7373
- [x] Transfer ETH
74+
- [X] Transfer ERC20 token
7475
- [x] Wallets
7576
- [x] Mnemonic
7677
- [x] Ledger
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# OpenZeppelin Contracts
2+
3+
The files in this directory were sourced unmodified from OpenZeppelin Contracts v4.7.3.
4+
5+
They are not meant to be edited.
6+
7+
The originals can be found on [GitHub] and [npm].
8+
9+
[GitHub]: https://github.com/OpenZeppelin/openzeppelin-contracts/tree/v4.7.3
10+
[npm]: https://www.npmjs.com/package/@openzeppelin/contracts/v/4.7.3
11+
12+
Generated with OpenZeppelin Contracts Wizard (https://zpl.in/wizard).
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,383 @@
1+
// SPDX-License-Identifier: MIT
2+
// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC20/ERC20.sol)
3+
4+
pragma solidity ^0.8.0;
5+
6+
import "./IERC20.sol";
7+
import "./extensions/IERC20Metadata.sol";
8+
import "../../utils/Context.sol";
9+
10+
/**
11+
* @dev Implementation of the {IERC20} interface.
12+
*
13+
* This implementation is agnostic to the way tokens are created. This means
14+
* that a supply mechanism has to be added in a derived contract using {_mint}.
15+
* For a generic mechanism see {ERC20PresetMinterPauser}.
16+
*
17+
* TIP: For a detailed writeup see our guide
18+
* https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How
19+
* to implement supply mechanisms].
20+
*
21+
* We have followed general OpenZeppelin Contracts guidelines: functions revert
22+
* instead returning `false` on failure. This behavior is nonetheless
23+
* conventional and does not conflict with the expectations of ERC20
24+
* applications.
25+
*
26+
* Additionally, an {Approval} event is emitted on calls to {transferFrom}.
27+
* This allows applications to reconstruct the allowance for all accounts just
28+
* by listening to said events. Other implementations of the EIP may not emit
29+
* these events, as it isn't required by the specification.
30+
*
31+
* Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
32+
* functions have been added to mitigate the well-known issues around setting
33+
* allowances. See {IERC20-approve}.
34+
*/
35+
contract ERC20 is Context, IERC20, IERC20Metadata {
36+
mapping(address => uint256) private _balances;
37+
38+
mapping(address => mapping(address => uint256)) private _allowances;
39+
40+
uint256 private _totalSupply;
41+
42+
string private _name;
43+
string private _symbol;
44+
45+
/**
46+
* @dev Sets the values for {name} and {symbol}.
47+
*
48+
* The default value of {decimals} is 18. To select a different value for
49+
* {decimals} you should overload it.
50+
*
51+
* All two of these values are immutable: they can only be set once during
52+
* construction.
53+
*/
54+
constructor(string memory name_, string memory symbol_) {
55+
_name = name_;
56+
_symbol = symbol_;
57+
}
58+
59+
/**
60+
* @dev Returns the name of the token.
61+
*/
62+
function name() public view virtual override returns (string memory) {
63+
return _name;
64+
}
65+
66+
/**
67+
* @dev Returns the symbol of the token, usually a shorter version of the
68+
* name.
69+
*/
70+
function symbol() public view virtual override returns (string memory) {
71+
return _symbol;
72+
}
73+
74+
/**
75+
* @dev Returns the number of decimals used to get its user representation.
76+
* For example, if `decimals` equals `2`, a balance of `505` tokens should
77+
* be displayed to a user as `5.05` (`505 / 10 ** 2`).
78+
*
79+
* Tokens usually opt for a value of 18, imitating the relationship between
80+
* Ether and Wei. This is the value {ERC20} uses, unless this function is
81+
* overridden;
82+
*
83+
* NOTE: This information is only used for _display_ purposes: it in
84+
* no way affects any of the arithmetic of the contract, including
85+
* {IERC20-balanceOf} and {IERC20-transfer}.
86+
*/
87+
function decimals() public view virtual override returns (uint8) {
88+
return 18;
89+
}
90+
91+
/**
92+
* @dev See {IERC20-totalSupply}.
93+
*/
94+
function totalSupply() public view virtual override returns (uint256) {
95+
return _totalSupply;
96+
}
97+
98+
/**
99+
* @dev See {IERC20-balanceOf}.
100+
*/
101+
function balanceOf(address account) public view virtual override returns (uint256) {
102+
return _balances[account];
103+
}
104+
105+
/**
106+
* @dev See {IERC20-transfer}.
107+
*
108+
* Requirements:
109+
*
110+
* - `to` cannot be the zero address.
111+
* - the caller must have a balance of at least `amount`.
112+
*/
113+
function transfer(address to, uint256 amount) public virtual override returns (bool) {
114+
address owner = _msgSender();
115+
_transfer(owner, to, amount);
116+
return true;
117+
}
118+
119+
/**
120+
* @dev See {IERC20-allowance}.
121+
*/
122+
function allowance(address owner, address spender) public view virtual override returns (uint256) {
123+
return _allowances[owner][spender];
124+
}
125+
126+
/**
127+
* @dev See {IERC20-approve}.
128+
*
129+
* NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on
130+
* `transferFrom`. This is semantically equivalent to an infinite approval.
131+
*
132+
* Requirements:
133+
*
134+
* - `spender` cannot be the zero address.
135+
*/
136+
function approve(address spender, uint256 amount) public virtual override returns (bool) {
137+
address owner = _msgSender();
138+
_approve(owner, spender, amount);
139+
return true;
140+
}
141+
142+
/**
143+
* @dev See {IERC20-transferFrom}.
144+
*
145+
* Emits an {Approval} event indicating the updated allowance. This is not
146+
* required by the EIP. See the note at the beginning of {ERC20}.
147+
*
148+
* NOTE: Does not update the allowance if the current allowance
149+
* is the maximum `uint256`.
150+
*
151+
* Requirements:
152+
*
153+
* - `from` and `to` cannot be the zero address.
154+
* - `from` must have a balance of at least `amount`.
155+
* - the caller must have allowance for ``from``'s tokens of at least
156+
* `amount`.
157+
*/
158+
function transferFrom(
159+
address from,
160+
address to,
161+
uint256 amount
162+
) public virtual override returns (bool) {
163+
address spender = _msgSender();
164+
_spendAllowance(from, spender, amount);
165+
_transfer(from, to, amount);
166+
return true;
167+
}
168+
169+
/**
170+
* @dev Atomically increases the allowance granted to `spender` by the caller.
171+
*
172+
* This is an alternative to {approve} that can be used as a mitigation for
173+
* problems described in {IERC20-approve}.
174+
*
175+
* Emits an {Approval} event indicating the updated allowance.
176+
*
177+
* Requirements:
178+
*
179+
* - `spender` cannot be the zero address.
180+
*/
181+
function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
182+
address owner = _msgSender();
183+
_approve(owner, spender, allowance(owner, spender) + addedValue);
184+
return true;
185+
}
186+
187+
/**
188+
* @dev Atomically decreases the allowance granted to `spender` by the caller.
189+
*
190+
* This is an alternative to {approve} that can be used as a mitigation for
191+
* problems described in {IERC20-approve}.
192+
*
193+
* Emits an {Approval} event indicating the updated allowance.
194+
*
195+
* Requirements:
196+
*
197+
* - `spender` cannot be the zero address.
198+
* - `spender` must have allowance for the caller of at least
199+
* `subtractedValue`.
200+
*/
201+
function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
202+
address owner = _msgSender();
203+
uint256 currentAllowance = allowance(owner, spender);
204+
require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
205+
unchecked {
206+
_approve(owner, spender, currentAllowance - subtractedValue);
207+
}
208+
209+
return true;
210+
}
211+
212+
/**
213+
* @dev Moves `amount` of tokens from `from` to `to`.
214+
*
215+
* This internal function is equivalent to {transfer}, and can be used to
216+
* e.g. implement automatic token fees, slashing mechanisms, etc.
217+
*
218+
* Emits a {Transfer} event.
219+
*
220+
* Requirements:
221+
*
222+
* - `from` cannot be the zero address.
223+
* - `to` cannot be the zero address.
224+
* - `from` must have a balance of at least `amount`.
225+
*/
226+
function _transfer(
227+
address from,
228+
address to,
229+
uint256 amount
230+
) internal virtual {
231+
require(from != address(0), "ERC20: transfer from the zero address");
232+
require(to != address(0), "ERC20: transfer to the zero address");
233+
234+
_beforeTokenTransfer(from, to, amount);
235+
236+
uint256 fromBalance = _balances[from];
237+
require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
238+
unchecked {
239+
_balances[from] = fromBalance - amount;
240+
}
241+
_balances[to] += amount;
242+
243+
emit Transfer(from, to, amount);
244+
245+
_afterTokenTransfer(from, to, amount);
246+
}
247+
248+
/** @dev Creates `amount` tokens and assigns them to `account`, increasing
249+
* the total supply.
250+
*
251+
* Emits a {Transfer} event with `from` set to the zero address.
252+
*
253+
* Requirements:
254+
*
255+
* - `account` cannot be the zero address.
256+
*/
257+
function _mint(address account, uint256 amount) internal virtual {
258+
require(account != address(0), "ERC20: mint to the zero address");
259+
260+
_beforeTokenTransfer(address(0), account, amount);
261+
262+
_totalSupply += amount;
263+
_balances[account] += amount;
264+
emit Transfer(address(0), account, amount);
265+
266+
_afterTokenTransfer(address(0), account, amount);
267+
}
268+
269+
/**
270+
* @dev Destroys `amount` tokens from `account`, reducing the
271+
* total supply.
272+
*
273+
* Emits a {Transfer} event with `to` set to the zero address.
274+
*
275+
* Requirements:
276+
*
277+
* - `account` cannot be the zero address.
278+
* - `account` must have at least `amount` tokens.
279+
*/
280+
function _burn(address account, uint256 amount) internal virtual {
281+
require(account != address(0), "ERC20: burn from the zero address");
282+
283+
_beforeTokenTransfer(account, address(0), amount);
284+
285+
uint256 accountBalance = _balances[account];
286+
require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
287+
unchecked {
288+
_balances[account] = accountBalance - amount;
289+
}
290+
_totalSupply -= amount;
291+
292+
emit Transfer(account, address(0), amount);
293+
294+
_afterTokenTransfer(account, address(0), amount);
295+
}
296+
297+
/**
298+
* @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
299+
*
300+
* This internal function is equivalent to `approve`, and can be used to
301+
* e.g. set automatic allowances for certain subsystems, etc.
302+
*
303+
* Emits an {Approval} event.
304+
*
305+
* Requirements:
306+
*
307+
* - `owner` cannot be the zero address.
308+
* - `spender` cannot be the zero address.
309+
*/
310+
function _approve(
311+
address owner,
312+
address spender,
313+
uint256 amount
314+
) internal virtual {
315+
require(owner != address(0), "ERC20: approve from the zero address");
316+
require(spender != address(0), "ERC20: approve to the zero address");
317+
318+
_allowances[owner][spender] = amount;
319+
emit Approval(owner, spender, amount);
320+
}
321+
322+
/**
323+
* @dev Updates `owner` s allowance for `spender` based on spent `amount`.
324+
*
325+
* Does not update the allowance amount in case of infinite allowance.
326+
* Revert if not enough allowance is available.
327+
*
328+
* Might emit an {Approval} event.
329+
*/
330+
function _spendAllowance(
331+
address owner,
332+
address spender,
333+
uint256 amount
334+
) internal virtual {
335+
uint256 currentAllowance = allowance(owner, spender);
336+
if (currentAllowance != type(uint256).max) {
337+
require(currentAllowance >= amount, "ERC20: insufficient allowance");
338+
unchecked {
339+
_approve(owner, spender, currentAllowance - amount);
340+
}
341+
}
342+
}
343+
344+
/**
345+
* @dev Hook that is called before any transfer of tokens. This includes
346+
* minting and burning.
347+
*
348+
* Calling conditions:
349+
*
350+
* - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
351+
* will be transferred to `to`.
352+
* - when `from` is zero, `amount` tokens will be minted for `to`.
353+
* - when `to` is zero, `amount` of ``from``'s tokens will be burned.
354+
* - `from` and `to` are never both zero.
355+
*
356+
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
357+
*/
358+
function _beforeTokenTransfer(
359+
address from,
360+
address to,
361+
uint256 amount
362+
) internal virtual {}
363+
364+
/**
365+
* @dev Hook that is called after any transfer of tokens. This includes
366+
* minting and burning.
367+
*
368+
* Calling conditions:
369+
*
370+
* - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
371+
* has been transferred to `to`.
372+
* - when `from` is zero, `amount` tokens have been minted for `to`.
373+
* - when `to` is zero, `amount` of ``from``'s tokens have been burned.
374+
* - `from` and `to` are never both zero.
375+
*
376+
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
377+
*/
378+
function _afterTokenTransfer(
379+
address from,
380+
address to,
381+
uint256 amount
382+
) internal virtual {}
383+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// SPDX-License-Identifier: MIT
2+
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)
3+
4+
pragma solidity ^0.8.0;
5+
6+
/**
7+
* @dev Interface of the ERC20 standard as defined in the EIP.
8+
*/
9+
interface IERC20 {
10+
/**
11+
* @dev Emitted when `value` tokens are moved from one account (`from`) to
12+
* another (`to`).
13+
*
14+
* Note that `value` may be zero.
15+
*/
16+
event Transfer(address indexed from, address indexed to, uint256 value);
17+
18+
/**
19+
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
20+
* a call to {approve}. `value` is the new allowance.
21+
*/
22+
event Approval(address indexed owner, address indexed spender, uint256 value);
23+
24+
/**
25+
* @dev Returns the amount of tokens in existence.
26+
*/
27+
function totalSupply() external view returns (uint256);
28+
29+
/**
30+
* @dev Returns the amount of tokens owned by `account`.
31+
*/
32+
function balanceOf(address account) external view returns (uint256);
33+
34+
/**
35+
* @dev Moves `amount` tokens from the caller's account to `to`.
36+
*
37+
* Returns a boolean value indicating whether the operation succeeded.
38+
*
39+
* Emits a {Transfer} event.
40+
*/
41+
function transfer(address to, uint256 amount) external returns (bool);
42+
43+
/**
44+
* @dev Returns the remaining number of tokens that `spender` will be
45+
* allowed to spend on behalf of `owner` through {transferFrom}. This is
46+
* zero by default.
47+
*
48+
* This value changes when {approve} or {transferFrom} are called.
49+
*/
50+
function allowance(address owner, address spender) external view returns (uint256);
51+
52+
/**
53+
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
54+
*
55+
* Returns a boolean value indicating whether the operation succeeded.
56+
*
57+
* IMPORTANT: Beware that changing an allowance with this method brings the risk
58+
* that someone may use both the old and the new allowance by unfortunate
59+
* transaction ordering. One possible solution to mitigate this race
60+
* condition is to first reduce the spender's allowance to 0 and set the
61+
* desired value afterwards:
62+
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
63+
*
64+
* Emits an {Approval} event.
65+
*/
66+
function approve(address spender, uint256 amount) external returns (bool);
67+
68+
/**
69+
* @dev Moves `amount` tokens from `from` to `to` using the
70+
* allowance mechanism. `amount` is then deducted from the caller's
71+
* allowance.
72+
*
73+
* Returns a boolean value indicating whether the operation succeeded.
74+
*
75+
* Emits a {Transfer} event.
76+
*/
77+
function transferFrom(
78+
address from,
79+
address to,
80+
uint256 amount
81+
) external returns (bool);
82+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// SPDX-License-Identifier: MIT
2+
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)
3+
4+
pragma solidity ^0.8.0;
5+
6+
import "../IERC20.sol";
7+
8+
/**
9+
* @dev Interface for the optional metadata functions from the ERC20 standard.
10+
*
11+
* _Available since v4.1._
12+
*/
13+
interface IERC20Metadata is IERC20 {
14+
/**
15+
* @dev Returns the name of the token.
16+
*/
17+
function name() external view returns (string memory);
18+
19+
/**
20+
* @dev Returns the symbol of the token.
21+
*/
22+
function symbol() external view returns (string memory);
23+
24+
/**
25+
* @dev Returns the decimals places of the token.
26+
*/
27+
function decimals() external view returns (uint8);
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// SPDX-License-Identifier: MIT
2+
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
3+
4+
pragma solidity ^0.8.0;
5+
6+
/**
7+
* @dev Provides information about the current execution context, including the
8+
* sender of the transaction and its data. While these are generally available
9+
* via msg.sender and msg.data, they should not be accessed in such a direct
10+
* manner, since when dealing with meta-transactions the account sending and
11+
* paying for execution may not be the actual sender (as far as an application
12+
* is concerned).
13+
*
14+
* This contract is only required for intermediate, library-like contracts.
15+
*/
16+
abstract contract Context {
17+
function _msgSender() internal view virtual returns (address) {
18+
return msg.sender;
19+
}
20+
21+
function _msgData() internal view virtual returns (bytes calldata) {
22+
return msg.data;
23+
}
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.4;
3+
4+
import "./@openzeppelin/contracts/token/ERC20/ERC20.sol";
5+
6+
contract ERC20Example is ERC20 {
7+
constructor() ERC20("ERC20Example", "XYZ") {
8+
_mint(msg.sender, 1000 * 10 ** decimals());
9+
}
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
use ethers::{
2+
contract::{abigen, ContractFactory},
3+
core::utils::Anvil,
4+
middleware::SignerMiddleware,
5+
providers::{Http, Provider},
6+
signers::{LocalWallet, Signer},
7+
solc::Solc,
8+
types::{Address, U256},
9+
utils::AnvilInstance,
10+
};
11+
use eyre::Result;
12+
use std::{convert::TryFrom, path::Path, sync::Arc, time::Duration};
13+
14+
#[tokio::main]
15+
async fn main() -> Result<()> {
16+
// Start a fresh local Anvil node to execute the transfer on.
17+
let anvil = Anvil::new().spawn();
18+
// Use the first wallet autogenerated by the Anvil node to send the transaction.
19+
let from_wallet: LocalWallet = anvil.keys()[0].clone().into();
20+
let to_address = anvil.addresses()[2].clone();
21+
22+
// Deploy an ERC20 token contract on the freshly started local Anvil node that doesn't have any
23+
// tokens deployed on it. This will also allocate tokens to the `from_wallet` address.
24+
// You don't need to do this to transfer an already deployed ERC20 token, in that case you only
25+
// need the token address.
26+
let token_address = deploy_token_contract(&anvil, from_wallet.clone()).await?;
27+
28+
// 1. Generate the ABI for the ERC20 contract. This is will define an `ERC20Contract` struct in
29+
// this scope that will let us call the methods of the contract.
30+
abigen!(
31+
ERC20Contract,
32+
r#"[
33+
function balanceOf(address account) external view returns (uint256)
34+
function decimals() external view returns (uint8)
35+
function symbol() external view returns (string memory)
36+
function transfer(address to, uint256 amount) external returns (bool)
37+
event Transfer(address indexed from, address indexed to, uint256 value)
38+
]"#,
39+
);
40+
41+
// 2. Create the contract instance to let us call methods of the contract and let it sign
42+
// transactions with the sender wallet.
43+
let provider =
44+
Provider::<Http>::try_from(anvil.endpoint())?.interval(Duration::from_millis(10u64));
45+
let signer =
46+
Arc::new(SignerMiddleware::new(provider, from_wallet.with_chain_id(anvil.chain_id())));
47+
let contract = ERC20Contract::new(token_address, signer);
48+
49+
// 3. Fetch the decimals used by the contract so we can compute the decimal amount to send.
50+
let whole_amount: u64 = 1000;
51+
let decimals = contract.decimals().call().await?;
52+
let decimal_amount = U256::from(whole_amount) * U256::exp10(decimals as usize);
53+
54+
// 4. Transfer the desired amount of tokens to the `to_address`
55+
let tx = contract.transfer(to_address, decimal_amount);
56+
let pending_tx = tx.send().await?;
57+
let _mined_tx = pending_tx.await?;
58+
59+
// 5. Fetch the balance of the recipient.
60+
let balance = contract.balance_of(to_address).call().await?;
61+
assert_eq!(balance, decimal_amount);
62+
63+
Ok(())
64+
}
65+
66+
/// Helper function to deploy a contract on a local anvil node. See
67+
/// `examples/contracts/examples/deploy_from_solidity.rs` for detailed explanation on how to deploy
68+
/// a contract.
69+
/// Allocates tokens to the deployer address and returns the ERC20 contract address.
70+
async fn deploy_token_contract(
71+
anvil: &AnvilInstance,
72+
deployer_wallet: LocalWallet,
73+
) -> Result<Address> {
74+
let source = Path::new(&env!("CARGO_MANIFEST_DIR"))
75+
.join("examples/contracts/erc20_example/ERC20Example.sol");
76+
let compiled = Solc::default().compile_source(source).expect("Could not compile contract");
77+
let (abi, bytecode, _runtime_bytecode) =
78+
compiled.find("ERC20Example").expect("could not find contract").into_parts_or_default();
79+
80+
let provider =
81+
Provider::<Http>::try_from(anvil.endpoint())?.interval(Duration::from_millis(10u64));
82+
let client = SignerMiddleware::new(provider, deployer_wallet.with_chain_id(anvil.chain_id()));
83+
let client = Arc::new(client);
84+
let factory = ContractFactory::new(abi, bytecode, client.clone());
85+
let contract = factory.deploy(())?.send().await?;
86+
87+
Ok(contract.address())
88+
}

0 commit comments

Comments
 (0)
Please sign in to comment.