Unverified Commit 715ec806 by Nicolás Venturo Committed by GitHub

ERC721 deploy ready fixes (#2192)

* Add baseURI to ERC721MinterPauser constructor

* Add tokenURI to mint

* Remove tokenId and tokenURI from ERC721 deploy ready

* Rename ERC721MinterPauser to ERC721MinterAutoID, remove Pausable mechanisms

* Restore pausing to ERC721

* Rename deploy ready to presets

* Rename ERC721 preset
parent 5bb8d024
...@@ -16,11 +16,11 @@ import "../token/ERC20/ERC20Pausable.sol"; ...@@ -16,11 +16,11 @@ import "../token/ERC20/ERC20Pausable.sol";
* This contract uses {AccessControl} to lock permissioned functions using the * This contract uses {AccessControl} to lock permissioned functions using the
* different roles - head to its documentation for details. * different roles - head to its documentation for details.
* *
* The account that deploys the contract will be granted the minter role, the * The account that deploys the contract will be granted the minter and pauser
* pauser role, and the default admin role, meaning it will be able to grant * roles, as well as the default admin role, which will let it grant both minter
* both the minter and pauser roles. * and pauser roles to aother accounts
*/ */
contract ERC20MinterPauser is Context, AccessControl, ERC20Burnable, ERC20Pausable { contract ERC20PresetMinterPauser is Context, AccessControl, ERC20Burnable, ERC20Pausable {
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
...@@ -47,7 +47,7 @@ contract ERC20MinterPauser is Context, AccessControl, ERC20Burnable, ERC20Pausab ...@@ -47,7 +47,7 @@ contract ERC20MinterPauser is Context, AccessControl, ERC20Burnable, ERC20Pausab
* - the caller must have the `MINTER_ROLE`. * - the caller must have the `MINTER_ROLE`.
*/ */
function mint(address to, uint256 amount) public { function mint(address to, uint256 amount) public {
require(hasRole(MINTER_ROLE, _msgSender()), "ERC20MinterPauser: must have minter role to mint"); require(hasRole(MINTER_ROLE, _msgSender()), "ERC20PresetMinterPauser: must have minter role to mint");
_mint(to, amount); _mint(to, amount);
} }
...@@ -61,7 +61,7 @@ contract ERC20MinterPauser is Context, AccessControl, ERC20Burnable, ERC20Pausab ...@@ -61,7 +61,7 @@ contract ERC20MinterPauser is Context, AccessControl, ERC20Burnable, ERC20Pausab
* - the caller must have the `PAUSER_ROLE`. * - the caller must have the `PAUSER_ROLE`.
*/ */
function pause() public { function pause() public {
require(hasRole(PAUSER_ROLE, _msgSender()), "ERC20MinterPauser: must have pauser role to pause"); require(hasRole(PAUSER_ROLE, _msgSender()), "ERC20PresetMinterPauser: must have pauser role to pause");
_pause(); _pause();
} }
...@@ -75,7 +75,7 @@ contract ERC20MinterPauser is Context, AccessControl, ERC20Burnable, ERC20Pausab ...@@ -75,7 +75,7 @@ contract ERC20MinterPauser is Context, AccessControl, ERC20Burnable, ERC20Pausab
* - the caller must have the `PAUSER_ROLE`. * - the caller must have the `PAUSER_ROLE`.
*/ */
function unpause() public { function unpause() public {
require(hasRole(PAUSER_ROLE, _msgSender()), "ERC20MinterPauser: must have pauser role to unpause"); require(hasRole(PAUSER_ROLE, _msgSender()), "ERC20PresetMinterPauser: must have pauser role to unpause");
_unpause(); _unpause();
} }
......
...@@ -2,6 +2,7 @@ pragma solidity ^0.6.0; ...@@ -2,6 +2,7 @@ pragma solidity ^0.6.0;
import "../access/AccessControl.sol"; import "../access/AccessControl.sol";
import "../GSN/Context.sol"; import "../GSN/Context.sol";
import "../utils/Counters.sol";
import "../token/ERC721/ERC721.sol"; import "../token/ERC721/ERC721.sol";
import "../token/ERC721/ERC721Burnable.sol"; import "../token/ERC721/ERC721Burnable.sol";
import "../token/ERC721/ERC721Pausable.sol"; import "../token/ERC721/ERC721Pausable.sol";
...@@ -12,33 +13,43 @@ import "../token/ERC721/ERC721Pausable.sol"; ...@@ -12,33 +13,43 @@ import "../token/ERC721/ERC721Pausable.sol";
* - ability for holders to burn (destroy) their tokens * - ability for holders to burn (destroy) their tokens
* - a minter role that allows for token minting (creation) * - a minter role that allows for token minting (creation)
* - a pauser role that allows to stop all token transfers * - a pauser role that allows to stop all token transfers
* - token ID and URI autogeneration
* *
* This contract uses {AccessControl} to lock permissioned functions using the * This contract uses {AccessControl} to lock permissioned functions using the
* different roles - head to its documentation for details. * different roles - head to its documentation for details.
* *
* The account that deploys the contract will be granted the minter role, the * The account that deploys the contract will be granted the minter and pauser
* pauser role, and the default admin role, meaning it will be able to grant * roles, as well as the default admin role, which will let it grant both minter
* both the minter and pauser roles. * and pauser roles to aother accounts
*/ */
contract ERC721MinterPauser is Context, AccessControl, ERC721Burnable, ERC721Pausable { contract ERC721PresetMinterPauserAutoId is Context, AccessControl, ERC721Burnable, ERC721Pausable {
using Counters for Counters.Counter;
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
Counters.Counter private _tokenIdTracker;
/** /**
* @dev Grants `DEFAULT_ADMIN_ROLE`, `MINTER_ROLE` and `PAUSER_ROLE` to the * @dev Grants `DEFAULT_ADMIN_ROLE` and `MINTER_ROLE`to the account that
* account that deploys the contract. * deploys the contract.
* *
* See {ERC721-constructor}. * Token URIs will be autogenerated based on `baseURI` and their token IDs.
* See {ERC721-tokenURI}.
*/ */
constructor(string memory name, string memory symbol) public ERC721(name, symbol) { constructor(string memory name, string memory symbol, string memory baseURI) public ERC721(name, symbol) {
_setupRole(DEFAULT_ADMIN_ROLE, _msgSender()); _setupRole(DEFAULT_ADMIN_ROLE, _msgSender());
_setupRole(MINTER_ROLE, _msgSender()); _setupRole(MINTER_ROLE, _msgSender());
_setupRole(PAUSER_ROLE, _msgSender()); _setupRole(PAUSER_ROLE, _msgSender());
_setBaseURI(baseURI);
} }
/** /**
* @dev Creates the `tokenId` tokens for `to`. * @dev Creates a new token for `to`. Its token ID will be automatically
* assigned (and available on the emitted {Transfer} event), and the token
* URI autogenerated based on the base URI passed at construction.
* *
* See {ERC721-_mint}. * See {ERC721-_mint}.
* *
...@@ -46,9 +57,13 @@ contract ERC721MinterPauser is Context, AccessControl, ERC721Burnable, ERC721Pau ...@@ -46,9 +57,13 @@ contract ERC721MinterPauser is Context, AccessControl, ERC721Burnable, ERC721Pau
* *
* - the caller must have the `MINTER_ROLE`. * - the caller must have the `MINTER_ROLE`.
*/ */
function mint(address to, uint256 tokenId) public { function mint(address to) public {
require(hasRole(MINTER_ROLE, _msgSender()), "ERC721MinterPauser: must have minter role to mint"); require(hasRole(MINTER_ROLE, _msgSender()), "ERC721PresetMinterPauserAutoId: must have minter role to mint");
_mint(to, tokenId);
// We can just use balanceOf to create the new tokenId because tokens
// can be burned (destroyed), so we need a separate counter.
_mint(to, _tokenIdTracker.current());
_tokenIdTracker.increment();
} }
/** /**
...@@ -61,21 +76,21 @@ contract ERC721MinterPauser is Context, AccessControl, ERC721Burnable, ERC721Pau ...@@ -61,21 +76,21 @@ contract ERC721MinterPauser is Context, AccessControl, ERC721Burnable, ERC721Pau
* - the caller must have the `PAUSER_ROLE`. * - the caller must have the `PAUSER_ROLE`.
*/ */
function pause() public { function pause() public {
require(hasRole(PAUSER_ROLE, _msgSender()), "ERC721MinterPauser: must have pauser role to pause"); require(hasRole(PAUSER_ROLE, _msgSender()), "ERC721PresetMinterPauserAutoId: must have pauser role to pause");
_pause(); _pause();
} }
/** /**
* @dev Unpauses all token transfers. * @dev Unpauses all token transfers.
* *
* See {ERC20Pausable} and {Pausable-_unpause}. * See {ERC721Pausable} and {Pausable-_unpause}.
* *
* Requirements: * Requirements:
* *
* - the caller must have the `PAUSER_ROLE`. * - the caller must have the `PAUSER_ROLE`.
*/ */
function unpause() public { function unpause() public {
require(hasRole(PAUSER_ROLE, _msgSender()), "ERC721MinterPauser: must have pauser role to unpause"); require(hasRole(PAUSER_ROLE, _msgSender()), "ERC721PresetMinterPauserAutoId: must have pauser role to unpause");
_unpause(); _unpause();
} }
......
= Deploy Ready = Presets
These contracts integrate different Ethereum standards (ERCs) with custom extensions and modules, showcasing common configurations that are ready to deploy **without having to write any Solidity code**. These contracts integrate different Ethereum standards (ERCs) with custom extensions and modules, showcasing common configurations that are ready to deploy **without having to write any Solidity code**.
...@@ -8,6 +8,6 @@ TIP: Intermediate and advanced users can use these as starting points when writi ...@@ -8,6 +8,6 @@ TIP: Intermediate and advanced users can use these as starting points when writi
== Tokens == Tokens
{{ERC20MinterPauser}} {{ERC20PresetMinterPauser}}
{{ERC721MinterPauser}} {{ERC721PresetMinterPauserAutoId}}
...@@ -134,11 +134,33 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Enumerable ...@@ -134,11 +134,33 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Enumerable
/** /**
* @dev Returns the URI for a given token ID. May return an empty string. * @dev Returns the URI for a given token ID. May return an empty string.
* *
* If no base URI was set (via {_setBaseURI}), return the token ID's URI. * If a base URI is set (via {_setBaseURI}), it is added as a prefix to the
* If a base URI was set, it will be added as a prefix to the token ID's URI, * token's own URI (via {_setTokenURI}).
* or to the token ID itself, if no URI is set for that token ID.
* *
* Reverts if the token ID does not exist. * If there is a base URI but no token URI, the token's ID will be used as
* its URI when appending it to the base URI. This pattern for autogenerated
* token URIs can lead to large gas savings.
*
* .Examples
* |===
* |`_setBaseURI()` |`_setTokenURI()` |`tokenURI()`
* | ""
* | ""
* | ""
* | ""
* | "token.uri/123"
* | "token.uri/123"
* | "token.uri/"
* | "123"
* | "token.uri/123"
* | "token.uri/"
* | ""
* | "token.uri/<tokenId>"
* |===
*
* Requirements:
*
* - `tokenId` must exist.
*/ */
function tokenURI(uint256 tokenId) public view override returns (string memory) { function tokenURI(uint256 tokenId) public view override returns (string memory) {
require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token"); require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");
......
...@@ -5,9 +5,9 @@ const { ZERO_ADDRESS } = constants; ...@@ -5,9 +5,9 @@ const { ZERO_ADDRESS } = constants;
const { expect } = require('chai'); const { expect } = require('chai');
const ERC20MinterPauser = contract.fromArtifact('ERC20MinterPauser'); const ERC20PresetMinterPauser = contract.fromArtifact('ERC20PresetMinterPauser');
describe('ERC20MinterPauser', function () { describe('ERC20PresetMinterPauser', function () {
const [ deployer, other ] = accounts; const [ deployer, other ] = accounts;
const name = 'MinterPauserToken'; const name = 'MinterPauserToken';
...@@ -20,7 +20,7 @@ describe('ERC20MinterPauser', function () { ...@@ -20,7 +20,7 @@ describe('ERC20MinterPauser', function () {
const PAUSER_ROLE = web3.utils.soliditySha3('PAUSER_ROLE'); const PAUSER_ROLE = web3.utils.soliditySha3('PAUSER_ROLE');
beforeEach(async function () { beforeEach(async function () {
this.token = await ERC20MinterPauser.new(name, symbol, { from: deployer }); this.token = await ERC20PresetMinterPauser.new(name, symbol, { from: deployer });
}); });
it('deployer has the default admin role', async function () { it('deployer has the default admin role', async function () {
...@@ -54,7 +54,7 @@ describe('ERC20MinterPauser', function () { ...@@ -54,7 +54,7 @@ describe('ERC20MinterPauser', function () {
it('other accounts cannot mint tokens', async function () { it('other accounts cannot mint tokens', async function () {
await expectRevert( await expectRevert(
this.token.mint(other, amount, { from: other }), this.token.mint(other, amount, { from: other }),
'ERC20MinterPauser: must have minter role to mint' 'ERC20PresetMinterPauser: must have minter role to mint'
); );
}); });
}); });
...@@ -86,7 +86,7 @@ describe('ERC20MinterPauser', function () { ...@@ -86,7 +86,7 @@ describe('ERC20MinterPauser', function () {
}); });
it('other accounts cannot pause', async function () { it('other accounts cannot pause', async function () {
await expectRevert(this.token.pause({ from: other }), 'ERC20MinterPauser: must have pauser role to pause'); await expectRevert(this.token.pause({ from: other }), 'ERC20PresetMinterPauser: must have pauser role to pause');
}); });
}); });
......
...@@ -5,22 +5,32 @@ const { ZERO_ADDRESS } = constants; ...@@ -5,22 +5,32 @@ const { ZERO_ADDRESS } = constants;
const { expect } = require('chai'); const { expect } = require('chai');
const ERC721MinterPauser = contract.fromArtifact('ERC721MinterPauser'); const ERC721PresetMinterPauserAutoId = contract.fromArtifact('ERC721PresetMinterPauserAutoId');
describe('ERC721MinterPauser', function () { describe('ERC721PresetMinterPauserAutoId', function () {
const [ deployer, other ] = accounts; const [ deployer, other ] = accounts;
const name = 'MinterPauserToken'; const name = 'MinterAutoIDToken';
const symbol = 'DRT'; const symbol = 'MAIT';
const baseURI = 'my.app/';
const tokenId = new BN('1337');
const DEFAULT_ADMIN_ROLE = '0x0000000000000000000000000000000000000000000000000000000000000000'; const DEFAULT_ADMIN_ROLE = '0x0000000000000000000000000000000000000000000000000000000000000000';
const MINTER_ROLE = web3.utils.soliditySha3('MINTER_ROLE'); const MINTER_ROLE = web3.utils.soliditySha3('MINTER_ROLE');
const PAUSER_ROLE = web3.utils.soliditySha3('PAUSER_ROLE');
beforeEach(async function () { beforeEach(async function () {
this.token = await ERC721MinterPauser.new(name, symbol, { from: deployer }); this.token = await ERC721PresetMinterPauserAutoId.new(name, symbol, baseURI, { from: deployer });
});
it('token has correct name', async function () {
expect(await this.token.name()).to.equal(name);
});
it('token has correct symbol', async function () {
expect(await this.token.symbol()).to.equal(symbol);
});
it('token has correct base URI', async function () {
expect(await this.token.baseURI()).to.equal(baseURI);
}); });
it('deployer has the default admin role', async function () { it('deployer has the default admin role', async function () {
...@@ -33,29 +43,27 @@ describe('ERC721MinterPauser', function () { ...@@ -33,29 +43,27 @@ describe('ERC721MinterPauser', function () {
expect(await this.token.getRoleMember(MINTER_ROLE, 0)).to.equal(deployer); expect(await this.token.getRoleMember(MINTER_ROLE, 0)).to.equal(deployer);
}); });
it('deployer has the pauser role', async function () { it('minter role admin is the default admin', async function () {
expect(await this.token.getRoleMemberCount(PAUSER_ROLE)).to.be.bignumber.equal('1');
expect(await this.token.getRoleMember(PAUSER_ROLE, 0)).to.equal(deployer);
});
it('minter and pauser role admin is the default admin', async function () {
expect(await this.token.getRoleAdmin(MINTER_ROLE)).to.equal(DEFAULT_ADMIN_ROLE); expect(await this.token.getRoleAdmin(MINTER_ROLE)).to.equal(DEFAULT_ADMIN_ROLE);
expect(await this.token.getRoleAdmin(PAUSER_ROLE)).to.equal(DEFAULT_ADMIN_ROLE);
}); });
describe('minting', function () { describe('minting', function () {
it('deployer can mint tokens', async function () { it('deployer can mint tokens', async function () {
const receipt = await this.token.mint(other, tokenId, { from: deployer }); const tokenId = new BN('0');
const receipt = await this.token.mint(other, { from: deployer });
expectEvent(receipt, 'Transfer', { from: ZERO_ADDRESS, to: other, tokenId }); expectEvent(receipt, 'Transfer', { from: ZERO_ADDRESS, to: other, tokenId });
expect(await this.token.balanceOf(other)).to.be.bignumber.equal('1'); expect(await this.token.balanceOf(other)).to.be.bignumber.equal('1');
expect(await this.token.ownerOf(tokenId)).to.equal(other); expect(await this.token.ownerOf(tokenId)).to.equal(other);
expect(await this.token.tokenURI(tokenId)).to.equal(baseURI + tokenId);
}); });
it('other accounts cannot mint tokens', async function () { it('other accounts cannot mint tokens', async function () {
await expectRevert( await expectRevert(
this.token.mint(other, tokenId, { from: other }), this.token.mint(other, { from: other }),
'ERC721MinterPauser: must have minter role to mint' 'ERC721PresetMinterPauserAutoId: must have minter role to mint'
); );
}); });
}); });
...@@ -81,19 +89,24 @@ describe('ERC721MinterPauser', function () { ...@@ -81,19 +89,24 @@ describe('ERC721MinterPauser', function () {
await this.token.pause({ from: deployer }); await this.token.pause({ from: deployer });
await expectRevert( await expectRevert(
this.token.mint(other, tokenId, { from: deployer }), this.token.mint(other, { from: deployer }),
'ERC721Pausable: token transfer while paused' 'ERC721Pausable: token transfer while paused'
); );
}); });
it('other accounts cannot pause', async function () { it('other accounts cannot pause', async function () {
await expectRevert(this.token.pause({ from: other }), 'ERC721MinterPauser: must have pauser role to pause'); await expectRevert(
this.token.pause({ from: other }),
'ERC721PresetMinterPauserAutoId: must have pauser role to pause'
);
}); });
}); });
describe('burning', function () { describe('burning', function () {
it('holders can burn their tokens', async function () { it('holders can burn their tokens', async function () {
await this.token.mint(other, tokenId, { from: deployer }); const tokenId = new BN('0');
await this.token.mint(other, { from: deployer });
const receipt = await this.token.burn(tokenId, { from: other }); const receipt = await this.token.burn(tokenId, { from: other });
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment