diff --git a/CHANGELOG.md b/CHANGELOG.md index cf46bfa593e..962976dd9ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ ### Breaking changes * `ERC721`: `burn(owner, tokenId)` was removed, use `burn(owner)` instead. ([#2125](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2125)) * `ERC721`: `_checkOnERC721Received` was removed. ([#2125](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2125)) + * `ERC721`: `_transferFrom` and `_safeTransferFrom` were renamed to `_transfer` and `_safeTransfer`. ([#2162](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2162)) + * `Ownable`: removed `_transferOwnership`. ([#2162](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2162)) * `PullPayment`, `Escrow`: `withdrawWithGas` was removed. The old `withdraw` function now forwards all gas. ([#2125](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2125)) * `Roles` was removed, use `AccessControl` as a replacement. ([#2112](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2112)) * `ECDSA`: when receiving an invalid signature, `recover` now reverts instead of returning the zero address. ([#2114](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2114)) diff --git a/contracts/GSN/GSNRecipient.sol b/contracts/GSN/GSNRecipient.sol index 50e0b111b52..f0554e7c0f6 100644 --- a/contracts/GSN/GSNRecipient.sol +++ b/contracts/GSN/GSNRecipient.sol @@ -119,7 +119,7 @@ abstract contract GSNRecipient is IRelayRecipient, Context { * * - the caller must be the `RelayHub` contract. */ - function preRelayedCall(bytes calldata context) external virtual override returns (bytes32) { + function preRelayedCall(bytes memory context) public virtual override returns (bytes32) { require(msg.sender == getHubAddr(), "GSNRecipient: caller is not RelayHub"); return _preRelayedCall(context); } @@ -142,7 +142,7 @@ abstract contract GSNRecipient is IRelayRecipient, Context { * * - the caller must be the `RelayHub` contract. */ - function postRelayedCall(bytes calldata context, bool success, uint256 actualCharge, bytes32 preRetVal) external virtual override { + function postRelayedCall(bytes memory context, bool success, uint256 actualCharge, bytes32 preRetVal) public virtual override { require(msg.sender == getHubAddr(), "GSNRecipient: caller is not RelayHub"); _postRelayedCall(context, success, actualCharge, preRetVal); } diff --git a/contracts/GSN/GSNRecipientERC20Fee.sol b/contracts/GSN/GSNRecipientERC20Fee.sol index 893872c5ffa..4bfee568281 100644 --- a/contracts/GSN/GSNRecipientERC20Fee.sol +++ b/contracts/GSN/GSNRecipientERC20Fee.sol @@ -53,15 +53,15 @@ contract GSNRecipientERC20Fee is GSNRecipient { function acceptRelayedCall( address, address from, - bytes calldata, + bytes memory, uint256 transactionFee, uint256 gasPrice, uint256, uint256, - bytes calldata, + bytes memory, uint256 maxPossibleCharge ) - external + public view virtual override diff --git a/contracts/GSN/GSNRecipientSignature.sol b/contracts/GSN/GSNRecipientSignature.sol index 055be817521..a777e37f4a3 100644 --- a/contracts/GSN/GSNRecipientSignature.sol +++ b/contracts/GSN/GSNRecipientSignature.sol @@ -32,15 +32,15 @@ contract GSNRecipientSignature is GSNRecipient { function acceptRelayedCall( address relay, address from, - bytes calldata encodedFunction, + bytes memory encodedFunction, uint256 transactionFee, uint256 gasPrice, uint256 gasLimit, uint256 nonce, - bytes calldata approvalData, + bytes memory approvalData, uint256 ) - external + public view virtual override diff --git a/contracts/access/AccessControl.sol b/contracts/access/AccessControl.sol index 8f82c1bddbc..e03f72b2a4a 100644 --- a/contracts/access/AccessControl.sol +++ b/contracts/access/AccessControl.sol @@ -1,6 +1,7 @@ pragma solidity ^0.6.0; import "../utils/EnumerableSet.sol"; +import "../utils/Address.sol"; import "../GSN/Context.sol"; /** @@ -25,10 +26,7 @@ import "../GSN/Context.sol"; * } * ``` * - * Roles can be granted and revoked programatically by calling the `internal` - * {_grantRole} and {_revokeRole} functions. - * - * This can also be achieved dynamically via the `external` {grantRole} and + * Roles can be granted and revoked dynamically via the {grantRole} and * {revokeRole} functions. Each role has an associated admin role, and only * accounts that have a role's admin role can call {grantRole} and {revokeRoke}. * @@ -39,6 +37,7 @@ import "../GSN/Context.sol"; */ abstract contract AccessControl is Context { using EnumerableSet for EnumerableSet.AddressSet; + using Address for address; struct RoleData { EnumerableSet.AddressSet members; @@ -52,9 +51,8 @@ abstract contract AccessControl is Context { /** * @dev Emitted when `account` is granted `role`. * - * `sender` is the account that originated the contract call: - * - if using `grantRole`, it is the admin role bearer - * - if using `_grantRole`, its meaning is system-dependent + * `sender` is the account that originated the contract call, an admin role + * bearer except when using {_setupRole}. */ event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender); @@ -64,7 +62,6 @@ abstract contract AccessControl is Context { * `sender` is the account that originated the contract call: * - if using `revokeRole`, it is the admin role bearer * - if using `renounceRole`, it is the role bearer (i.e. `account`) - * - if using `_renounceRole`, its meaning is system-dependent */ event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender); @@ -105,20 +102,21 @@ abstract contract AccessControl is Context { * * To change a role's admin, use {_setRoleAdmin}. */ - function getRoleAdmin(bytes32 role) external view returns (bytes32) { + function getRoleAdmin(bytes32 role) public view returns (bytes32) { return _roles[role].adminRole; } /** * @dev Grants `role` to `account`. * - * Calls {_grantRole} internally. + * If `account` had not been already granted `role`, emits a {RoleGranted} + * event. * * Requirements: * * - the caller must have `role`'s admin role. */ - function grantRole(bytes32 role, address account) external virtual { + function grantRole(bytes32 role, address account) public virtual { require(hasRole(_roles[role].adminRole, _msgSender()), "AccessControl: sender must be an admin to grant"); _grantRole(role, account); @@ -127,13 +125,13 @@ abstract contract AccessControl is Context { /** * @dev Revokes `role` from `account`. * - * Calls {_revokeRole} internally. + * If `account` had been granted `role`, emits a {RoleRevoked} event. * * Requirements: * * - the caller must have `role`'s admin role. */ - function revokeRole(bytes32 role, address account) external virtual { + function revokeRole(bytes32 role, address account) public virtual { require(hasRole(_roles[role].adminRole, _msgSender()), "AccessControl: sender must be an admin to revoke"); _revokeRole(role, account); @@ -146,11 +144,14 @@ abstract contract AccessControl is Context { * purpose is to provide a mechanism for accounts to lose their privileges * if they are compromised (such as when a trusted device is misplaced). * + * If the calling account had been granted `role`, emits a {RoleRevoked} + * event. + * * Requirements: * * - the caller must be `account`. */ - function renounceRole(bytes32 role, address account) external virtual { + function renounceRole(bytes32 role, address account) public virtual { require(account == _msgSender(), "AccessControl: can only renounce roles for self"); _revokeRole(role, account); @@ -160,23 +161,16 @@ abstract contract AccessControl is Context { * @dev Grants `role` to `account`. * * If `account` had not been already granted `role`, emits a {RoleGranted} - * event. - */ - function _grantRole(bytes32 role, address account) internal virtual { - if (_roles[role].members.add(account)) { - emit RoleGranted(role, account, _msgSender()); - } - } - - /** - * @dev Revokes `role` from `account`. + * event. Note that unlike {grantRole}, this function doesn't perform any + * checks on the calling account. * - * If `account` had been granted `role`, emits a {RoleRevoked} event. + * Requirements: + * + * - this function can only be called from a constructor. */ - function _revokeRole(bytes32 role, address account) internal virtual { - if (_roles[role].members.remove(account)) { - emit RoleRevoked(role, account, _msgSender()); - } + function _setupRole(bytes32 role, address account) internal virtual { + require(!address(this).isContract(), "AccessControl: roles cannot be setup after construction"); + _grantRole(role, account); } /** @@ -185,4 +179,16 @@ abstract contract AccessControl is Context { function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual { _roles[role].adminRole = adminRole; } + + function _grantRole(bytes32 role, address account) private { + if (_roles[role].members.add(account)) { + emit RoleGranted(role, account, _msgSender()); + } + } + + function _revokeRole(bytes32 role, address account) private { + if (_roles[role].members.remove(account)) { + emit RoleRevoked(role, account, _msgSender()); + } + } } diff --git a/contracts/access/Ownable.sol b/contracts/access/Ownable.sol index bea305ba52f..738493f8e73 100644 --- a/contracts/access/Ownable.sol +++ b/contracts/access/Ownable.sol @@ -59,13 +59,6 @@ contract Ownable is Context { * Can only be called by the current owner. */ function transferOwnership(address newOwner) public virtual onlyOwner { - _transferOwnership(newOwner); - } - - /** - * @dev Transfers ownership of the contract to a new account (`newOwner`). - */ - function _transferOwnership(address newOwner) internal virtual { require(newOwner != address(0), "Ownable: new owner is the zero address"); emit OwnershipTransferred(_owner, newOwner); _owner = newOwner; diff --git a/contracts/introspection/ERC165.sol b/contracts/introspection/ERC165.sol index 881a636e965..45d78099b92 100644 --- a/contracts/introspection/ERC165.sol +++ b/contracts/introspection/ERC165.sol @@ -30,7 +30,7 @@ contract ERC165 is IERC165 { * * Time complexity O(1), guaranteed to always use less than 30 000 gas. */ - function supportsInterface(bytes4 interfaceId) external view override returns (bool) { + function supportsInterface(bytes4 interfaceId) public view override returns (bool) { return _supportedInterfaces[interfaceId]; } diff --git a/contracts/introspection/ERC1820Implementer.sol b/contracts/introspection/ERC1820Implementer.sol index 3d44c7ec3a2..0fbbd931945 100644 --- a/contracts/introspection/ERC1820Implementer.sol +++ b/contracts/introspection/ERC1820Implementer.sol @@ -18,7 +18,7 @@ contract ERC1820Implementer is IERC1820Implementer { /** * See {IERC1820Implementer-canImplementInterfaceForAddress}. */ - function canImplementInterfaceForAddress(bytes32 interfaceHash, address account) external view override returns (bytes32) { + function canImplementInterfaceForAddress(bytes32 interfaceHash, address account) public view override returns (bytes32) { return _supportedInterfaces[interfaceHash][account] ? _ERC1820_ACCEPT_MAGIC : bytes32(0x00); } diff --git a/contracts/mocks/AccessControlMock.sol b/contracts/mocks/AccessControlMock.sol index fe89d00f61b..876e78a2af8 100644 --- a/contracts/mocks/AccessControlMock.sol +++ b/contracts/mocks/AccessControlMock.sol @@ -4,10 +4,14 @@ import "../access/AccessControl.sol"; contract AccessControlMock is AccessControl { constructor() public { - _grantRole(DEFAULT_ADMIN_ROLE, _msgSender()); + _setupRole(DEFAULT_ADMIN_ROLE, _msgSender()); } function setRoleAdmin(bytes32 roleId, bytes32 adminRoleId) public { _setRoleAdmin(roleId, adminRoleId); } + + function setupRole(bytes32 roleId, address account) public { + _setupRole(roleId, account); + } } diff --git a/contracts/mocks/ERC165/ERC165InterfacesSupported.sol b/contracts/mocks/ERC165/ERC165InterfacesSupported.sol index 1f184db6437..fb3b82c9f6d 100644 --- a/contracts/mocks/ERC165/ERC165InterfacesSupported.sol +++ b/contracts/mocks/ERC165/ERC165InterfacesSupported.sol @@ -34,7 +34,7 @@ contract SupportsInterfaceWithLookupMock is IERC165 { /** * @dev Implement supportsInterface(bytes4) using a lookup table. */ - function supportsInterface(bytes4 interfaceId) external view override returns (bool) { + function supportsInterface(bytes4 interfaceId) public view override returns (bool) { return _supportedInterfaces[interfaceId]; } diff --git a/contracts/token/ERC721/ERC721.sol b/contracts/token/ERC721/ERC721.sol index 0a39c084b98..ad6cdb89add 100644 --- a/contracts/token/ERC721/ERC721.sol +++ b/contracts/token/ERC721/ERC721.sol @@ -130,7 +130,7 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Enumerable * @dev Gets the token name. * @return string representing the token name */ - function name() external view override returns (string memory) { + function name() public view override returns (string memory) { return _name; } @@ -138,7 +138,7 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Enumerable * @dev Gets the token symbol. * @return string representing the token symbol */ - function symbol() external view override returns (string memory) { + function symbol() public view override returns (string memory) { return _symbol; } @@ -150,7 +150,7 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Enumerable * * Reverts if the token ID does not exist. */ - function tokenURI(uint256 tokenId) external view override returns (string memory) { + function tokenURI(uint256 tokenId) public view override returns (string memory) { require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token"); string memory _tokenURI = _tokenURIs[tokenId]; @@ -169,7 +169,7 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Enumerable * automatically added as a preffix in {tokenURI} to each token's URI, when * they are non-empty. */ - function baseURI() external view returns (string memory) { + function baseURI() public view returns (string memory) { return _baseURI; } @@ -269,7 +269,7 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Enumerable //solhint-disable-next-line max-line-length require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved"); - _transferFrom(from, to, tokenId); + _transfer(from, to, tokenId); } /** @@ -301,7 +301,7 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Enumerable */ function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory _data) public virtual override { require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved"); - _safeTransferFrom(from, to, tokenId, _data); + _safeTransfer(from, to, tokenId, _data); } /** @@ -316,8 +316,8 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Enumerable * @param tokenId uint256 ID of the token to be transferred * @param _data bytes data to send along with a safe transfer check */ - function _safeTransferFrom(address from, address to, uint256 tokenId, bytes memory _data) internal virtual { - _transferFrom(from, to, tokenId); + function _safeTransfer(address from, address to, uint256 tokenId, bytes memory _data) internal virtual { + _transfer(from, to, tokenId); require(_checkOnERC721Received(from, to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer"); } @@ -432,7 +432,7 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Enumerable * @param to address to receive the ownership of the given token ID * @param tokenId uint256 ID of the token to be transferred */ - function _transferFrom(address from, address to, uint256 tokenId) internal virtual { + function _transfer(address from, address to, uint256 tokenId) internal virtual { require(ownerOf(tokenId) == from, "ERC721: transfer of token that is not own"); require(to != address(0), "ERC721: transfer to the zero address"); diff --git a/docs/modules/ROOT/pages/access-control.adoc b/docs/modules/ROOT/pages/access-control.adoc index f5137a0b3f0..d2ad728a88b 100644 --- a/docs/modules/ROOT/pages/access-control.adoc +++ b/docs/modules/ROOT/pages/access-control.adoc @@ -69,7 +69,7 @@ contract MyToken is ERC20, AccessControl { constructor(address minter) public { // Grant the minter role to a specified account - _grantRole(MINTER_ROLE, minter); + _setupRole(MINTER_ROLE, minter); } function mint(address to, uint256 amount) public { @@ -98,8 +98,8 @@ contract MyToken is ERC20, AccessControl { bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE"); constructor(address minter, address burner) public { - _grantRole(MINTER_ROLE, minter); - _grantRole(BURNER_ROLE, burner); + _setupRole(MINTER_ROLE, minter); + _setupRole(BURNER_ROLE, burner); } function mint(address to, uint256 amount) public { @@ -119,11 +119,11 @@ So clean! By splitting concerns this way, more granular levels of permission may [[granting-and-revoking]] === Granting and Revoking Roles -The ERC20 token example above uses `\_grantRole`, an `internal` function that is useful when programmatically asigning roles (such as during construction). But what if we later want to grant the 'minter' role to additional accounts? +The ERC20 token example above uses `\_setupRole`, an `internal` function that is useful when programmatically asigning roles (such as during construction). But what if we later want to grant the 'minter' role to additional accounts? By default, **accounts with a role cannot grant it or revoke it from other accounts**: all having a role does is making the `hasRole` check pass. To grant and revoke roles dynamically, you will need help from the _role's admin_. -Every role has an associated admin role, which grants permission to call the `grantRole` and `revokeRole` `external` functions. A role can be granted or revoked by using these if the calling account has the corresponding admin role. Multiple roles may have the same admin role to make management easier. A role's admin can even be the same role itself, which would cause accounts with that role to be able to also grant and revoke it. +Every role has an associated admin role, which grants permission to call the `grantRole` and `revokeRole` functions. A role can be granted or revoked by using these if the calling account has the corresponding admin role. Multiple roles may have the same admin role to make management easier. A role's admin can even be the same role itself, which would cause accounts with that role to be able to also grant and revoke it. This mechanism can be used to create complex permissioning structures resembling organizational charts, but it also provides an easy way to manage simpler applications. `AccessControl` includes a special role, called `DEFAULT_ADMIN_ROLE`, which acts as the **default admin role for all roles**. An account with this role will be able to manage any other role, unless `\_setRoleAdmin` is used to select a new admin role. @@ -143,7 +143,7 @@ contract MyToken is ERC20, AccessControl { constructor() public { // Grant the contract deployer the default admin role: it will be able // to grant and revoke any roles - _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); + _setupRole(DEFAULT_ADMIN_ROLE, msg.sender); } function mint(address to, uint256 amount) public { diff --git a/test/access/AccessControl.test.js b/test/access/AccessControl.test.js index c49276f925d..db4cf47c026 100644 --- a/test/access/AccessControl.test.js +++ b/test/access/AccessControl.test.js @@ -17,6 +17,15 @@ describe('AccessControl', function () { this.accessControl = await AccessControlMock.new({ from: admin }); }); + describe('_setupRole', function () { + it('cannot be called outside the constructor', async function () { + await expectRevert( + this.accessControl.setupRole(OTHER_ROLE, other), + 'AccessControl: roles cannot be setup after construction' + ); + }); + }); + describe('default admin', function () { it('deployer has default admin role', async function () { expect(await this.accessControl.hasRole(DEFAULT_ADMIN_ROLE, admin)).to.equal(true);