Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/erc20 erc165 #1073 #1629

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## 2.2.0 (unreleased)

### New features:
* `ERC20`, `ERC20Burnable`, `ERC20Detailed`: added ERC165 implementations
* `ERC20`: added internal `_approve(address owner, address spender, uint256 value)`, allowing derived contracts to set the allowance of arbitrary accounts.
* `ERC20Metadata`: added internal `_setTokenURI(string memory tokenURI)`.

Expand Down
20 changes: 19 additions & 1 deletion contracts/token/ERC20/ERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ pragma solidity ^0.5.2;

import "./IERC20.sol";
import "../../math/SafeMath.sol";
import "../../introspection/ERC165.sol";

/**
* @title Standard ERC20 token
Expand All @@ -15,7 +16,7 @@ import "../../math/SafeMath.sol";
* all accounts just by listening to said events. Note that this isn't required by the specification, and other
* compliant implementations may not do it.
*/
contract ERC20 is IERC20 {
contract ERC20 is ERC165, IERC20 {
using SafeMath for uint256;

mapping (address => uint256) private _balances;
Expand All @@ -24,6 +25,23 @@ contract ERC20 is IERC20 {

uint256 private _totalSupply;

bytes4 private constant _INTERFACE_ID_ERC20 = 0x36372b07;
/*
* 0x36372b07 ===
* bytes4(keccak256('transfer(address,uint256)')) ^
* bytes4(keccak256('approve(address,uint256)')) ^
* bytes4(keccak256('transferFrom(address,address,uint256)')) ^
* bytes4(keccak256('totalSupply()')) ^
* bytes4(keccak256('balanceOf(address)')) ^
* bytes4(keccak256('allowance(address,address)'))
*/

constructor()
public
{
_registerInterface(_INTERFACE_ID_ERC20);
}

/**
* @dev Total number of tokens in existence
*/
Expand Down
14 changes: 13 additions & 1 deletion contracts/token/ERC20/ERC20Burnable.sol
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
pragma solidity ^0.5.2;

import "./ERC20.sol";
import "../../introspection/ERC165.sol";

/**
* @title Burnable Token
* @dev Token that can be irreversibly burned (destroyed).
*/
contract ERC20Burnable is ERC20 {
contract ERC20Burnable is ERC165, ERC20 {
bytes4 private constant _INTERFACE_ID_ERC20_BURNABLE = 0x3b5a0bf8;
/*
* 0x3b5a0bf8 ===
* bytes4(keccak256('burn(uint256)') ^
* bytes4(keccak256('burnFrom(address,uint256)'))
*/

constructor () public {
_registerInterface(_INTERFACE_ID_ERC20_BURNABLE);
}

/**
* @dev Burns a specific amount of tokens.
* @param value The amount of token to be burned.
Expand Down
12 changes: 11 additions & 1 deletion contracts/token/ERC20/ERC20Detailed.sol
Original file line number Diff line number Diff line change
@@ -1,22 +1,32 @@
pragma solidity ^0.5.2;

import "./IERC20.sol";
import "../../introspection/ERC165.sol";

/**
* @title ERC20Detailed token
* @dev The decimals are only for visualization purposes.
* All the operations are done using the smallest and indivisible token unit,
* just as on Ethereum all the operations are done in wei.
*/
contract ERC20Detailed is IERC20 {
contract ERC20Detailed is ERC165, IERC20 {
string private _name;
string private _symbol;
uint8 private _decimals;

bytes4 private constant _INTERFACE_ID_ERC20_DETAILED = 0xa219a025;
/*
* 0xa219a025 ===
* bytes4(keccak256('name()') ^
* bytes4(keccak256('symbol()')) ^
* bytes4(keccak256('decimals()'))
*/

constructor (string memory name, string memory symbol, uint8 decimals) public {
_name = name;
_symbol = symbol;
_decimals = decimals;
_registerInterface(_INTERFACE_ID_ERC20_DETAILED);
}

/**
Expand Down
17 changes: 17 additions & 0 deletions test/introspection/SupportsInterface.behavior.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,23 @@ const INTERFACE_IDS = {
ERC165: makeInterfaceId([
'supportsInterface(bytes4)',
]),
ERC20: makeInterfaceId([
'transfer(address,uint256)',
'approve(address,uint256)',
'transferFrom(address,address,uint256)',
'totalSupply()',
'balanceOf(address)',
'allowance(address,address)',
]),
ERC20Burnable: makeInterfaceId([
'burn(uint256)',
'burnFrom(address,uint256)',
]),
ERC20Detailed: makeInterfaceId([
'name()',
'symbol()',
'decimals()',
]),
ERC721: makeInterfaceId([
'balanceOf(address)',
'ownerOf(uint256)',
Expand Down
6 changes: 6 additions & 0 deletions test/token/ERC20/ERC20.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const { BN, constants, expectEvent, shouldFail } = require('openzeppelin-test-he
const { ZERO_ADDRESS } = constants;

const ERC20Mock = artifacts.require('ERC20Mock');
const { shouldSupportInterfaces } = require('../../introspection/SupportsInterface.behavior');

contract('ERC20', function ([_, initialHolder, recipient, anotherAccount]) {
const initialSupply = new BN(100);
Expand Down Expand Up @@ -559,4 +560,9 @@ contract('ERC20', function ([_, initialHolder, recipient, anotherAccount]) {
});
});
}

shouldSupportInterfaces([
'ERC165',
'ERC20',
]);
});
6 changes: 6 additions & 0 deletions test/token/ERC20/ERC20Burnable.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const { BN } = require('openzeppelin-test-helpers');

const { shouldBehaveLikeERC20Burnable } = require('./behaviors/ERC20Burnable.behavior');
const ERC20BurnableMock = artifacts.require('ERC20BurnableMock');
const { shouldSupportInterfaces } = require('../../introspection/SupportsInterface.behavior');

contract('ERC20Burnable', function ([_, owner, ...otherAccounts]) {
const initialBalance = new BN(1000);
Expand All @@ -11,4 +12,9 @@ contract('ERC20Burnable', function ([_, owner, ...otherAccounts]) {
});

shouldBehaveLikeERC20Burnable(owner, initialBalance, otherAccounts);

shouldSupportInterfaces([
'ERC165',
'ERC20Burnable',
]);
});
14 changes: 10 additions & 4 deletions test/token/ERC20/ERC20Detailed.test.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,31 @@
const { BN } = require('openzeppelin-test-helpers');

const ERC20DetailedMock = artifacts.require('ERC20DetailedMock');
const { shouldSupportInterfaces } = require('../../introspection/SupportsInterface.behavior');

contract('ERC20Detailed', function () {
const _name = 'My Detailed ERC20';
const _symbol = 'MDT';
const _decimals = new BN(18);

beforeEach(async function () {
this.detailedERC20 = await ERC20DetailedMock.new(_name, _symbol, _decimals);
this.token = await ERC20DetailedMock.new(_name, _symbol, _decimals);
});

it('has a name', async function () {
(await this.detailedERC20.name()).should.be.equal(_name);
(await this.token.name()).should.be.equal(_name);
});

it('has a symbol', async function () {
(await this.detailedERC20.symbol()).should.be.equal(_symbol);
(await this.token.symbol()).should.be.equal(_symbol);
});

it('has an amount of decimals', async function () {
(await this.detailedERC20.decimals()).should.be.bignumber.equal(_decimals);
(await this.token.decimals()).should.be.bignumber.equal(_decimals);
});

shouldSupportInterfaces([
'ERC165',
'ERC20Detailed',
]);
});