🎓 A1Learner
🔍
0 of 48 lessons completed

Solidity ERC20 Allowances

An ERC20 allowance is permission for one address to move tokens that belong to another address. The owner grants the permission, and the spender uses it within the granted amount.

Approve records permission without moving tokens

Approving 50 units records an allowance of 50. Both balances stay exactly where they were.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

contract AllowanceCoin {
    uint256 public totalSupply;
    mapping(address => uint256) public balanceOf;
    mapping(address => mapping(address => uint256)) public allowance;
    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);

    constructor() {
        totalSupply = 1000;
        balanceOf[msg.sender] = 1000;
    }

    function approve(address spender, uint256 amount) public returns (bool) {
        allowance[msg.sender][spender] = amount;
        emit Approval(msg.sender, spender, amount);
        return true;
    }

    function transferFrom(address from, address to, uint256 amount) public returns (bool) {
        require(to != address(0), "zero receiver");
        require(balanceOf[from] >= amount, "not enough balance");
        require(allowance[from][msg.sender] >= amount, "allowance too small");
        allowance[from][msg.sender] -= amount;
        balanceOf[from] -= amount;
        balanceOf[to] += amount;
        emit Transfer(from, to, amount);
        return true;
    }
}

TransferFrom spends the allowance

After an approval of 50, the spender moves 30 to a receiver. The allowance drops to 20 and the balances move.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

contract AllowanceCoin {
    uint256 public totalSupply;
    mapping(address => uint256) public balanceOf;
    mapping(address => mapping(address => uint256)) public allowance;
    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);

    constructor() {
        totalSupply = 1000;
        balanceOf[msg.sender] = 1000;
    }

    function approve(address spender, uint256 amount) public returns (bool) {
        allowance[msg.sender][spender] = amount;
        emit Approval(msg.sender, spender, amount);
        return true;
    }

    function transferFrom(address from, address to, uint256 amount) public returns (bool) {
        require(to != address(0), "zero receiver");
        require(balanceOf[from] >= amount, "not enough balance");
        require(allowance[from][msg.sender] >= amount, "allowance too small");
        allowance[from][msg.sender] -= amount;
        balanceOf[from] -= amount;
        balanceOf[to] += amount;
        emit Transfer(from, to, amount);
        return true;
    }
}

TransferFrom above the allowance reverts

A spender may not move more than the granted amount. A spender with no approval may not move anything.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

contract AllowanceCoin {
    uint256 public totalSupply;
    mapping(address => uint256) public balanceOf;
    mapping(address => mapping(address => uint256)) public allowance;
    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);

    constructor() {
        totalSupply = 1000;
        balanceOf[msg.sender] = 1000;
    }

    function approve(address spender, uint256 amount) public returns (bool) {
        allowance[msg.sender][spender] = amount;
        emit Approval(msg.sender, spender, amount);
        return true;
    }

    function transferFrom(address from, address to, uint256 amount) public returns (bool) {
        require(to != address(0), "zero receiver");
        require(balanceOf[from] >= amount, "not enough balance");
        require(allowance[from][msg.sender] >= amount, "allowance too small");
        allowance[from][msg.sender] -= amount;
        balanceOf[from] -= amount;
        balanceOf[to] += amount;
        emit Transfer(from, to, amount);
        return true;
    }
}

Example

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

contract AllowanceCoin {
    uint256 public totalSupply;
    mapping(address => uint256) public balanceOf;
    mapping(address => mapping(address => uint256)) public allowance;
    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);

    constructor() {
        totalSupply = 1000;
        balanceOf[msg.sender] = 1000;
    }

    function approve(address spender, uint256 amount) public returns (bool) {
        allowance[msg.sender][spender] = amount;
        emit Approval(msg.sender, spender, amount);
        return true;
    }

    function transferFrom(address from, address to, uint256 amount) public returns (bool) {
        require(to != address(0), "zero receiver");
        require(balanceOf[from] >= amount, "not enough balance");
        require(allowance[from][msg.sender] >= amount, "allowance too small");
        allowance[from][msg.sender] -= amount;
        balanceOf[from] -= amount;
        balanceOf[to] += amount;
        emit Transfer(from, to, amount);
        return true;
    }
}

Key points

  • Allowance: permission for a spender to move a set amount of the owner tokens.
  • Approve: the function the owner calls to grant an allowance.
  • Allowance record: a mapping from each owner and each spender to the amount allowed.
  • TransferFrom: the function the spender calls to move tokens from the owner to a receiver.
  • Spending: each transferFrom subtracts the moved amount from the remaining allowance.
  • Revoking: setting an allowance to zero removes the permission.

In this example, the owner approves 100 units for a spender. The spender moves 40 units twice to the same receiver, which leaves an allowance of 20 and a receiver balance of 80.

Note

an allowance does not move tokens by itself, so approving an address never changes any balance.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Remix IDE

Exercise

After approving 50 and a transferFrom of 30, what is the remaining allowance?