diff --git a/contracts/examples/StandaloneERC20.sol b/contracts/examples/StandaloneERC20.sol new file mode 100644 index 000000000..ae760c9f3 --- /dev/null +++ b/contracts/examples/StandaloneERC20.sol @@ -0,0 +1,60 @@ +pragma solidity ^0.4.24; + +import "zos-lib/contracts/Initializable.sol"; +import "../token/ERC20/ERC20Detailed.sol"; +import "../token/ERC20/ERC20Mintable.sol"; +import "../token/ERC20/ERC20Pausable.sol"; + + +/** + * @title Standard ERC20 token, with minting and pause functionality. + * + */ +contract StandaloneERC20 is Initializable, ERC20Detailed, ERC20Mintable, ERC20Pausable { + function initialize(string name, string symbol, uint8 decimals, uint256 initialSupply, address initialHolder, address[] minters, address[] pausers) public initializer { + require(initialSupply > 0); + + ERC20Detailed.initialize(name, symbol, decimals); + + // Mint the initial supply + _mint(initialHolder, initialSupply); + + // Initialize the minter and pauser roles, and renounce them + ERC20Mintable.initialize(address(this)); + renounceMinter(); + + ERC20Pausable.initialize(address(this)); + renouncePauser(); + + // Add the requested minters and pausers (this can be done after renouncing since + // these are the internal calls) + for (uint256 i = 0; i < minters.length; ++i) { + _addMinter(minters[i]); + } + + for (i = 0; i < pausers.length; ++i) { + _addPauser(pausers[i]); + } + } + + function initialize(string name, string symbol, uint8 decimals, address[] minters, address[] pausers) public initializer { + ERC20Detailed.initialize(name, symbol, decimals); + + // Initialize the minter and pauser roles, and renounce them + ERC20Mintable.initialize(address(this)); + renounceMinter(); + + ERC20Pausable.initialize(address(this)); + renouncePauser(); + + // Add the requested minters and pausers (this can be done after renouncing since + // these are the internal calls) + for (uint256 i = 0; i < minters.length; ++i) { + _addMinter(minters[i]); + } + + for (i = 0; i < pausers.length; ++i) { + _addPauser(pausers[i]); + } + } +} diff --git a/contracts/examples/StandaloneERC721.sol b/contracts/examples/StandaloneERC721.sol new file mode 100644 index 000000000..4640b3ed5 --- /dev/null +++ b/contracts/examples/StandaloneERC721.sol @@ -0,0 +1,38 @@ +pragma solidity ^0.4.24; + +import "zos-lib/contracts/Initializable.sol"; +import "../token/ERC721/ERC721.sol"; +import "../token/ERC721/ERC721Enumerable.sol"; +import "../token/ERC721/ERC721Metadata.sol"; +import "../token/ERC721/ERC721MetadataMintable.sol"; +import "../token/ERC721/ERC721Pausable.sol"; + + +/** + * @title Standard ERC721 token, with minting and pause functionality. + * + */ +contract StandaloneERC721 is Initializable, ERC721, ERC721Enumerable, ERC721Metadata, ERC721MetadataMintable, ERC721Pausable { + function initialize(string name, string symbol, address[] minters, address[] pausers) public initializer { + ERC721.initialize(); + ERC721Enumerable.initialize(); + ERC721Metadata.initialize(name, symbol); + + // Initialize the minter and pauser roles, and renounce them + ERC721MetadataMintable.initialize(address(this)); + renounceMinter(); + + ERC721Pausable.initialize(address(this)); + renouncePauser(); + + // Add the requested minters and pausers (this can be done after renouncing since + // these are the internal calls) + for (uint256 i = 0; i < minters.length; ++i) { + _addMinter(minters[i]); + } + + for (i = 0; i < pausers.length; ++i) { + _addPauser(pausers[i]); + } + } +} diff --git a/contracts/examples/StandardToken.sol b/contracts/examples/StandardToken.sol new file mode 100644 index 000000000..1e54d6667 --- /dev/null +++ b/contracts/examples/StandardToken.sol @@ -0,0 +1,39 @@ +pragma solidity ^0.4.24; + +import "zos-lib/contracts/Initializable.sol"; +import "../token/ERC20/ERC20Detailed.sol"; +import "../token/ERC20/ERC20Mintable.sol"; +import "../token/ERC20/ERC20Pausable.sol"; + + +/** + * @title Standard ERC20 token, with minting and pause functionality. + * + */ +contract StandardToken is Initializable, ERC20Detailed, ERC20Mintable, ERC20Pausable { + function initialize(string name, string symbol, uint8 decimals, uint256 initialSupply, address initialHolder, address[] minters, address[] pausers) public initializer { + ERC20Detailed.initialize(name, symbol, decimals); + + // Mint the initial supply + if (initialSupply > 0) { // To allow passing a null address when not doing any initial supply + _mint(initialHolder, initialSupply); + } + + // Initialize the minter and pauser roles, and renounce them + ERC20Mintable.initialize(address(this)); + renounceMinter(); + + ERC20Pausable.initialize(address(this)); + renouncePauser(); + + // Add the requested minters and pausers (this can be done after renouncing since + // these are the internal calls) + for (uint256 i = 0; i < minters.length; ++i) { + _addMinter(minters[i]); + } + + for (i = 0; i < pausers.length; ++i) { + _addPauser(pausers[i]); + } + } +} diff --git a/test/examples/StandaloneERC20.test.js b/test/examples/StandaloneERC20.test.js new file mode 100644 index 000000000..bc803e723 --- /dev/null +++ b/test/examples/StandaloneERC20.test.js @@ -0,0 +1,158 @@ +const encodeCall = require('zos-lib/lib/helpers/encodeCall').default; +const { shouldBehaveLikeERC20Mintable } = require('../token/ERC20/behaviors/ERC20Mintable.behavior'); +const { assertRevert } = require('../helpers/assertRevert'); + +const BigNumber = web3.BigNumber; + +const should = require('chai') + .use(require('chai-bignumber')(BigNumber)) + .should(); + +const StandaloneERC20 = artifacts.require('StandaloneERC20'); + +contract('StandaloneERC20', function ([_, deployer, initialHolder, minterA, minterB, pauserA, pauserB, anyone, ...otherAccounts]) { + const name = "StandaloneERC20"; + const symbol = "SAERC20"; + const decimals = 18; + + const initialSupply = 300; + + const minters = [minterA, minterB]; + const pausers = [pauserA, pauserB]; + + const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; + + beforeEach(async function () { + this.token = await StandaloneERC20.new({ from: deployer }); + }); + + async function initializeFull(token, name, symbol, decimals, initialSupply, initialHolder, minters, pausers, from) { + const callData = encodeCall('initialize', ['string', 'string', 'uint8', 'uint256', 'address', 'address[]', 'address[]'], [name, symbol, decimals, initialSupply, initialHolder, minters, pausers]); + await token.sendTransaction({ data: callData, from }); + } + + async function initializePartial(token, name, symbol, decimals, minters, pausers, from) { + const callData = encodeCall('initialize', ['string', 'string', 'uint8', 'address[]', 'address[]'], [name, symbol, decimals, minters, pausers]); + await token.sendTransaction({ data: callData, from }); + } + + describe('with all arguments', function () { + it('reverts if initial balance is zero', async function () { + await assertRevert(initializeFull(this.token, name, symbol, decimals, 0, ZERO_ADDRESS, minters, pausers, deployer)); + }); + + it('can be created with no minters', async function () { + await initializeFull(this.token, name, symbol, decimals, initialSupply, initialHolder, [], pausers, deployer); + + for (const minter of minters) { + (await this.token.isMinter(minter)).should.equal(false); + } + }); + + it('can be created with no pausers', async function () { + await initializeFull(this.token, name, symbol, decimals, initialSupply, initialHolder, minters, [], deployer); + + for (const pauser of pausers) { + (await this.token.isPauser(pauser)).should.equal(false); + } + }); + + context('with token', async function () { + beforeEach(async function () { + await initializeFull(this.token, name, symbol, decimals, initialSupply, initialHolder, minters, pausers, deployer); + }); + + it('initializes metadata', async function () { + (await this.token.name()).should.equal(name); + (await this.token.symbol()).should.equal(symbol); + (await this.token.decimals()).should.be.bignumber.equal(decimals); + }); + + it('assigns the initial supply to the initial holder', async function () { + (await this.token.balanceOf(initialHolder)).should.be.bignumber.equal(initialSupply); + }); + + describe('mintability', function () { + beforeEach(function () { + this.contract = this.token; + }); + + it('all minters have the minter role', async function () { + for (const minter of minters) { + (await this.token.isMinter(minter)).should.equal(true); + } + }); + + shouldBehaveLikeERC20Mintable(minterA, otherAccounts); + }); + + describe('pausability', function () { + beforeEach(function () { + this.contract = this.token; + }); + + it('all pausers have the minter role', async function () { + for (const pauser of pausers) { + (await this.token.isPauser(pauser)).should.equal(true); + } + }); + }); + }); + }); + + describe('without initial balance', function () { + it('can be created with no minters', async function () { + await initializePartial(this.token, name, symbol, decimals, [], pausers, deployer); + + for (const minter of minters) { + (await this.token.isMinter(minter)).should.equal(false); + } + }); + + it('can be created with no pausers', async function () { + await initializePartial(this.token, name, symbol, decimals, minters, [], deployer); + + for (const pauser of pausers) { + (await this.token.isPauser(pauser)).should.equal(false); + } + }); + + context('with token', async function () { + beforeEach(async function () { + await initializePartial(this.token, name, symbol, decimals, minters, pausers, deployer); + }); + + it('initializes metadata', async function () { + (await this.token.name()).should.equal(name); + (await this.token.symbol()).should.equal(symbol); + (await this.token.decimals()).should.be.bignumber.equal(decimals); + }); + + describe('mintability', function () { + beforeEach(function () { + this.contract = this.token; + }); + + it('all minters have the minter role', async function () { + for (const minter of minters) { + (await this.token.isMinter(minter)).should.equal(true); + } + }); + + shouldBehaveLikeERC20Mintable(minterA, otherAccounts); + }); + + describe('pausability', function () { + beforeEach(function () { + this.contract = this.token; + }); + + it('all pausers have the minter role', async function () { + for (const pauser of pausers) { + (await this.token.isPauser(pauser)).should.equal(true); + } + }); + }); + }); + }); +}); diff --git a/test/examples/StandaloneERC721.test.js b/test/examples/StandaloneERC721.test.js new file mode 100644 index 000000000..37d39f82c --- /dev/null +++ b/test/examples/StandaloneERC721.test.js @@ -0,0 +1,78 @@ +const encodeCall = require('zos-lib/lib/helpers/encodeCall').default; +const { assertRevert } = require('../helpers/assertRevert'); + +const BigNumber = web3.BigNumber; + +const should = require('chai') + .use(require('chai-bignumber')(BigNumber)) + .should(); + +const StandaloneERC721 = artifacts.require('StandaloneERC721'); + +contract('StandaloneERC721', function ([_, deployer, minterA, minterB, pauserA, pauserB, anyone, ...otherAccounts]) { + const name = "StandaloneERC721"; + const symbol = "SAERC721"; + + const minters = [minterA, minterB]; + const pausers = [pauserA, pauserB]; + + beforeEach(async function () { + this.token = await StandaloneERC721.new({ from: deployer }); + }); + + async function initialize(token, name, symbol, minters, pausers, from) { + const callData = encodeCall('initialize', ['string', 'string', 'address[]', 'address[]'], [name, symbol, minters, pausers]); + await token.sendTransaction({ data: callData, from }); + } + + it('can be created with no minters', async function () { + await initialize(this.token, name, symbol, [], pausers, deployer); + + for (const minter of minters) { + (await this.token.isMinter(minter)).should.equal(false); + } + }); + + it('can be created with no pausers', async function () { + await initialize(this.token, name, symbol, minters, [], deployer); + + for (const pauser of pausers) { + (await this.token.isPauser(pauser)).should.equal(false); + } + }); + + context('with token', async function () { + beforeEach(async function () { + await initialize(this.token, name, symbol, minters, pausers, deployer); + }); + + it('initializes metadata', async function () { + (await this.token.name()).should.equal(name); + (await this.token.symbol()).should.equal(symbol); + }); + + describe('mintability', function () { + beforeEach(function () { + this.contract = this.token; + }); + + it('all minters have the minter role', async function () { + for (const minter of minters) { + (await this.token.isMinter(minter)).should.equal(true); + } + }); + }); + + describe('pausability', function () { + beforeEach(function () { + this.contract = this.token; + }); + + it('all pausers have the minter role', async function () { + for (const pauser of pausers) { + (await this.token.isPauser(pauser)).should.equal(true); + } + }); + }); + }); +}); diff --git a/test/examples/StandardToken.test.js b/test/examples/StandardToken.test.js new file mode 100644 index 000000000..811bb1a24 --- /dev/null +++ b/test/examples/StandardToken.test.js @@ -0,0 +1,96 @@ +const encodeCall = require('zos-lib/lib/helpers/encodeCall').default; +const { shouldBehaveLikeERC20Mintable } = require('../token/ERC20/behaviors/ERC20Mintable.behavior'); +const { shouldBehaveLikePublicRole } = require('../access/roles/PublicRole.behavior'); + +const BigNumber = web3.BigNumber; + +const should = require('chai') + .use(require('chai-bignumber')(BigNumber)) + .should(); + +const StandardToken = artifacts.require('StandardToken'); + +contract('StandardToken', function ([_, deployer, initialHolder, minterA, minterB, pauserA, pauserB, anyone, ...otherAccounts]) { + const name = "StdToken"; + const symbol = "STDT"; + const decimals = 18; + + const initialSupply = 300; + + const minters = [minterA, minterB]; + const pausers = [pauserA, pauserB]; + + const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; + + beforeEach(async function () { + this.token = await StandardToken.new({ from: deployer }); + }); + + async function initialize(token, name, symbol, decimals, initialSupply, initialHolder, minters, pausers, from) { + const callData = encodeCall('initialize', ['string', 'string', 'uint8', 'uint256', 'address', 'address[]', 'address[]'], [name, symbol, decimals, initialSupply, initialHolder, minters, pausers]); + await token.sendTransaction({ data: callData, from }); + } + + context('with all arguments', function () { + beforeEach(async function () { + await initialize(this.token, name, symbol, decimals, initialSupply, initialHolder, minters, pausers, deployer); + }); + + it('initializes metadata', async function () { + (await this.token.name()).should.equal(name); + (await this.token.symbol()).should.equal(symbol); + (await this.token.decimals()).should.be.bignumber.equal(decimals); + }); + + it('assigns the initial supply to the initial holder', async function () { + (await this.token.balanceOf(initialHolder)).should.be.bignumber.equal(initialSupply); + }); + + describe('mintability', function () { + beforeEach(function () { + this.contract = this.token; + }); + + it('all minters have the minter role', async function () { + for (const minter of minters) { + (await this.token.isMinter(minter)).should.equal(true); + } + }); + + shouldBehaveLikeERC20Mintable(minterA, otherAccounts); + }); + + describe('pausability', function () { + beforeEach(function () { + this.contract = this.token; + }); + + it('all pausers have the minter role', async function () { + for (const pauser of pausers) { + (await this.token.isPauser(pauser)).should.equal(true); + } + }); + }); + }); + + it('can be created with zero initial balance', async function () { + await initialize(this.token, name, symbol, decimals, 0, ZERO_ADDRESS, minters, pausers, deployer); + (await this.token.balanceOf(initialHolder)).should.be.bignumber.equal(0); + }); + + it('can be created with no minters', async function () { + await initialize(this.token, name, symbol, decimals, initialSupply, initialHolder, [], pausers, deployer); + + for (const minter of minters) { + (await this.token.isMinter(minter)).should.equal(false); + } + }); + + it('can be created with no pausers', async function () { + await initialize(this.token, name, symbol, decimals, initialSupply, initialHolder, minters, [], deployer); + + for (const pauser of pausers) { + (await this.token.isPauser(pauser)).should.equal(false); + } + }); +});