Add 'deploy-ready' token contracts (#2167)
* Add ERC20DeployReady * Add ERC721DeployReady * Improve docs * Fix linter errors * Rename DeployReady contracts to MinterPauser, add docs * Fix deploy ready docs * Minor doc adjustment
This commit is contained in:
85
contracts/deploy-ready/ERC20MinterPauser.sol
Normal file
85
contracts/deploy-ready/ERC20MinterPauser.sol
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
pragma solidity ^0.6.0;
|
||||||
|
|
||||||
|
import "../access/AccessControl.sol";
|
||||||
|
import "../GSN/Context.sol";
|
||||||
|
import "../token/ERC20/ERC20.sol";
|
||||||
|
import "../token/ERC20/ERC20Burnable.sol";
|
||||||
|
import "../token/ERC20/ERC20Pausable.sol";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev {ERC20} token, including:
|
||||||
|
*
|
||||||
|
* - ability for holders to burn (destroy) their tokens
|
||||||
|
* - a minter role that allows for token minting (creation)
|
||||||
|
* - a pauser role that allows to stop all token transfers
|
||||||
|
*
|
||||||
|
* This contract uses {AccessControl} to lock permissioned functions using the
|
||||||
|
* different roles - head to its documentation for details.
|
||||||
|
*
|
||||||
|
* The account that deploys the contract will be granted the minter role, the
|
||||||
|
* pauser role, and the default admin role, meaning it will be able to grant
|
||||||
|
* both the minter and pauser roles.
|
||||||
|
*/
|
||||||
|
contract ERC20MinterPauser is Context, AccessControl, ERC20Burnable, ERC20Pausable {
|
||||||
|
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
|
||||||
|
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Grants `DEFAULT_ADMIN_ROLE`, `MINTER_ROLE` and `PAUSER_ROLE` to the
|
||||||
|
* account that deploys the contract.
|
||||||
|
*
|
||||||
|
* See {ERC20-constructor}.
|
||||||
|
*/
|
||||||
|
constructor(string memory name, string memory symbol) public ERC20(name, symbol) {
|
||||||
|
_setupRole(DEFAULT_ADMIN_ROLE, _msgSender());
|
||||||
|
|
||||||
|
_setupRole(MINTER_ROLE, _msgSender());
|
||||||
|
_setupRole(PAUSER_ROLE, _msgSender());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Creates `amount` new tokens for `to`.
|
||||||
|
*
|
||||||
|
* See {ERC20-_mint}.
|
||||||
|
*
|
||||||
|
* Requirements:
|
||||||
|
*
|
||||||
|
* - the caller must have the `MINTER_ROLE`.
|
||||||
|
*/
|
||||||
|
function mint(address to, uint256 amount) public {
|
||||||
|
require(hasRole(MINTER_ROLE, _msgSender()), "ERC20MinterPauser: must have minter role to mint");
|
||||||
|
_mint(to, amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Pauses all token transfers.
|
||||||
|
*
|
||||||
|
* See {ERC20Pausable} and {Pausable-_pause}.
|
||||||
|
*
|
||||||
|
* Requirements:
|
||||||
|
*
|
||||||
|
* - the caller must have the `PAUSER_ROLE`.
|
||||||
|
*/
|
||||||
|
function pause() public {
|
||||||
|
require(hasRole(PAUSER_ROLE, _msgSender()), "ERC20MinterPauser: must have pauser role to pause");
|
||||||
|
_pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Unpauses all token transfers.
|
||||||
|
*
|
||||||
|
* See {ERC20Pausable} and {Pausable-_unpause}.
|
||||||
|
*
|
||||||
|
* Requirements:
|
||||||
|
*
|
||||||
|
* - the caller must have the `PAUSER_ROLE`.
|
||||||
|
*/
|
||||||
|
function unpause() public {
|
||||||
|
require(hasRole(PAUSER_ROLE, _msgSender()), "ERC20MinterPauser: must have pauser role to unpause");
|
||||||
|
_unpause();
|
||||||
|
}
|
||||||
|
|
||||||
|
function _beforeTokenTransfer(address from, address to, uint256 amount) internal override(ERC20, ERC20Pausable) {
|
||||||
|
super._beforeTokenTransfer(from, to, amount);
|
||||||
|
}
|
||||||
|
}
|
||||||
85
contracts/deploy-ready/ERC721MinterPauser.sol
Normal file
85
contracts/deploy-ready/ERC721MinterPauser.sol
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
pragma solidity ^0.6.0;
|
||||||
|
|
||||||
|
import "../access/AccessControl.sol";
|
||||||
|
import "../GSN/Context.sol";
|
||||||
|
import "../token/ERC721/ERC721.sol";
|
||||||
|
import "../token/ERC721/ERC721Burnable.sol";
|
||||||
|
import "../token/ERC721/ERC721Pausable.sol";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev {ERC721} token, including:
|
||||||
|
*
|
||||||
|
* - ability for holders to burn (destroy) their tokens
|
||||||
|
* - a minter role that allows for token minting (creation)
|
||||||
|
* - a pauser role that allows to stop all token transfers
|
||||||
|
*
|
||||||
|
* This contract uses {AccessControl} to lock permissioned functions using the
|
||||||
|
* different roles - head to its documentation for details.
|
||||||
|
*
|
||||||
|
* The account that deploys the contract will be granted the minter role, the
|
||||||
|
* pauser role, and the default admin role, meaning it will be able to grant
|
||||||
|
* both the minter and pauser roles.
|
||||||
|
*/
|
||||||
|
contract ERC721MinterPauser is Context, AccessControl, ERC721Burnable, ERC721Pausable {
|
||||||
|
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
|
||||||
|
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Grants `DEFAULT_ADMIN_ROLE`, `MINTER_ROLE` and `PAUSER_ROLE` to the
|
||||||
|
* account that deploys the contract.
|
||||||
|
*
|
||||||
|
* See {ERC721-constructor}.
|
||||||
|
*/
|
||||||
|
constructor(string memory name, string memory symbol) public ERC721(name, symbol) {
|
||||||
|
_setupRole(DEFAULT_ADMIN_ROLE, _msgSender());
|
||||||
|
|
||||||
|
_setupRole(MINTER_ROLE, _msgSender());
|
||||||
|
_setupRole(PAUSER_ROLE, _msgSender());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Creates the `tokenId` tokens for `to`.
|
||||||
|
*
|
||||||
|
* See {ERC721-_mint}.
|
||||||
|
*
|
||||||
|
* Requirements:
|
||||||
|
*
|
||||||
|
* - the caller must have the `MINTER_ROLE`.
|
||||||
|
*/
|
||||||
|
function mint(address to, uint256 tokenId) public {
|
||||||
|
require(hasRole(MINTER_ROLE, _msgSender()), "ERC721MinterPauser: must have minter role to mint");
|
||||||
|
_mint(to, tokenId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Pauses all token transfers.
|
||||||
|
*
|
||||||
|
* See {ERC721Pausable} and {Pausable-_pause}.
|
||||||
|
*
|
||||||
|
* Requirements:
|
||||||
|
*
|
||||||
|
* - the caller must have the `PAUSER_ROLE`.
|
||||||
|
*/
|
||||||
|
function pause() public {
|
||||||
|
require(hasRole(PAUSER_ROLE, _msgSender()), "ERC721MinterPauser: must have pauser role to pause");
|
||||||
|
_pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Unpauses all token transfers.
|
||||||
|
*
|
||||||
|
* See {ERC20Pausable} and {Pausable-_unpause}.
|
||||||
|
*
|
||||||
|
* Requirements:
|
||||||
|
*
|
||||||
|
* - the caller must have the `PAUSER_ROLE`.
|
||||||
|
*/
|
||||||
|
function unpause() public {
|
||||||
|
require(hasRole(PAUSER_ROLE, _msgSender()), "ERC721MinterPauser: must have pauser role to unpause");
|
||||||
|
_unpause();
|
||||||
|
}
|
||||||
|
|
||||||
|
function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal override(ERC721, ERC721Pausable) {
|
||||||
|
super._beforeTokenTransfer(from, to, tokenId);
|
||||||
|
}
|
||||||
|
}
|
||||||
13
contracts/deploy-ready/README.adoc
Normal file
13
contracts/deploy-ready/README.adoc
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
= Deploy Ready
|
||||||
|
|
||||||
|
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**.
|
||||||
|
|
||||||
|
They can be used as-is for quick prototyping and testing, but are **also suitable for production environments**.
|
||||||
|
|
||||||
|
TIP: Intermediate and advanced users can use these as starting points when writing their own contracts, extending them with custom functionality as they see fit.
|
||||||
|
|
||||||
|
== Tokens
|
||||||
|
|
||||||
|
{{ERC20MinterPauser}}
|
||||||
|
|
||||||
|
{{ERC721MinterPauser}}
|
||||||
103
test/deploy-ready/ERC20MinterPauser.test.js
Normal file
103
test/deploy-ready/ERC20MinterPauser.test.js
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
const { accounts, contract, web3 } = require('@openzeppelin/test-environment');
|
||||||
|
|
||||||
|
const { BN, constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers');
|
||||||
|
const { ZERO_ADDRESS } = constants;
|
||||||
|
|
||||||
|
const { expect } = require('chai');
|
||||||
|
|
||||||
|
const ERC20MinterPauser = contract.fromArtifact('ERC20MinterPauser');
|
||||||
|
|
||||||
|
describe('ERC20MinterPauser', function () {
|
||||||
|
const [ deployer, other ] = accounts;
|
||||||
|
|
||||||
|
const name = 'MinterPauserToken';
|
||||||
|
const symbol = 'DRT';
|
||||||
|
|
||||||
|
const amount = new BN('5000');
|
||||||
|
|
||||||
|
const DEFAULT_ADMIN_ROLE = '0x0000000000000000000000000000000000000000000000000000000000000000';
|
||||||
|
const MINTER_ROLE = web3.utils.soliditySha3('MINTER_ROLE');
|
||||||
|
const PAUSER_ROLE = web3.utils.soliditySha3('PAUSER_ROLE');
|
||||||
|
|
||||||
|
beforeEach(async function () {
|
||||||
|
this.token = await ERC20MinterPauser.new(name, symbol, { from: deployer });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('deployer has the default admin role', async function () {
|
||||||
|
expect(await this.token.getRoleMemberCount(DEFAULT_ADMIN_ROLE)).to.be.bignumber.equal('1');
|
||||||
|
expect(await this.token.getRoleMember(DEFAULT_ADMIN_ROLE, 0)).to.equal(deployer);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('deployer has the minter role', async function () {
|
||||||
|
expect(await this.token.getRoleMemberCount(MINTER_ROLE)).to.be.bignumber.equal('1');
|
||||||
|
expect(await this.token.getRoleMember(MINTER_ROLE, 0)).to.equal(deployer);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('deployer has the pauser role', 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(PAUSER_ROLE)).to.equal(DEFAULT_ADMIN_ROLE);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('minting', function () {
|
||||||
|
it('deployer can mint tokens', async function () {
|
||||||
|
const receipt = await this.token.mint(other, amount, { from: deployer });
|
||||||
|
expectEvent(receipt, 'Transfer', { from: ZERO_ADDRESS, to: other, value: amount });
|
||||||
|
|
||||||
|
expect(await this.token.balanceOf(other)).to.be.bignumber.equal(amount);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('other accounts cannot mint tokens', async function () {
|
||||||
|
await expectRevert(
|
||||||
|
this.token.mint(other, amount, { from: other }),
|
||||||
|
'ERC20MinterPauser: must have minter role to mint'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('pausing', function () {
|
||||||
|
it('deployer can pause', async function () {
|
||||||
|
const receipt = await this.token.pause({ from: deployer });
|
||||||
|
expectEvent(receipt, 'Paused', { account: deployer });
|
||||||
|
|
||||||
|
expect(await this.token.paused()).to.equal(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('deployer can unpause', async function () {
|
||||||
|
await this.token.pause({ from: deployer });
|
||||||
|
|
||||||
|
const receipt = await this.token.unpause({ from: deployer });
|
||||||
|
expectEvent(receipt, 'Unpaused', { account: deployer });
|
||||||
|
|
||||||
|
expect(await this.token.paused()).to.equal(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('cannot mint while paused', async function () {
|
||||||
|
await this.token.pause({ from: deployer });
|
||||||
|
|
||||||
|
await expectRevert(
|
||||||
|
this.token.mint(other, amount, { from: deployer }),
|
||||||
|
'ERC20Pausable: token transfer while paused'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('other accounts cannot pause', async function () {
|
||||||
|
await expectRevert(this.token.pause({ from: other }), 'ERC20MinterPauser: must have pauser role to pause');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('burning', function () {
|
||||||
|
it('holders can burn their tokens', async function () {
|
||||||
|
await this.token.mint(other, amount, { from: deployer });
|
||||||
|
|
||||||
|
const receipt = await this.token.burn(amount.subn(1), { from: other });
|
||||||
|
expectEvent(receipt, 'Transfer', { from: other, to: ZERO_ADDRESS, value: amount.subn(1) });
|
||||||
|
|
||||||
|
expect(await this.token.balanceOf(other)).to.be.bignumber.equal('1');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
106
test/deploy-ready/ERC721MinterPauser.test.js
Normal file
106
test/deploy-ready/ERC721MinterPauser.test.js
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
const { accounts, contract, web3 } = require('@openzeppelin/test-environment');
|
||||||
|
|
||||||
|
const { BN, constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers');
|
||||||
|
const { ZERO_ADDRESS } = constants;
|
||||||
|
|
||||||
|
const { expect } = require('chai');
|
||||||
|
|
||||||
|
const ERC721MinterPauser = contract.fromArtifact('ERC721MinterPauser');
|
||||||
|
|
||||||
|
describe('ERC721MinterPauser', function () {
|
||||||
|
const [ deployer, other ] = accounts;
|
||||||
|
|
||||||
|
const name = 'MinterPauserToken';
|
||||||
|
const symbol = 'DRT';
|
||||||
|
|
||||||
|
const tokenId = new BN('1337');
|
||||||
|
|
||||||
|
const DEFAULT_ADMIN_ROLE = '0x0000000000000000000000000000000000000000000000000000000000000000';
|
||||||
|
const MINTER_ROLE = web3.utils.soliditySha3('MINTER_ROLE');
|
||||||
|
const PAUSER_ROLE = web3.utils.soliditySha3('PAUSER_ROLE');
|
||||||
|
|
||||||
|
beforeEach(async function () {
|
||||||
|
this.token = await ERC721MinterPauser.new(name, symbol, { from: deployer });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('deployer has the default admin role', async function () {
|
||||||
|
expect(await this.token.getRoleMemberCount(DEFAULT_ADMIN_ROLE)).to.be.bignumber.equal('1');
|
||||||
|
expect(await this.token.getRoleMember(DEFAULT_ADMIN_ROLE, 0)).to.equal(deployer);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('deployer has the minter role', async function () {
|
||||||
|
expect(await this.token.getRoleMemberCount(MINTER_ROLE)).to.be.bignumber.equal('1');
|
||||||
|
expect(await this.token.getRoleMember(MINTER_ROLE, 0)).to.equal(deployer);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('deployer has the pauser role', 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(PAUSER_ROLE)).to.equal(DEFAULT_ADMIN_ROLE);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('minting', function () {
|
||||||
|
it('deployer can mint tokens', async function () {
|
||||||
|
const receipt = await this.token.mint(other, tokenId, { from: deployer });
|
||||||
|
expectEvent(receipt, 'Transfer', { from: ZERO_ADDRESS, to: other, tokenId });
|
||||||
|
|
||||||
|
expect(await this.token.balanceOf(other)).to.be.bignumber.equal('1');
|
||||||
|
expect(await this.token.ownerOf(tokenId)).to.equal(other);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('other accounts cannot mint tokens', async function () {
|
||||||
|
await expectRevert(
|
||||||
|
this.token.mint(other, tokenId, { from: other }),
|
||||||
|
'ERC721MinterPauser: must have minter role to mint'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('pausing', function () {
|
||||||
|
it('deployer can pause', async function () {
|
||||||
|
const receipt = await this.token.pause({ from: deployer });
|
||||||
|
expectEvent(receipt, 'Paused', { account: deployer });
|
||||||
|
|
||||||
|
expect(await this.token.paused()).to.equal(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('deployer can unpause', async function () {
|
||||||
|
await this.token.pause({ from: deployer });
|
||||||
|
|
||||||
|
const receipt = await this.token.unpause({ from: deployer });
|
||||||
|
expectEvent(receipt, 'Unpaused', { account: deployer });
|
||||||
|
|
||||||
|
expect(await this.token.paused()).to.equal(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('cannot mint while paused', async function () {
|
||||||
|
await this.token.pause({ from: deployer });
|
||||||
|
|
||||||
|
await expectRevert(
|
||||||
|
this.token.mint(other, tokenId, { from: deployer }),
|
||||||
|
'ERC721Pausable: token transfer while paused'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('other accounts cannot pause', async function () {
|
||||||
|
await expectRevert(this.token.pause({ from: other }), 'ERC721MinterPauser: must have pauser role to pause');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('burning', function () {
|
||||||
|
it('holders can burn their tokens', async function () {
|
||||||
|
await this.token.mint(other, tokenId, { from: deployer });
|
||||||
|
|
||||||
|
const receipt = await this.token.burn(tokenId, { from: other });
|
||||||
|
|
||||||
|
expectEvent(receipt, 'Transfer', { from: other, to: ZERO_ADDRESS, tokenId });
|
||||||
|
|
||||||
|
expect(await this.token.balanceOf(other)).to.be.bignumber.equal('0');
|
||||||
|
expect(await this.token.totalSupply()).to.be.bignumber.equal('0');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user