Remove 'external' functions (#2162)
* 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>
This commit is contained in:
@ -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))
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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];
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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];
|
||||
}
|
||||
|
||||
|
||||
@ -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");
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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);
|
||||
|
||||
Reference in New Issue
Block a user