diff --git a/CHANGELOG.md b/CHANGELOG.md index 03974ab30..c54750b06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ### New features: * Now targeting the 0.5.x line of Solidity compilers. For 0.4.24 support, use version 2.0 of OpenZeppelin. - * `WhitelistCrowdsale`: a crowdsale where only whitelisted accounts (`WhitelistedRole`) can purchase tokens. Adding or removing accounts from the whitelist is done by whitelisters (`WhitelisterRole`). Similar to the pre-2.0 `WhitelistedCrowdsale`. ([#1525](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1525)) + * `WhitelistCrowdsale`: a crowdsale where only whitelisted accounts (`WhitelistedRole`) can purchase tokens. Adding or removing accounts from the whitelist is done by whitelist admins (`WhitelistAdminRole`). Similar to the pre-2.0 `WhitelistedCrowdsale`. ([#1525](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1525), [#1589](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1589)) * `RefundablePostDeliveryCrowdsale`: replacement for `RefundableCrowdsale` (deprecated, see below) where tokens are only granted once the crowdsale ends (if it meets its goal). ([#1543](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1543)) * `PausableCrowdsale`: allows for pausers (`PauserRole`) to pause token purchases. Other crowdsale operations (e.g. withdrawals and refunds, if applicable) are not affected. ([#832](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/832)) * `ERC20`: `transferFrom` and `_burnFrom ` now emit `Approval` events, to represent the token's state comprehensively through events. ([#1524](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1524)) diff --git a/contracts/access/roles/WhitelistAdminRole.sol b/contracts/access/roles/WhitelistAdminRole.sol new file mode 100644 index 000000000..8af7412ba --- /dev/null +++ b/contracts/access/roles/WhitelistAdminRole.sol @@ -0,0 +1,47 @@ +pragma solidity ^0.5.0; + +import "../Roles.sol"; + +/** + * @title WhitelistAdminRole + * @dev WhitelistAdmins are responsible for assigning and removing Whitelisted accounts. + */ +contract WhitelistAdminRole { + using Roles for Roles.Role; + + event WhitelistAdminAdded(address indexed account); + event WhitelistAdminRemoved(address indexed account); + + Roles.Role private _whitelistAdmins; + + constructor () internal { + _addWhitelistAdmin(msg.sender); + } + + modifier onlyWhitelistAdmin() { + require(isWhitelistAdmin(msg.sender)); + _; + } + + function isWhitelistAdmin(address account) public view returns (bool) { + return _whitelistAdmins.has(account); + } + + function addWhitelistAdmin(address account) public onlyWhitelistAdmin { + _addWhitelistAdmin(account); + } + + function renounceWhitelistAdmin() public { + _removeWhitelistAdmin(msg.sender); + } + + function _addWhitelistAdmin(address account) internal { + _whitelistAdmins.add(account); + emit WhitelistAdminAdded(account); + } + + function _removeWhitelistAdmin(address account) internal { + _whitelistAdmins.remove(account); + emit WhitelistAdminRemoved(account); + } +} diff --git a/contracts/access/roles/WhitelistedRole.sol b/contracts/access/roles/WhitelistedRole.sol index 040d4db58..3ba5ae0cf 100644 --- a/contracts/access/roles/WhitelistedRole.sol +++ b/contracts/access/roles/WhitelistedRole.sol @@ -1,15 +1,15 @@ pragma solidity ^0.5.0; import "../Roles.sol"; -import "./WhitelisterRole.sol"; +import "./WhitelistAdminRole.sol"; /** * @title WhitelistedRole - * @dev Whitelisted accounts have been approved by a Whitelister to perform certain actions (e.g. participate in a - * crowdsale). This role is special in that the only accounts that can add it are Whitelisters (who can also remove it), - * and not Whitelisteds themselves. + * @dev Whitelisted accounts have been approved by a WhitelistAdmin to perform certain actions (e.g. participate in a + * crowdsale). This role is special in that the only accounts that can add it are WhitelistAdmins (who can also remove + * it), and not Whitelisteds themselves. */ -contract WhitelistedRole is WhitelisterRole { +contract WhitelistedRole is WhitelistAdminRole { using Roles for Roles.Role; event WhitelistedAdded(address indexed account); @@ -26,11 +26,11 @@ contract WhitelistedRole is WhitelisterRole { return _whitelisteds.has(account); } - function addWhitelisted(address account) public onlyWhitelister { + function addWhitelisted(address account) public onlyWhitelistAdmin { _addWhitelisted(account); } - function removeWhitelisted(address account) public onlyWhitelister { + function removeWhitelisted(address account) public onlyWhitelistAdmin { _removeWhitelisted(account); } diff --git a/contracts/access/roles/WhitelisterRole.sol b/contracts/access/roles/WhitelisterRole.sol deleted file mode 100644 index 8812db6ed..000000000 --- a/contracts/access/roles/WhitelisterRole.sol +++ /dev/null @@ -1,47 +0,0 @@ -pragma solidity ^0.5.0; - -import "../Roles.sol"; - -/** - * @title WhitelisterRole - * @dev Whitelisters are responsible for assigning and removing Whitelisted accounts. - */ -contract WhitelisterRole { - using Roles for Roles.Role; - - event WhitelisterAdded(address indexed account); - event WhitelisterRemoved(address indexed account); - - Roles.Role private _whitelisters; - - constructor () internal { - _addWhitelister(msg.sender); - } - - modifier onlyWhitelister() { - require(isWhitelister(msg.sender)); - _; - } - - function isWhitelister(address account) public view returns (bool) { - return _whitelisters.has(account); - } - - function addWhitelister(address account) public onlyWhitelister { - _addWhitelister(account); - } - - function renounceWhitelister() public { - _removeWhitelister(msg.sender); - } - - function _addWhitelister(address account) internal { - _whitelisters.add(account); - emit WhitelisterAdded(account); - } - - function _removeWhitelister(address account) internal { - _whitelisters.remove(account); - emit WhitelisterRemoved(account); - } -} diff --git a/contracts/mocks/WhitelistAdminRoleMock.sol b/contracts/mocks/WhitelistAdminRoleMock.sol new file mode 100644 index 000000000..5d07f0c40 --- /dev/null +++ b/contracts/mocks/WhitelistAdminRoleMock.sol @@ -0,0 +1,17 @@ +pragma solidity ^0.5.0; + +import "../access/roles/WhitelistAdminRole.sol"; + +contract WhitelistAdminRoleMock is WhitelistAdminRole { + function removeWhitelistAdmin(address account) public { + _removeWhitelistAdmin(account); + } + + function onlyWhitelistAdminMock() public view onlyWhitelistAdmin { + } + + // Causes a compilation error if super._removeWhitelistAdmin is not internal + function _removeWhitelistAdmin(address account) internal { + super._removeWhitelistAdmin(account); + } +} diff --git a/contracts/mocks/WhitelisterRoleMock.sol b/contracts/mocks/WhitelisterRoleMock.sol deleted file mode 100644 index 714410410..000000000 --- a/contracts/mocks/WhitelisterRoleMock.sol +++ /dev/null @@ -1,17 +0,0 @@ -pragma solidity ^0.5.0; - -import "../access/roles/WhitelisterRole.sol"; - -contract WhitelisterRoleMock is WhitelisterRole { - function removeWhitelister(address account) public { - _removeWhitelister(account); - } - - function onlyWhitelisterMock() public view onlyWhitelister { - } - - // Causes a compilation error if super._removeWhitelister is not internal - function _removeWhitelister(address account) internal { - super._removeWhitelister(account); - } -} diff --git a/test/access/roles/WhitelistAdminRole.test.js b/test/access/roles/WhitelistAdminRole.test.js new file mode 100644 index 000000000..e59dcd895 --- /dev/null +++ b/test/access/roles/WhitelistAdminRole.test.js @@ -0,0 +1,11 @@ +const { shouldBehaveLikePublicRole } = require('../../access/roles/PublicRole.behavior'); +const WhitelistAdminRoleMock = artifacts.require('WhitelistAdminRoleMock'); + +contract('WhitelistAdminRole', function ([_, whitelistAdmin, otherWhitelistAdmin, ...otherAccounts]) { + beforeEach(async function () { + this.contract = await WhitelistAdminRoleMock.new({ from: whitelistAdmin }); + await this.contract.addWhitelistAdmin(otherWhitelistAdmin, { from: whitelistAdmin }); + }); + + shouldBehaveLikePublicRole(whitelistAdmin, otherWhitelistAdmin, otherAccounts, 'whitelistAdmin'); +}); diff --git a/test/access/roles/WhitelistedRole.test.js b/test/access/roles/WhitelistedRole.test.js index 2290424cd..e578f6fa2 100644 --- a/test/access/roles/WhitelistedRole.test.js +++ b/test/access/roles/WhitelistedRole.test.js @@ -1,12 +1,12 @@ const { shouldBehaveLikePublicRole } = require('../../access/roles/PublicRole.behavior'); const WhitelistedRoleMock = artifacts.require('WhitelistedRoleMock'); -contract('WhitelistedRole', function ([_, whitelisted, otherWhitelisted, whitelister, ...otherAccounts]) { +contract('WhitelistedRole', function ([_, whitelisted, otherWhitelisted, whitelistAdmin, ...otherAccounts]) { beforeEach(async function () { - this.contract = await WhitelistedRoleMock.new({ from: whitelister }); - await this.contract.addWhitelisted(whitelisted, { from: whitelister }); - await this.contract.addWhitelisted(otherWhitelisted, { from: whitelister }); + this.contract = await WhitelistedRoleMock.new({ from: whitelistAdmin }); + await this.contract.addWhitelisted(whitelisted, { from: whitelistAdmin }); + await this.contract.addWhitelisted(otherWhitelisted, { from: whitelistAdmin }); }); - shouldBehaveLikePublicRole(whitelisted, otherWhitelisted, otherAccounts, 'whitelisted', whitelister); + shouldBehaveLikePublicRole(whitelisted, otherWhitelisted, otherAccounts, 'whitelisted', whitelistAdmin); }); diff --git a/test/access/roles/WhitelisterRole.test.js b/test/access/roles/WhitelisterRole.test.js deleted file mode 100644 index 39e664407..000000000 --- a/test/access/roles/WhitelisterRole.test.js +++ /dev/null @@ -1,11 +0,0 @@ -const { shouldBehaveLikePublicRole } = require('../../access/roles/PublicRole.behavior'); -const WhitelisterRoleMock = artifacts.require('WhitelisterRoleMock'); - -contract('WhitelisterRole', function ([_, whitelister, otherWhitelister, ...otherAccounts]) { - beforeEach(async function () { - this.contract = await WhitelisterRoleMock.new({ from: whitelister }); - await this.contract.addWhitelister(otherWhitelister, { from: whitelister }); - }); - - shouldBehaveLikePublicRole(whitelister, otherWhitelister, otherAccounts, 'whitelister'); -});