From 4ca0d7ab86ed69ef34fdeb4647163c60f64e2baa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Mon, 30 Mar 2020 15:35:21 -0300 Subject: [PATCH 01/10] Merge ERC20Detailed into ERC20, make derived contracts abstract --- contracts/GSN/GSNRecipientERC20Fee.sol | 9 ++--- contracts/mocks/ERC20BurnableMock.sol | 8 +++- contracts/mocks/ERC20CappedMock.sol | 4 +- contracts/mocks/ERC20DetailedMock.sol | 13 ------ contracts/mocks/ERC20Mock.sol | 8 +++- contracts/mocks/ERC20PausableMock.sol | 8 +++- contracts/mocks/ERC20SnapshotMock.sol | 8 +++- contracts/token/ERC20/ERC20.sol | 46 +++++++++++++++++++++ contracts/token/ERC20/ERC20Burnable.sol | 2 +- contracts/token/ERC20/ERC20Capped.sol | 2 +- contracts/token/ERC20/ERC20Detailed.sol | 54 ------------------------- contracts/token/ERC20/ERC20Pausable.sol | 2 +- contracts/token/ERC20/ERC20Snapshot.sol | 2 +- test/GSN/GSNRecipientERC20Fee.test.js | 4 +- test/token/ERC20/ERC20.test.js | 18 ++++++++- test/token/ERC20/ERC20Burnable.test.js | 6 ++- test/token/ERC20/ERC20Capped.test.js | 8 +++- test/token/ERC20/ERC20Detailed.test.js | 28 ------------- test/token/ERC20/ERC20Pausable.test.js | 6 ++- test/token/ERC20/ERC20Snapshot.test.js | 6 ++- 20 files changed, 125 insertions(+), 117 deletions(-) delete mode 100644 contracts/mocks/ERC20DetailedMock.sol delete mode 100644 contracts/token/ERC20/ERC20Detailed.sol delete mode 100644 test/token/ERC20/ERC20Detailed.test.js diff --git a/contracts/GSN/GSNRecipientERC20Fee.sol b/contracts/GSN/GSNRecipientERC20Fee.sol index 893872c5ffa..daba7ebf2de 100644 --- a/contracts/GSN/GSNRecipientERC20Fee.sol +++ b/contracts/GSN/GSNRecipientERC20Fee.sol @@ -5,7 +5,6 @@ import "../math/SafeMath.sol"; import "../access/Ownable.sol"; import "../token/ERC20/SafeERC20.sol"; import "../token/ERC20/ERC20.sol"; -import "../token/ERC20/ERC20Detailed.sol"; /** * @dev A xref:ROOT:gsn-strategies.adoc#gsn-strategies[GSN strategy] that charges transaction fees in a special purpose ERC20 @@ -112,10 +111,10 @@ contract GSNRecipientERC20Fee is GSNRecipient { * outside of this context. */ // solhint-disable-next-line contract-name-camelcase -contract __unstable__ERC20Owned is ERC20, ERC20Detailed, Ownable { +contract __unstable__ERC20Owned is ERC20, Ownable { uint256 private constant _UINT256_MAX = 2**256 - 1; - constructor(string memory name, string memory symbol, uint8 decimals) public ERC20Detailed(name, symbol, decimals) { } + constructor(string memory name, string memory symbol, uint8 decimals) public ERC20(name, symbol, decimals) { } // The owner (GSNRecipientERC20Fee) can mint tokens function mint(address account, uint256 amount) public onlyOwner { @@ -123,7 +122,7 @@ contract __unstable__ERC20Owned is ERC20, ERC20Detailed, Ownable { } // The owner has 'infinite' allowance for all token holders - function allowance(address tokenOwner, address spender) public view override(ERC20, IERC20) returns (uint256) { + function allowance(address tokenOwner, address spender) public view override returns (uint256) { if (spender == owner()) { return _UINT256_MAX; } else { @@ -140,7 +139,7 @@ contract __unstable__ERC20Owned is ERC20, ERC20Detailed, Ownable { } } - function transferFrom(address sender, address recipient, uint256 amount) public override(ERC20, IERC20) returns (bool) { + function transferFrom(address sender, address recipient, uint256 amount) public override returns (bool) { if (recipient == owner()) { _transfer(sender, recipient, amount); return true; diff --git a/contracts/mocks/ERC20BurnableMock.sol b/contracts/mocks/ERC20BurnableMock.sol index 17bf07150eb..ecb333bb782 100644 --- a/contracts/mocks/ERC20BurnableMock.sol +++ b/contracts/mocks/ERC20BurnableMock.sol @@ -3,7 +3,13 @@ pragma solidity ^0.6.0; import "../token/ERC20/ERC20Burnable.sol"; contract ERC20BurnableMock is ERC20Burnable { - constructor (address initialAccount, uint256 initialBalance) public { + constructor ( + string memory name, + string memory symbol, + uint8 decimals, + address initialAccount, + uint256 initialBalance + ) public ERC20(name, symbol, decimals) { _mint(initialAccount, initialBalance); } } diff --git a/contracts/mocks/ERC20CappedMock.sol b/contracts/mocks/ERC20CappedMock.sol index 605d5f78e97..9e487920194 100644 --- a/contracts/mocks/ERC20CappedMock.sol +++ b/contracts/mocks/ERC20CappedMock.sol @@ -3,7 +3,9 @@ pragma solidity ^0.6.0; import "../token/ERC20/ERC20Capped.sol"; contract ERC20CappedMock is ERC20Capped { - constructor (uint256 cap) public ERC20Capped(cap) { } + constructor (string memory name, string memory symbol, uint8 decimals, uint256 cap) + public ERC20(name, symbol, decimals) ERC20Capped(cap) + { } function mint(address to, uint256 tokenId) public { _mint(to, tokenId); diff --git a/contracts/mocks/ERC20DetailedMock.sol b/contracts/mocks/ERC20DetailedMock.sol deleted file mode 100644 index fcd5403c559..00000000000 --- a/contracts/mocks/ERC20DetailedMock.sol +++ /dev/null @@ -1,13 +0,0 @@ -pragma solidity ^0.6.0; - -import "../token/ERC20/ERC20.sol"; -import "../token/ERC20/ERC20Detailed.sol"; - -contract ERC20DetailedMock is ERC20, ERC20Detailed { - constructor (string memory name, string memory symbol, uint8 decimals) - public - ERC20Detailed(name, symbol, decimals) - { - - } -} diff --git a/contracts/mocks/ERC20Mock.sol b/contracts/mocks/ERC20Mock.sol index b263f4d639b..f95b84d48ce 100644 --- a/contracts/mocks/ERC20Mock.sol +++ b/contracts/mocks/ERC20Mock.sol @@ -4,7 +4,13 @@ import "../token/ERC20/ERC20.sol"; // mock class using ERC20 contract ERC20Mock is ERC20 { - constructor (address initialAccount, uint256 initialBalance) public payable { + constructor ( + string memory name, + string memory symbol, + uint8 decimals, + address initialAccount, + uint256 initialBalance + ) public payable ERC20(name, symbol, decimals) { _mint(initialAccount, initialBalance); } diff --git a/contracts/mocks/ERC20PausableMock.sol b/contracts/mocks/ERC20PausableMock.sol index d15b0237eac..5ca6e47fbf8 100644 --- a/contracts/mocks/ERC20PausableMock.sol +++ b/contracts/mocks/ERC20PausableMock.sol @@ -4,7 +4,13 @@ import "../token/ERC20/ERC20Pausable.sol"; // mock class using ERC20Pausable contract ERC20PausableMock is ERC20Pausable { - constructor (address initialAccount, uint256 initialBalance) public { + constructor ( + string memory name, + string memory symbol, + uint8 decimals, + address initialAccount, + uint256 initialBalance + ) public ERC20(name, symbol, decimals) { _mint(initialAccount, initialBalance); } diff --git a/contracts/mocks/ERC20SnapshotMock.sol b/contracts/mocks/ERC20SnapshotMock.sol index 9d5c091ca75..726859e1194 100644 --- a/contracts/mocks/ERC20SnapshotMock.sol +++ b/contracts/mocks/ERC20SnapshotMock.sol @@ -4,7 +4,13 @@ import "../token/ERC20/ERC20Snapshot.sol"; contract ERC20SnapshotMock is ERC20Snapshot { - constructor(address initialAccount, uint256 initialBalance) public { + constructor( + string memory name, + string memory symbol, + uint8 decimals, + address initialAccount, + uint256 initialBalance + ) public ERC20(name, symbol, decimals) { _mint(initialAccount, initialBalance); } diff --git a/contracts/token/ERC20/ERC20.sol b/contracts/token/ERC20/ERC20.sol index 6b0d1b14c7e..bd6e404ee8a 100644 --- a/contracts/token/ERC20/ERC20.sol +++ b/contracts/token/ERC20/ERC20.sol @@ -37,6 +37,52 @@ contract ERC20 is Context, IERC20 { uint256 private _totalSupply; + string private _name; + string private _symbol; + uint8 private _decimals; + + /** + * @dev Sets the values for `name`, `symbol`, and `decimals`. All three of + * these values are immutable: they can only be set once during + * construction. + */ + constructor (string memory name, string memory symbol, uint8 decimals) public { + _name = name; + _symbol = symbol; + _decimals = decimals; + } + + /** + * @dev Returns the name of the token. + */ + function name() public view returns (string memory) { + return _name; + } + + /** + * @dev Returns the symbol of the token, usually a shorter version of the + * name. + */ + function symbol() public view returns (string memory) { + return _symbol; + } + + /** + * @dev Returns the number of decimals used to get its user representation. + * For example, if `decimals` equals `2`, a balance of `505` tokens should + * be displayed to a user as `5,05` (`505 / 10 ** 2`). + * + * Tokens usually opt for a value of 18, imitating the relationship between + * Ether and Wei. + * + * NOTE: This information is only used for _display_ purposes: it in + * no way affects any of the arithmetic of the contract, including + * {IERC20-balanceOf} and {IERC20-transfer}. + */ + function decimals() public view returns (uint8) { + return _decimals; + } + /** * @dev See {IERC20-totalSupply}. */ diff --git a/contracts/token/ERC20/ERC20Burnable.sol b/contracts/token/ERC20/ERC20Burnable.sol index 749fdc93d1a..c7a95e3e244 100644 --- a/contracts/token/ERC20/ERC20Burnable.sol +++ b/contracts/token/ERC20/ERC20Burnable.sol @@ -8,7 +8,7 @@ import "./ERC20.sol"; * tokens and those that they have an allowance for, in a way that can be * recognized off-chain (via event analysis). */ -contract ERC20Burnable is Context, ERC20 { +abstract contract ERC20Burnable is Context, ERC20 { /** * @dev Destroys `amount` tokens from the caller. * diff --git a/contracts/token/ERC20/ERC20Capped.sol b/contracts/token/ERC20/ERC20Capped.sol index 5f695c81831..c64ea386f1b 100644 --- a/contracts/token/ERC20/ERC20Capped.sol +++ b/contracts/token/ERC20/ERC20Capped.sol @@ -5,7 +5,7 @@ import "./ERC20.sol"; /** * @dev Extension of {ERC20} that adds a cap to the supply of tokens. */ -contract ERC20Capped is ERC20 { +abstract contract ERC20Capped is ERC20 { uint256 private _cap; /** diff --git a/contracts/token/ERC20/ERC20Detailed.sol b/contracts/token/ERC20/ERC20Detailed.sol deleted file mode 100644 index fd44f834f48..00000000000 --- a/contracts/token/ERC20/ERC20Detailed.sol +++ /dev/null @@ -1,54 +0,0 @@ -pragma solidity ^0.6.0; - -import "./IERC20.sol"; - -/** - * @dev Optional functions from the ERC20 standard. - */ -abstract contract ERC20Detailed is IERC20 { - string private _name; - string private _symbol; - uint8 private _decimals; - - /** - * @dev Sets the values for `name`, `symbol`, and `decimals`. All three of - * these values are immutable: they can only be set once during - * construction. - */ - constructor (string memory name, string memory symbol, uint8 decimals) public { - _name = name; - _symbol = symbol; - _decimals = decimals; - } - - /** - * @dev Returns the name of the token. - */ - function name() public view returns (string memory) { - return _name; - } - - /** - * @dev Returns the symbol of the token, usually a shorter version of the - * name. - */ - function symbol() public view returns (string memory) { - return _symbol; - } - - /** - * @dev Returns the number of decimals used to get its user representation. - * For example, if `decimals` equals `2`, a balance of `505` tokens should - * be displayed to a user as `5,05` (`505 / 10 ** 2`). - * - * Tokens usually opt for a value of 18, imitating the relationship between - * Ether and Wei. - * - * NOTE: This information is only used for _display_ purposes: it in - * no way affects any of the arithmetic of the contract, including - * {IERC20-balanceOf} and {IERC20-transfer}. - */ - function decimals() public view returns (uint8) { - return _decimals; - } -} diff --git a/contracts/token/ERC20/ERC20Pausable.sol b/contracts/token/ERC20/ERC20Pausable.sol index a29554a8641..5aa21a6038a 100644 --- a/contracts/token/ERC20/ERC20Pausable.sol +++ b/contracts/token/ERC20/ERC20Pausable.sol @@ -11,7 +11,7 @@ import "../../utils/Pausable.sol"; * period, or having an emergency switch for freezing all token transfers in the * event of a large bug. */ -contract ERC20Pausable is ERC20, Pausable { +abstract contract ERC20Pausable is ERC20, Pausable { /** * @dev See {ERC20-_beforeTokenTransfer}. * diff --git a/contracts/token/ERC20/ERC20Snapshot.sol b/contracts/token/ERC20/ERC20Snapshot.sol index 0783308d8b9..fef67dded1e 100644 --- a/contracts/token/ERC20/ERC20Snapshot.sol +++ b/contracts/token/ERC20/ERC20Snapshot.sol @@ -17,7 +17,7 @@ import "./ERC20.sol"; * account address. * @author Validity Labs AG */ -contract ERC20Snapshot is ERC20 { +abstract contract ERC20Snapshot is ERC20 { // Inspired by Jordi Baylina's MiniMeToken to record historical balances: // https://github.com/Giveth/minimd/blob/ea04d950eea153a04c51fa510b068b9dded390cb/contracts/MiniMeToken.sol diff --git a/test/GSN/GSNRecipientERC20Fee.test.js b/test/GSN/GSNRecipientERC20Fee.test.js index 68393edc260..bef4a9bfbd5 100644 --- a/test/GSN/GSNRecipientERC20Fee.test.js +++ b/test/GSN/GSNRecipientERC20Fee.test.js @@ -6,7 +6,7 @@ const gsn = require('@openzeppelin/gsn-helpers'); const { expect } = require('chai'); const GSNRecipientERC20FeeMock = contract.fromArtifact('GSNRecipientERC20FeeMock'); -const ERC20Detailed = contract.fromArtifact('ERC20Detailed'); +const ERC20 = contract.fromArtifact('ERC20'); const IRelayHub = contract.fromArtifact('IRelayHub'); describe('GSNRecipientERC20Fee', function () { @@ -17,7 +17,7 @@ describe('GSNRecipientERC20Fee', function () { beforeEach(async function () { this.recipient = await GSNRecipientERC20FeeMock.new(name, symbol); - this.token = await ERC20Detailed.at(await this.recipient.token()); + this.token = await ERC20.at(await this.recipient.token()); }); describe('token', function () { diff --git a/test/token/ERC20/ERC20.test.js b/test/token/ERC20/ERC20.test.js index f2ecb8e0ed3..c280e6a4f34 100644 --- a/test/token/ERC20/ERC20.test.js +++ b/test/token/ERC20/ERC20.test.js @@ -15,10 +15,26 @@ const ERC20Mock = contract.fromArtifact('ERC20Mock'); describe('ERC20', function () { const [ initialHolder, recipient, anotherAccount ] = accounts; + const name = 'My Token'; + const symbol = 'MTKN'; + const decimals = new BN(18); + const initialSupply = new BN(100); beforeEach(async function () { - this.token = await ERC20Mock.new(initialHolder, initialSupply); + this.token = await ERC20Mock.new(name, symbol, decimals, initialHolder, initialSupply); + }); + + it('has a name', async function () { + expect(await this.token.name()).to.equal(name); + }); + + it('has a symbol', async function () { + expect(await this.token.symbol()).to.equal(symbol); + }); + + it('has an amount of decimals', async function () { + expect(await this.token.decimals()).to.be.bignumber.equal(decimals); }); shouldBehaveLikeERC20('ERC20', initialSupply, initialHolder, recipient, anotherAccount); diff --git a/test/token/ERC20/ERC20Burnable.test.js b/test/token/ERC20/ERC20Burnable.test.js index fbed19c9fe2..6eea2e039ef 100644 --- a/test/token/ERC20/ERC20Burnable.test.js +++ b/test/token/ERC20/ERC20Burnable.test.js @@ -10,8 +10,12 @@ describe('ERC20Burnable', function () { const initialBalance = new BN(1000); + const name = 'My Token'; + const symbol = 'MTKN'; + const decimals = new BN(18); + beforeEach(async function () { - this.token = await ERC20BurnableMock.new(owner, initialBalance, { from: owner }); + this.token = await ERC20BurnableMock.new(name, symbol, decimals, owner, initialBalance, { from: owner }); }); shouldBehaveLikeERC20Burnable(owner, initialBalance, otherAccounts); diff --git a/test/token/ERC20/ERC20Capped.test.js b/test/token/ERC20/ERC20Capped.test.js index 5978e952e75..2bdf773014b 100644 --- a/test/token/ERC20/ERC20Capped.test.js +++ b/test/token/ERC20/ERC20Capped.test.js @@ -10,15 +10,19 @@ describe('ERC20Capped', function () { const cap = ether('1000'); + const name = 'My Token'; + const symbol = 'MTKN'; + const decimals = new BN(18); + it('requires a non-zero cap', async function () { await expectRevert( - ERC20Capped.new(new BN(0), { from: minter }), 'ERC20Capped: cap is 0' + ERC20Capped.new(name, symbol, decimals, new BN(0), { from: minter }), 'ERC20Capped: cap is 0' ); }); context('once deployed', async function () { beforeEach(async function () { - this.token = await ERC20Capped.new(cap, { from: minter }); + this.token = await ERC20Capped.new(name, symbol, decimals, cap, { from: minter }); }); shouldBehaveLikeERC20Capped(minter, otherAccounts, cap); diff --git a/test/token/ERC20/ERC20Detailed.test.js b/test/token/ERC20/ERC20Detailed.test.js deleted file mode 100644 index 6be53c8957c..00000000000 --- a/test/token/ERC20/ERC20Detailed.test.js +++ /dev/null @@ -1,28 +0,0 @@ -const { contract } = require('@openzeppelin/test-environment'); -const { BN } = require('@openzeppelin/test-helpers'); - -const { expect } = require('chai'); - -const ERC20DetailedMock = contract.fromArtifact('ERC20DetailedMock'); - -describe('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); - }); - - it('has a name', async function () { - expect(await this.detailedERC20.name()).to.equal(_name); - }); - - it('has a symbol', async function () { - expect(await this.detailedERC20.symbol()).to.equal(_symbol); - }); - - it('has an amount of decimals', async function () { - expect(await this.detailedERC20.decimals()).to.be.bignumber.equal(_decimals); - }); -}); diff --git a/test/token/ERC20/ERC20Pausable.test.js b/test/token/ERC20/ERC20Pausable.test.js index 9b57b6633de..2a6a891db40 100644 --- a/test/token/ERC20/ERC20Pausable.test.js +++ b/test/token/ERC20/ERC20Pausable.test.js @@ -11,8 +11,12 @@ describe('ERC20Pausable', function () { const initialSupply = new BN(100); + const name = 'My Token'; + const symbol = 'MTKN'; + const decimals = new BN(18); + beforeEach(async function () { - this.token = await ERC20PausableMock.new(holder, initialSupply); + this.token = await ERC20PausableMock.new(name, symbol, decimals, holder, initialSupply); }); describe('pausable token', function () { diff --git a/test/token/ERC20/ERC20Snapshot.test.js b/test/token/ERC20/ERC20Snapshot.test.js index 43bbdba7817..66183b1a389 100644 --- a/test/token/ERC20/ERC20Snapshot.test.js +++ b/test/token/ERC20/ERC20Snapshot.test.js @@ -10,8 +10,12 @@ describe('ERC20Snapshot', function () { const initialSupply = new BN(100); + const name = 'My Token'; + const symbol = 'MTKN'; + const decimals = new BN(18); + beforeEach(async function () { - this.token = await ERC20SnapshotMock.new(initialHolder, initialSupply); + this.token = await ERC20SnapshotMock.new(name, symbol, decimals, initialHolder, initialSupply); }); describe('snapshot', function () { From 11a0d9a968b22d2687ccca5c3340cb8e29ff33a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Mon, 30 Mar 2020 15:35:28 -0300 Subject: [PATCH 02/10] Fix Create2 tests --- contracts/mocks/Create2Impl.sol | 6 +++--- test/utils/Create2.test.js | 31 ++++++++++++++++++------------- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/contracts/mocks/Create2Impl.sol b/contracts/mocks/Create2Impl.sol index 6ef6cc86bec..018236882e8 100644 --- a/contracts/mocks/Create2Impl.sol +++ b/contracts/mocks/Create2Impl.sol @@ -1,16 +1,16 @@ pragma solidity ^0.6.0; import "../utils/Create2.sol"; -import "../token/ERC20/ERC20.sol"; +import "../introspection/ERC1820Implementer.sol"; contract Create2Impl { function deploy(uint256 value, bytes32 salt, bytes memory code) public { Create2.deploy(value, salt, code); } - function deployERC20(uint256 value, bytes32 salt) public { + function deployERC1820Implementer(uint256 value, bytes32 salt) public { // solhint-disable-next-line indent - Create2.deploy(value, salt, type(ERC20).creationCode); + Create2.deploy(value, salt, type(ERC1820Implementer).creationCode); } function computeAddress(bytes32 salt, bytes32 codeHash) public view returns (address) { diff --git a/test/utils/Create2.test.js b/test/utils/Create2.test.js index c74273cc22a..7b4c35b90a2 100644 --- a/test/utils/Create2.test.js +++ b/test/utils/Create2.test.js @@ -5,16 +5,20 @@ const { expect } = require('chai'); const Create2Impl = contract.fromArtifact('Create2Impl'); const ERC20Mock = contract.fromArtifact('ERC20Mock'); -const ERC20 = contract.fromArtifact('ERC20'); +const ERC1820Implementer = contract.fromArtifact('ERC1820Implementer'); describe('Create2', function () { const [deployerAccount] = accounts; const salt = 'salt message'; const saltHex = web3.utils.soliditySha3(salt); - const constructorByteCode = `${ERC20Mock.bytecode}${web3.eth.abi - .encodeParameters(['address', 'uint256'], [deployerAccount, 100]).slice(2) - }`; + + const encodedParams = web3.eth.abi.encodeParameters( + ['string', 'string', 'uint8', 'address', 'uint256'], + ['MyToken', 'MTKN', 18, deployerAccount, 100] + ).slice(2); + + const constructorByteCode = `${ERC20Mock.bytecode}${encodedParams}`; beforeEach(async function () { this.factory = await Create2Impl.new(); @@ -36,19 +40,20 @@ describe('Create2', function () { expect(onChainComputed).to.equal(offChainComputed); }); - it('should deploy a ERC20 from inline assembly code', async function () { + it('should deploy a ERC1820Implementer from inline assembly code', async function () { const offChainComputed = - computeCreate2Address(saltHex, ERC20.bytecode, this.factory.address); - await this.factory - .deploy(0, saltHex, ERC20.bytecode, { from: deployerAccount }); - expect(ERC20.bytecode).to.include((await web3.eth.getCode(offChainComputed)).slice(2)); + computeCreate2Address(saltHex, ERC1820Implementer.bytecode, this.factory.address); + + await this.factory.deployERC1820Implementer(0, saltHex); + + expect(ERC1820Implementer.bytecode).to.include((await web3.eth.getCode(offChainComputed)).slice(2)); }); it('should deploy a ERC20Mock with correct balances', async function () { - const offChainComputed = - computeCreate2Address(saltHex, constructorByteCode, this.factory.address); - await this.factory - .deploy(0, saltHex, constructorByteCode, { from: deployerAccount }); + const offChainComputed = computeCreate2Address(saltHex, constructorByteCode, this.factory.address); + + await this.factory.deploy(0, saltHex, constructorByteCode); + const erc20 = await ERC20Mock.at(offChainComputed); expect(await erc20.balanceOf(deployerAccount)).to.be.bignumber.equal(new BN(100)); }); From 62ac11c15eb01ae131ee26bb65719226615d2f9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Wed, 1 Apr 2020 12:00:19 -0300 Subject: [PATCH 03/10] Fix failing test --- test/token/ERC20/TokenTimelock.test.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/token/ERC20/TokenTimelock.test.js b/test/token/ERC20/TokenTimelock.test.js index 751916ae481..fd32f9608ed 100644 --- a/test/token/ERC20/TokenTimelock.test.js +++ b/test/token/ERC20/TokenTimelock.test.js @@ -10,11 +10,15 @@ const TokenTimelock = contract.fromArtifact('TokenTimelock'); describe('TokenTimelock', function () { const [ beneficiary ] = accounts; + const name = 'My Token'; + const symbol = 'MTKN'; + const decimals = new BN(18); + const amount = new BN(100); context('with token', function () { beforeEach(async function () { - this.token = await ERC20Mock.new(beneficiary, 0); // We're not using the preminted tokens + this.token = await ERC20Mock.new(name, symbol, decimals, beneficiary, 0); // We're not using the preminted tokens }); it('rejects a release time in the past', async function () { From 54e850edd9e3e56b14dae21fa9efc51a79a9d13f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Wed, 1 Apr 2020 17:26:00 -0300 Subject: [PATCH 04/10] Default decimals to 18 --- contracts/GSN/GSNRecipientERC20Fee.sol | 4 +-- contracts/mocks/ERC20BurnableMock.sol | 3 +- contracts/mocks/ERC20CappedMock.sol | 4 +-- contracts/mocks/ERC20Mock.sol | 3 +- contracts/mocks/ERC20PausableMock.sol | 3 +- contracts/mocks/ERC20SnapshotMock.sol | 3 +- contracts/token/ERC20/ERC20.sol | 40 ++++++++++++++++++++++---- test/token/ERC20/ERC20.test.js | 7 ++--- test/token/ERC20/ERC20Burnable.test.js | 3 +- test/token/ERC20/ERC20Capped.test.js | 3 +- test/token/ERC20/ERC20Pausable.test.js | 3 +- test/token/ERC20/ERC20Snapshot.test.js | 3 +- test/token/ERC20/TokenTimelock.test.js | 3 +- test/utils/Create2.test.js | 4 +-- 14 files changed, 53 insertions(+), 33 deletions(-) diff --git a/contracts/GSN/GSNRecipientERC20Fee.sol b/contracts/GSN/GSNRecipientERC20Fee.sol index daba7ebf2de..226559a537d 100644 --- a/contracts/GSN/GSNRecipientERC20Fee.sol +++ b/contracts/GSN/GSNRecipientERC20Fee.sol @@ -29,7 +29,7 @@ contract GSNRecipientERC20Fee is GSNRecipient { * @dev The arguments to the constructor are the details that the gas payment token will have: `name` and `symbol`. `decimals` is hard-coded to 18. */ constructor(string memory name, string memory symbol) public { - _token = new __unstable__ERC20Owned(name, symbol, 18); + _token = new __unstable__ERC20Owned(name, symbol); } /** @@ -114,7 +114,7 @@ contract GSNRecipientERC20Fee is GSNRecipient { contract __unstable__ERC20Owned is ERC20, Ownable { uint256 private constant _UINT256_MAX = 2**256 - 1; - constructor(string memory name, string memory symbol, uint8 decimals) public ERC20(name, symbol, decimals) { } + constructor(string memory name, string memory symbol) public ERC20(name, symbol) { } // The owner (GSNRecipientERC20Fee) can mint tokens function mint(address account, uint256 amount) public onlyOwner { diff --git a/contracts/mocks/ERC20BurnableMock.sol b/contracts/mocks/ERC20BurnableMock.sol index ecb333bb782..2d4a0df428a 100644 --- a/contracts/mocks/ERC20BurnableMock.sol +++ b/contracts/mocks/ERC20BurnableMock.sol @@ -6,10 +6,9 @@ contract ERC20BurnableMock is ERC20Burnable { constructor ( string memory name, string memory symbol, - uint8 decimals, address initialAccount, uint256 initialBalance - ) public ERC20(name, symbol, decimals) { + ) public ERC20(name, symbol) { _mint(initialAccount, initialBalance); } } diff --git a/contracts/mocks/ERC20CappedMock.sol b/contracts/mocks/ERC20CappedMock.sol index 9e487920194..00d3980a410 100644 --- a/contracts/mocks/ERC20CappedMock.sol +++ b/contracts/mocks/ERC20CappedMock.sol @@ -3,8 +3,8 @@ pragma solidity ^0.6.0; import "../token/ERC20/ERC20Capped.sol"; contract ERC20CappedMock is ERC20Capped { - constructor (string memory name, string memory symbol, uint8 decimals, uint256 cap) - public ERC20(name, symbol, decimals) ERC20Capped(cap) + constructor (string memory name, string memory symbol, uint256 cap) + public ERC20(name, symbol) ERC20Capped(cap) { } function mint(address to, uint256 tokenId) public { diff --git a/contracts/mocks/ERC20Mock.sol b/contracts/mocks/ERC20Mock.sol index f95b84d48ce..6d1da14a60d 100644 --- a/contracts/mocks/ERC20Mock.sol +++ b/contracts/mocks/ERC20Mock.sol @@ -7,10 +7,9 @@ contract ERC20Mock is ERC20 { constructor ( string memory name, string memory symbol, - uint8 decimals, address initialAccount, uint256 initialBalance - ) public payable ERC20(name, symbol, decimals) { + ) public payable ERC20(name, symbol) { _mint(initialAccount, initialBalance); } diff --git a/contracts/mocks/ERC20PausableMock.sol b/contracts/mocks/ERC20PausableMock.sol index 5ca6e47fbf8..639941c7f55 100644 --- a/contracts/mocks/ERC20PausableMock.sol +++ b/contracts/mocks/ERC20PausableMock.sol @@ -7,10 +7,9 @@ contract ERC20PausableMock is ERC20Pausable { constructor ( string memory name, string memory symbol, - uint8 decimals, address initialAccount, uint256 initialBalance - ) public ERC20(name, symbol, decimals) { + ) public ERC20(name, symbol) { _mint(initialAccount, initialBalance); } diff --git a/contracts/mocks/ERC20SnapshotMock.sol b/contracts/mocks/ERC20SnapshotMock.sol index 726859e1194..2d15df597ed 100644 --- a/contracts/mocks/ERC20SnapshotMock.sol +++ b/contracts/mocks/ERC20SnapshotMock.sol @@ -7,10 +7,9 @@ contract ERC20SnapshotMock is ERC20Snapshot { constructor( string memory name, string memory symbol, - uint8 decimals, address initialAccount, uint256 initialBalance - ) public ERC20(name, symbol, decimals) { + ) public ERC20(name, symbol) { _mint(initialAccount, initialBalance); } diff --git a/contracts/token/ERC20/ERC20.sol b/contracts/token/ERC20/ERC20.sol index bd6e404ee8a..c60440fd756 100644 --- a/contracts/token/ERC20/ERC20.sol +++ b/contracts/token/ERC20/ERC20.sol @@ -42,14 +42,18 @@ contract ERC20 is Context, IERC20 { uint8 private _decimals; /** - * @dev Sets the values for `name`, `symbol`, and `decimals`. All three of - * these values are immutable: they can only be set once during + * @dev Sets the values for {name} and {symbol}, initializes {decimals} with + * a default value of 18. + * + * To select a different value for {decimals}, use {_setupDecimals}. + * + * All three of these values are immutable: they can only be set once during * construction. */ - constructor (string memory name, string memory symbol, uint8 decimals) public { + constructor (string memory name, string memory symbol) public { _name = name; _symbol = symbol; - _decimals = decimals; + _decimals = 18; } /** @@ -73,7 +77,8 @@ contract ERC20 is Context, IERC20 { * be displayed to a user as `5,05` (`505 / 10 ** 2`). * * Tokens usually opt for a value of 18, imitating the relationship between - * Ether and Wei. + * Ether and Wei. This is the value {ERC20} uses, unless {_setupDecimals} is + * called. * * NOTE: This information is only used for _display_ purposes: it in * no way affects any of the arithmetic of the contract, including @@ -269,6 +274,18 @@ contract ERC20 is Context, IERC20 { emit Approval(owner, spender, amount); } + /** + * @dev Sets {decimals} to a value other than the default one of 18. + * + * Requirements: + * + * - this function can only be called from a constructor. + */ + function _setupDecimals(uint8 decimals_) internal { + require(isConstructor(), "ERC20: decimals cannot be changed after construction"); + _decimals = decimals_; + } + /** * @dev Hook that is called before any transfer of tokens. This includes * minting and burning. @@ -284,4 +301,17 @@ contract ERC20 is Context, IERC20 { * To learn more about hooks, head to xref:ROOT:using-hooks.adoc[Using Hooks]. */ function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual { } + + // @dev Returns true if and only if the function is running in the constructor + function isConstructor() private view returns (bool) { + // extcodesize checks the size of the code stored in an address, and + // address returns the current address. Since the code is still not + // deployed when running a constructor, any checks on its code size will + // yield zero, making it an effective way to detect if a contract is + // under construction or not. + address self = address(this); + uint256 cs; + assembly { cs := extcodesize(self) } + return cs == 0; + } } diff --git a/test/token/ERC20/ERC20.test.js b/test/token/ERC20/ERC20.test.js index c280e6a4f34..14e4a74cbd0 100644 --- a/test/token/ERC20/ERC20.test.js +++ b/test/token/ERC20/ERC20.test.js @@ -17,12 +17,11 @@ describe('ERC20', function () { const name = 'My Token'; const symbol = 'MTKN'; - const decimals = new BN(18); const initialSupply = new BN(100); beforeEach(async function () { - this.token = await ERC20Mock.new(name, symbol, decimals, initialHolder, initialSupply); + this.token = await ERC20Mock.new(name, symbol, initialHolder, initialSupply); }); it('has a name', async function () { @@ -33,8 +32,8 @@ describe('ERC20', function () { expect(await this.token.symbol()).to.equal(symbol); }); - it('has an amount of decimals', async function () { - expect(await this.token.decimals()).to.be.bignumber.equal(decimals); + it('has 18 decimals', async function () { + expect(await this.token.decimals()).to.be.bignumber.equal(18); }); shouldBehaveLikeERC20('ERC20', initialSupply, initialHolder, recipient, anotherAccount); diff --git a/test/token/ERC20/ERC20Burnable.test.js b/test/token/ERC20/ERC20Burnable.test.js index 6eea2e039ef..24acca6c7c2 100644 --- a/test/token/ERC20/ERC20Burnable.test.js +++ b/test/token/ERC20/ERC20Burnable.test.js @@ -12,10 +12,9 @@ describe('ERC20Burnable', function () { const name = 'My Token'; const symbol = 'MTKN'; - const decimals = new BN(18); beforeEach(async function () { - this.token = await ERC20BurnableMock.new(name, symbol, decimals, owner, initialBalance, { from: owner }); + this.token = await ERC20BurnableMock.new(name, symbol, owner, initialBalance, { from: owner }); }); shouldBehaveLikeERC20Burnable(owner, initialBalance, otherAccounts); diff --git a/test/token/ERC20/ERC20Capped.test.js b/test/token/ERC20/ERC20Capped.test.js index 2bdf773014b..6d2d98a8aab 100644 --- a/test/token/ERC20/ERC20Capped.test.js +++ b/test/token/ERC20/ERC20Capped.test.js @@ -12,11 +12,10 @@ describe('ERC20Capped', function () { const name = 'My Token'; const symbol = 'MTKN'; - const decimals = new BN(18); it('requires a non-zero cap', async function () { await expectRevert( - ERC20Capped.new(name, symbol, decimals, new BN(0), { from: minter }), 'ERC20Capped: cap is 0' + ERC20Capped.new(name, symbol, new BN(0), { from: minter }), 'ERC20Capped: cap is 0' ); }); diff --git a/test/token/ERC20/ERC20Pausable.test.js b/test/token/ERC20/ERC20Pausable.test.js index 2a6a891db40..b43e820628f 100644 --- a/test/token/ERC20/ERC20Pausable.test.js +++ b/test/token/ERC20/ERC20Pausable.test.js @@ -13,10 +13,9 @@ describe('ERC20Pausable', function () { const name = 'My Token'; const symbol = 'MTKN'; - const decimals = new BN(18); beforeEach(async function () { - this.token = await ERC20PausableMock.new(name, symbol, decimals, holder, initialSupply); + this.token = await ERC20PausableMock.new(name, symbol, holder, initialSupply); }); describe('pausable token', function () { diff --git a/test/token/ERC20/ERC20Snapshot.test.js b/test/token/ERC20/ERC20Snapshot.test.js index 66183b1a389..c8aac9f7feb 100644 --- a/test/token/ERC20/ERC20Snapshot.test.js +++ b/test/token/ERC20/ERC20Snapshot.test.js @@ -12,10 +12,9 @@ describe('ERC20Snapshot', function () { const name = 'My Token'; const symbol = 'MTKN'; - const decimals = new BN(18); beforeEach(async function () { - this.token = await ERC20SnapshotMock.new(name, symbol, decimals, initialHolder, initialSupply); + this.token = await ERC20SnapshotMock.new(name, symbol, initialHolder, initialSupply); }); describe('snapshot', function () { diff --git a/test/token/ERC20/TokenTimelock.test.js b/test/token/ERC20/TokenTimelock.test.js index fd32f9608ed..10059d86122 100644 --- a/test/token/ERC20/TokenTimelock.test.js +++ b/test/token/ERC20/TokenTimelock.test.js @@ -12,13 +12,12 @@ describe('TokenTimelock', function () { const name = 'My Token'; const symbol = 'MTKN'; - const decimals = new BN(18); const amount = new BN(100); context('with token', function () { beforeEach(async function () { - this.token = await ERC20Mock.new(name, symbol, decimals, beneficiary, 0); // We're not using the preminted tokens + this.token = await ERC20Mock.new(name, symbol, beneficiary, 0); // We're not using the preminted tokens }); it('rejects a release time in the past', async function () { diff --git a/test/utils/Create2.test.js b/test/utils/Create2.test.js index 7b4c35b90a2..347ad5e54d5 100644 --- a/test/utils/Create2.test.js +++ b/test/utils/Create2.test.js @@ -14,8 +14,8 @@ describe('Create2', function () { const saltHex = web3.utils.soliditySha3(salt); const encodedParams = web3.eth.abi.encodeParameters( - ['string', 'string', 'uint8', 'address', 'uint256'], - ['MyToken', 'MTKN', 18, deployerAccount, 100] + ['string', 'string', 'address', 'uint256'], + ['MyToken', 'MTKN', deployerAccount, 100] ).slice(2); const constructorByteCode = `${ERC20Mock.bytecode}${encodedParams}`; From 9b61e0d4027d66852188e55969325cb3a892b842 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Wed, 1 Apr 2020 18:40:31 -0300 Subject: [PATCH 05/10] Add tests for setupDecimals --- contracts/mocks/ERC20DecimalsMock.sol | 13 +++++++++++++ contracts/token/ERC20/ERC20.sol | 5 +++-- test/token/ERC20/ERC20.test.js | 18 +++++++++++++++++- test/token/ERC20/ERC20Capped.test.js | 2 +- 4 files changed, 34 insertions(+), 4 deletions(-) create mode 100644 contracts/mocks/ERC20DecimalsMock.sol diff --git a/contracts/mocks/ERC20DecimalsMock.sol b/contracts/mocks/ERC20DecimalsMock.sol new file mode 100644 index 00000000000..d4d25c52f91 --- /dev/null +++ b/contracts/mocks/ERC20DecimalsMock.sol @@ -0,0 +1,13 @@ +pragma solidity ^0.6.0; + +import "../token/ERC20/ERC20.sol"; + +contract ERC20DecimalsMock is ERC20 { + constructor (string memory name, string memory symbol, uint8 decimals) public ERC20(name, symbol) { + _setupDecimals(decimals); + } + + function setupDecimals(uint8 decimals) public { + _setupDecimals(decimals); + } +} diff --git a/contracts/token/ERC20/ERC20.sol b/contracts/token/ERC20/ERC20.sol index c60440fd756..1f6f574b721 100644 --- a/contracts/token/ERC20/ERC20.sol +++ b/contracts/token/ERC20/ERC20.sol @@ -282,7 +282,7 @@ contract ERC20 is Context, IERC20 { * - this function can only be called from a constructor. */ function _setupDecimals(uint8 decimals_) internal { - require(isConstructor(), "ERC20: decimals cannot be changed after construction"); + require(_isConstructor(), "ERC20: decimals cannot be changed after construction"); _decimals = decimals_; } @@ -303,7 +303,7 @@ contract ERC20 is Context, IERC20 { function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual { } // @dev Returns true if and only if the function is running in the constructor - function isConstructor() private view returns (bool) { + function _isConstructor() private view returns (bool) { // extcodesize checks the size of the code stored in an address, and // address returns the current address. Since the code is still not // deployed when running a constructor, any checks on its code size will @@ -311,6 +311,7 @@ contract ERC20 is Context, IERC20 { // under construction or not. address self = address(this); uint256 cs; + // solhint-disable-next-line no-inline-assembly assembly { cs := extcodesize(self) } return cs == 0; } diff --git a/test/token/ERC20/ERC20.test.js b/test/token/ERC20/ERC20.test.js index 14e4a74cbd0..7996fa39ed1 100644 --- a/test/token/ERC20/ERC20.test.js +++ b/test/token/ERC20/ERC20.test.js @@ -11,6 +11,7 @@ const { } = require('./ERC20.behavior'); const ERC20Mock = contract.fromArtifact('ERC20Mock'); +const ERC20DecimalsMock = contract.fromArtifact('ERC20DecimalsMock'); describe('ERC20', function () { const [ initialHolder, recipient, anotherAccount ] = accounts; @@ -33,7 +34,22 @@ describe('ERC20', function () { }); it('has 18 decimals', async function () { - expect(await this.token.decimals()).to.be.bignumber.equal(18); + expect(await this.token.decimals()).to.be.bignumber.equal('18'); + }); + + describe('_setupDecimals', function () { + const decimals = new BN(6); + + it('can set decimals during construction', async function () { + const token = await ERC20DecimalsMock.new(name, symbol, decimals); + expect(await token.decimals()).to.be.bignumber.equal(decimals); + }); + + it('reverts if setting decimals after construction', async function () { + const token = await ERC20DecimalsMock.new(name, symbol, decimals); + + await expectRevert(token.setupDecimals(decimals.addn(1)), 'ERC20: decimals cannot be changed after construction'); + }); }); shouldBehaveLikeERC20('ERC20', initialSupply, initialHolder, recipient, anotherAccount); diff --git a/test/token/ERC20/ERC20Capped.test.js b/test/token/ERC20/ERC20Capped.test.js index 6d2d98a8aab..78d9a2069f9 100644 --- a/test/token/ERC20/ERC20Capped.test.js +++ b/test/token/ERC20/ERC20Capped.test.js @@ -21,7 +21,7 @@ describe('ERC20Capped', function () { context('once deployed', async function () { beforeEach(async function () { - this.token = await ERC20Capped.new(name, symbol, decimals, cap, { from: minter }); + this.token = await ERC20Capped.new(name, symbol, cap, { from: minter }); }); shouldBehaveLikeERC20Capped(minter, otherAccounts, cap); From 77fe2ff4831f0a1cf2bacbeacf4dc93c52bf4001 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Thu, 2 Apr 2020 14:59:07 -0300 Subject: [PATCH 06/10] Add changelog entry --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf46bfa593e..c103075c943 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,8 @@ * `ERC777`: removed `_callsTokensToSend` and `_callTokensReceived`. ([#2134](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2134)) * `EnumerableSet`: renamed `get` to `at`. ([#2151](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2151)) * `ERC165Checker`: functions no longer have a leading underscore. ([#2150](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2150)) + * `ERC20Detailed`: this contract was removed and its functionality merged into `ERC20`. ([#2161](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2161)) + * `ERC20`: added a constructor for `name` and `symbols`, `decimals` default to 18. ([#2161](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2161)) ## 2.5.0 (2020-02-04) From a4ba9f5b27916f6baa92d330ef1ad819828f4f9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Thu, 2 Apr 2020 15:01:07 -0300 Subject: [PATCH 07/10] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c103075c943..234bf83bffc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,7 +29,7 @@ * `EnumerableSet`: renamed `get` to `at`. ([#2151](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2151)) * `ERC165Checker`: functions no longer have a leading underscore. ([#2150](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2150)) * `ERC20Detailed`: this contract was removed and its functionality merged into `ERC20`. ([#2161](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2161)) - * `ERC20`: added a constructor for `name` and `symbols`, `decimals` default to 18. ([#2161](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2161)) + * `ERC20`: added a constructor for `name` and `symbol`, `decimals` default to 18. ([#2161](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2161)) ## 2.5.0 (2020-02-04) From 834c2a291977788599fc781053fef721728aa5cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Thu, 2 Apr 2020 15:01:22 -0300 Subject: [PATCH 08/10] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 234bf83bffc..7c8af187f2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,7 +29,7 @@ * `EnumerableSet`: renamed `get` to `at`. ([#2151](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2151)) * `ERC165Checker`: functions no longer have a leading underscore. ([#2150](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2150)) * `ERC20Detailed`: this contract was removed and its functionality merged into `ERC20`. ([#2161](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2161)) - * `ERC20`: added a constructor for `name` and `symbol`, `decimals` default to 18. ([#2161](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2161)) + * `ERC20`: added a constructor for `name` and `symbol`. `decimals` default to 18. ([#2161](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2161)) ## 2.5.0 (2020-02-04) From 907d734dcb384aa5aee6ad6ae3c18cd56666159f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Thu, 2 Apr 2020 15:19:45 -0300 Subject: [PATCH 09/10] Replace isConstructor for !isContract --- contracts/token/ERC20/ERC20.sol | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/contracts/token/ERC20/ERC20.sol b/contracts/token/ERC20/ERC20.sol index 1f6f574b721..3a094863508 100644 --- a/contracts/token/ERC20/ERC20.sol +++ b/contracts/token/ERC20/ERC20.sol @@ -3,6 +3,7 @@ pragma solidity ^0.6.0; import "../../GSN/Context.sol"; import "./IERC20.sol"; import "../../math/SafeMath.sol"; +import "../../utils/Address.sol"; /** * @dev Implementation of the {IERC20} interface. @@ -30,6 +31,7 @@ import "../../math/SafeMath.sol"; */ contract ERC20 is Context, IERC20 { using SafeMath for uint256; + using Address for address; mapping (address => uint256) private _balances; @@ -282,7 +284,7 @@ contract ERC20 is Context, IERC20 { * - this function can only be called from a constructor. */ function _setupDecimals(uint8 decimals_) internal { - require(_isConstructor(), "ERC20: decimals cannot be changed after construction"); + require(!address(this).isContract(), "ERC20: decimals cannot be changed after construction"); _decimals = decimals_; } @@ -301,18 +303,4 @@ contract ERC20 is Context, IERC20 { * To learn more about hooks, head to xref:ROOT:using-hooks.adoc[Using Hooks]. */ function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual { } - - // @dev Returns true if and only if the function is running in the constructor - function _isConstructor() private view returns (bool) { - // extcodesize checks the size of the code stored in an address, and - // address returns the current address. Since the code is still not - // deployed when running a constructor, any checks on its code size will - // yield zero, making it an effective way to detect if a contract is - // under construction or not. - address self = address(this); - uint256 cs; - // solhint-disable-next-line no-inline-assembly - assembly { cs := extcodesize(self) } - return cs == 0; - } } From cce340f6bfb5d3261ce6ca99d3e75e9db3700e25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Thu, 2 Apr 2020 15:21:49 -0300 Subject: [PATCH 10/10] Update CHANGELOG.md Co-Authored-By: Francisco Giordano --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c8af187f2b..3309ee853fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,7 +29,7 @@ * `EnumerableSet`: renamed `get` to `at`. ([#2151](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2151)) * `ERC165Checker`: functions no longer have a leading underscore. ([#2150](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2150)) * `ERC20Detailed`: this contract was removed and its functionality merged into `ERC20`. ([#2161](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2161)) - * `ERC20`: added a constructor for `name` and `symbol`. `decimals` default to 18. ([#2161](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2161)) + * `ERC20`: added a constructor for `name` and `symbol`. `decimals` now defaults to 18. ([#2161](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2161)) ## 2.5.0 (2020-02-04)