When an ERC20 token holder interacts with another contract using the token, two transactions are required:
- The token holder calls
approve
to set an allowance of tokens that the contract can use. (assuming an OpenZeppelin ERC20 implementation can use increaseAllowance) - The token holder calls the contract to perform an action and the contract can
transferFrom
an amount of tokens within the set allowance.
The receiving contract can use IERC20 to interact with the tokens ERC20 functions.
The example below includes a SimpleToken ERC20 contract and a TokenReceiver contract (that uses the token), along with the migrations script for truffle to deploy the contracts and some sample interactions using truffle console
.
SimpleToken.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.3;
import "@openzeppelin/contracts/utils/Context.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
/**
* @title SimpleToken
* @dev Very simple ERC20 Token example, where all tokens are pre-assigned to the creator.
* Note they can later distribute these tokens as they wish using `transfer` and other
* `ERC20` functions.
*/
contract SimpleToken is Context, ERC20 {
/**
* @dev Constructor that gives _msgSender() all of existing tokens.
*/
constructor () public ERC20("SimpleToken", "SIM") {
_mint(_msgSender(), 10000 * (10 ** uint256(decimals())));
}
}
TokenReceiver.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.3;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
/**
* @title TokenReceiver
* @dev Very simple example of a contract receiving ERC20 tokens.
*/
contract TokenReceiver {
IERC20 private _token;
event DoneStuff(address from);
/**
* @dev Constructor sets token that can be received
*/
constructor (IERC20 token) public {
_token = token;
}
/**
* @dev Do stuff, requires tokens
*/
function doStuff() external {
address from = msg.sender;
_token.transferFrom(from, address(this), 1000);
emit DoneStuff(from);
}
}
2_deploy.js
// migrations/2_deploy.js
const SimpleToken = artifacts.require('SimpleToken');
const TokenReceiver = artifacts.require('TokenReceiver');
module.exports = async function (deployer) {
await deployer.deploy(SimpleToken);
const token = await SimpleToken.deployed();
await deployer.deploy(TokenReceiver, token.address);
};
Deploy
npx truffle develop
...
truffle(develop)> migrate
Compiling your contracts...
===========================
...
Starting migrations...
======================
> Network name: 'develop'
...
Interact
Setup
truffle(develop)> token = await SimpleToken.deployed()
undefined
truffle(develop)> receiver = await TokenReceiver.deployed()
undefined
Attempt to do stuff (requires 1000 tokens but no allowance approved)
truffle(develop)> receiver.doStuff()
Thrown:
{ Error: Returned error: VM Exception while processing transaction: revert ERC20: transfer amount exceeds allowance -- Reason given: ERC20: transfer amount exceeds allowance.
...
Approve allowance for receiver contract (for non-OpenZeppelin ERC20 implementations you could call token.approve(receiver.address, 1000)
)
truffle(develop)> token.increaseAllowance(receiver.address, 1000)
Do stuff (requires 1000 tokens)
truffle(develop)> receiver.doStuff()
Check balance
truffle(develop)> (await token.balanceOf(receiver.address)).toString()
'1000'