diff --git a/CHANGELOG.md b/CHANGELOG.md index cac6d14fd..8f461f43c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * `BeaconProxy`: added new kind of proxy that allows simultaneous atomic upgrades. ([#2411](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2411)) * `EIP712`: added helpers to verify EIP712 typed data signatures on chain. ([#2418](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2418)) + * Presets: added token presets with preminted fixed supply `ERC20PresetFixedSupply` and `ERC777PresetFixedSupply`. ([#2399](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2399)) * `Address`: added `functionDelegateCall`, similar to the existing `functionCall`. ([#2333](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2333)) ## 3.3.0 (2020-11-26) diff --git a/contracts/presets/ERC20PresetFixedSupply.sol b/contracts/presets/ERC20PresetFixedSupply.sol new file mode 100644 index 000000000..839110952 --- /dev/null +++ b/contracts/presets/ERC20PresetFixedSupply.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.6.2; + +import "../token/ERC20/ERC20Burnable.sol"; + +/** + * @dev {ERC20} token, including: + * + * - Preminted initial supply + * - Ability for holders to burn (destroy) their tokens + * - No access control mechanism (for minting/pausing) and hence no governance + * + * This contract uses {ERC20Burnable} to include burn capabilities - head to + * its documentation for details. + */ +contract ERC20PresetFixedSupply is ERC20Burnable { + /** + * @dev Mints `initialSupply` amount of token and transfers them to `owner`. + * + * See {ERC20-constructor}. + */ + constructor( + string memory name, + string memory symbol, + uint256 initialSupply, + address owner + ) public ERC20(name, symbol) { + _mint(owner, initialSupply); + } +} diff --git a/contracts/presets/ERC777PresetFixedSupply.sol b/contracts/presets/ERC777PresetFixedSupply.sol new file mode 100644 index 000000000..5756b6498 --- /dev/null +++ b/contracts/presets/ERC777PresetFixedSupply.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.6.2; + +import "../token/ERC777/ERC777.sol"; + +/** + * @dev {ERC777} token, including: + * + * - Preminted initial supply + * - No access control mechanism (for minting/pausing) and hence no governance + */ +contract ERC777PresetFixedSupply is ERC777 { + /** + * @dev Mints `initialSupply` amount of token and transfers them to `owner`. + * + * See {ERC777-constructor}. + */ + constructor( + string memory name, + string memory symbol, + address[] memory defaultOperators, + uint256 initialSupply, + address owner + ) public ERC777(name, symbol, defaultOperators) { + _mint(owner, initialSupply, "", ""); + } +} diff --git a/contracts/presets/README.adoc b/contracts/presets/README.adoc index df2941907..8f1898576 100644 --- a/contracts/presets/README.adoc +++ b/contracts/presets/README.adoc @@ -16,3 +16,7 @@ TIP: Intermediate and advanced users can use these as starting points when writi {{ERC721PresetMinterPauserAutoId}} {{ERC1155PresetMinterPauser}} + +{{ERC20PresetFixedSupply}} + +{{ERC777PresetFixedSupply}} diff --git a/test/presets/ERC20PresetFixedSupply.test.js b/test/presets/ERC20PresetFixedSupply.test.js new file mode 100644 index 000000000..f1d0b53a0 --- /dev/null +++ b/test/presets/ERC20PresetFixedSupply.test.js @@ -0,0 +1,42 @@ +const { BN, constants, expectEvent } = require('@openzeppelin/test-helpers'); +const { ZERO_ADDRESS } = constants; + +const { expect } = require('chai'); + +const ERC20PresetFixedSupply = artifacts.require('ERC20PresetFixedSupply'); + +contract('ERC20PresetFixedSupply', function (accounts) { + const [deployer, owner] = accounts; + + const name = 'PresetFixedSupply'; + const symbol = 'PFS'; + + const initialSupply = new BN('50000'); + const amount = new BN('10000'); + + before(async function () { + this.token = await ERC20PresetFixedSupply.new(name, symbol, initialSupply, owner, { from: deployer }); + }); + + it('deployer has the balance equal to initial supply', async function () { + expect(await this.token.balanceOf(owner)).to.be.bignumber.equal(initialSupply); + }); + + it('total supply is equal to initial supply', async function () { + expect(await this.token.totalSupply()).to.be.bignumber.equal(initialSupply); + }); + + describe('burning', function () { + it('holders can burn their tokens', async function () { + const remainingBalance = initialSupply.sub(amount); + const receipt = await this.token.burn(amount, { from: owner }); + expectEvent(receipt, 'Transfer', { from: owner, to: ZERO_ADDRESS, value: amount }); + expect(await this.token.balanceOf(owner)).to.be.bignumber.equal(remainingBalance); + }); + + it('decrements totalSupply', async function () { + const expectedSupply = initialSupply.sub(amount); + expect(await this.token.totalSupply()).to.be.bignumber.equal(expectedSupply); + }); + }); +}); diff --git a/test/presets/ERC777PresetFixedSupply.test.js b/test/presets/ERC777PresetFixedSupply.test.js new file mode 100644 index 000000000..e6a842bec --- /dev/null +++ b/test/presets/ERC777PresetFixedSupply.test.js @@ -0,0 +1,49 @@ +const { BN, singletons } = require('@openzeppelin/test-helpers'); + +const { expect } = require('chai'); + +const ERC777PresetFixedSupply = artifacts.require('ERC777PresetFixedSupply'); + +contract('ERC777PresetFixedSupply', function (accounts) { + const [registryFunder, owner, defaultOperatorA, defaultOperatorB, anyone] = accounts; + + const initialSupply = new BN('10000'); + const name = 'ERC777Preset'; + const symbol = '777P'; + + const defaultOperators = [defaultOperatorA, defaultOperatorB]; + + before(async function () { + await singletons.ERC1820Registry(registryFunder); + }); + + beforeEach(async function () { + this.token = await ERC777PresetFixedSupply.new(name, symbol, defaultOperators, initialSupply, owner); + }); + + it('returns the name', async function () { + expect(await this.token.name()).to.equal(name); + }); + + it('returns the symbol', async function () { + expect(await this.token.symbol()).to.equal(symbol); + }); + + it('returns the default operators', async function () { + expect(await this.token.defaultOperators()).to.deep.equal(defaultOperators); + }); + + it('default operators are operators for all accounts', async function () { + for (const operator of defaultOperators) { + expect(await this.token.isOperatorFor(operator, anyone)).to.equal(true); + } + }); + + it('returns the total supply equal to initial supply', async function () { + expect(await this.token.totalSupply()).to.be.bignumber.equal(initialSupply); + }); + + it('returns the balance of owner equal to initial supply', async function () { + expect(await this.token.balanceOf(owner)).to.be.bignumber.equal(initialSupply); + }); +});