Skip to content

Commit

Permalink
Remove 'external' functions (#2162)
Browse files Browse the repository at this point in the history
* Remove _grantRole and _revokeRole, replace with _setupRole

* Make all external AccessControl functions public

* Remove Ownable._transferOwnership

* Rename ERC721's _safeTransferFrom and _transferFrom to _safeTransfer and _transfer

* Make all ERC721 external functions public

* Make all miscelaneous external functions public instead

* Add changelog entry

* Move calldata arguments to memory

* Update contracts/access/AccessControl.sol

Co-Authored-By: Francisco Giordano <frangio.1@gmail.com>

* Restrict setupRole to the constructor

* Replace isConstructor for !isContract

Co-authored-by: Francisco Giordano <frangio.1@gmail.com>
  • Loading branch information
nventuro and frangio committed Apr 2, 2020
1 parent 1bc923b commit 5b5d91c
Show file tree
Hide file tree
Showing 13 changed files with 77 additions and 63 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
### Breaking changes
* `ERC721`: `burn(owner, tokenId)` was removed, use `burn(tokenId)` 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))
Expand Down
4 changes: 2 additions & 2 deletions contracts/GSN/GSNRecipient.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -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);
}
Expand Down
6 changes: 3 additions & 3 deletions contracts/GSN/GSNRecipientERC20Fee.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions contracts/GSN/GSNRecipientSignature.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
64 changes: 35 additions & 29 deletions contracts/access/AccessControl.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pragma solidity ^0.6.0;

import "../utils/EnumerableSet.sol";
import "../utils/Address.sol";
import "../GSN/Context.sol";

/**
Expand All @@ -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}.
*
Expand All @@ -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;
Expand All @@ -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);

Expand All @@ -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);

Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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);
}

/**
Expand All @@ -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());
}
}
}
7 changes: 0 additions & 7 deletions contracts/access/Ownable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion contracts/introspection/ERC165.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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];
}

Expand Down
2 changes: 1 addition & 1 deletion contracts/introspection/ERC1820Implementer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
6 changes: 5 additions & 1 deletion contracts/mocks/AccessControlMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
2 changes: 1 addition & 1 deletion contracts/mocks/ERC165/ERC165InterfacesSupported.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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];
}

Expand Down
18 changes: 9 additions & 9 deletions contracts/token/ERC721/ERC721.sol
Original file line number Diff line number Diff line change
Expand Up @@ -130,15 +130,15 @@ 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;
}

/**
* @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;
}

Expand All @@ -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];
Expand All @@ -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;
}

Expand Down Expand Up @@ -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);
}

/**
Expand Down Expand Up @@ -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);
}

/**
Expand All @@ -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");
}

Expand Down Expand Up @@ -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");

Expand Down
12 changes: 6 additions & 6 deletions docs/modules/ROOT/pages/access-control.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand All @@ -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.

Expand All @@ -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 {
Expand Down
Loading

0 comments on commit 5b5d91c

Please sign in to comment.