Commit 95329dc3 by github-actions

Transpile be866876

parent 288e6df9
# Changelog # Changelog
## Unreleased ## Unreleased
* Enumerables: Improve gas cost of removal in `EnumerableSet` and `EnumerableMap`.
* Enumerables: Improve gas cost of lookup in `EnumerableSet` and `EnumerableMap`.
## Unreleased
* `IERC20Metadata`: add a new extended interface that includes the optional `name()`, `symbol()` and `decimals()` functions. ([#2561](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2561)) * `IERC20Metadata`: add a new extended interface that includes the optional `name()`, `symbol()` and `decimals()` functions. ([#2561](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2561))
* `ERC777`: make reception acquirement optional in `_mint`. ([#2552](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2552)) * `ERC777`: make reception acquirement optional in `_mint`. ([#2552](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2552))
...@@ -9,6 +13,22 @@ ...@@ -9,6 +13,22 @@
* `SignatureChecker`: add a signature verification library that supports both EOA and ERC1271 compliant contracts as signers. ([#2532](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2532)) * `SignatureChecker`: add a signature verification library that supports both EOA and ERC1271 compliant contracts as signers. ([#2532](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2532))
* `Multicall`: add abstract contract with `multicall(bytes[] calldata data)` function to bundle multiple calls together ([#2608](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2608)) * `Multicall`: add abstract contract with `multicall(bytes[] calldata data)` function to bundle multiple calls together ([#2608](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2608))
* `ECDSA`: add support for ERC2098 short-signatures. ([#2582](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2582)) * `ECDSA`: add support for ERC2098 short-signatures. ([#2582](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2582))
* `AccessControl`: add a `onlyRole` modifier to restrict specific function to callers bearing a specific role. ([#2609](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2609))
* `StorageSlot`: add a library for reading and writing primitive types to specific storage slots. ([#2542](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2542))
* UUPS Proxies: add `UUPSUpgradeable` to implement the UUPS proxy pattern together with `EIP1967Proxy`. ([#2542](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2542))
### Breaking changes
This release includes two small breaking changes in `TimelockController`.
1. The `onlyRole` modifier in this contract was designed to let anyone through if the role was granted to `address(0)`,
allowing the possibility to to make a role "open", which can be used for `EXECUTOR_ROLE`. This modifier is now
replaced by `AccessControl.onlyRole`, which does not have this ability. The previous behavior was moved to the
modifier `TimelockController.onlyRoleOrOpenRole`.
2. It was possible to make `PROPOSER_ROLE` an open role (as described in the previous item) if it was granted to
`address(0)`. This would affect the `schedule`, `scheduleBatch`, and `cancel` operations in `TimelockController`.
This ability was removed as it does not make sense to open up the `PROPOSER_ROLE` in the same way that it does for
`EXECUTOR_ROLE`.
## 4.0.0 (2021-03-23) ## 4.0.0 (2021-03-23)
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
pragma solidity ^0.8.0; pragma solidity ^0.8.0;
import "../utils/ContextUpgradeable.sol"; import "../utils/ContextUpgradeable.sol";
import "../utils/StringsUpgradeable.sol";
import "../utils/introspection/ERC165Upgradeable.sol"; import "../utils/introspection/ERC165Upgradeable.sol";
import "../proxy/utils/Initializable.sol"; import "../proxy/utils/Initializable.sol";
...@@ -101,6 +102,21 @@ abstract contract AccessControlUpgradeable is Initializable, ContextUpgradeable, ...@@ -101,6 +102,21 @@ abstract contract AccessControlUpgradeable is Initializable, ContextUpgradeable,
event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender); event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
/** /**
* @dev Modifier that checks that an account has a specific role. Reverts
* with a standardized message including the required role.
*
* The format of the revert reason is given by the following regular expression:
*
* /^AccessControl: account (0x[0-9a-f]{20}) is missing role (0x[0-9a-f]{32})$/
*
* _Available since v4.1._
*/
modifier onlyRole(bytes32 role) {
_checkRole(role, _msgSender());
_;
}
/**
* @dev See {IERC165-supportsInterface}. * @dev See {IERC165-supportsInterface}.
*/ */
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
...@@ -116,6 +132,24 @@ abstract contract AccessControlUpgradeable is Initializable, ContextUpgradeable, ...@@ -116,6 +132,24 @@ abstract contract AccessControlUpgradeable is Initializable, ContextUpgradeable,
} }
/** /**
* @dev Revert with a standard message if `account` is missing `role`.
*
* The format of the revert reason is given by the following regular expression:
*
* /^AccessControl: account (0x[0-9a-f]{20}) is missing role (0x[0-9a-f]{32})$/
*/
function _checkRole(bytes32 role, address account) internal view {
if(!hasRole(role, account)) {
revert(string(abi.encodePacked(
"AccessControl: account ",
StringsUpgradeable.toHexString(uint160(account), 20),
" is missing role ",
StringsUpgradeable.toHexString(uint256(role), 32)
)));
}
}
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and * @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}. * {revokeRole}.
* *
...@@ -135,9 +169,7 @@ abstract contract AccessControlUpgradeable is Initializable, ContextUpgradeable, ...@@ -135,9 +169,7 @@ abstract contract AccessControlUpgradeable is Initializable, ContextUpgradeable,
* *
* - the caller must have ``role``'s admin role. * - the caller must have ``role``'s admin role.
*/ */
function grantRole(bytes32 role, address account) public virtual override { function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
require(hasRole(getRoleAdmin(role), _msgSender()), "AccessControl: sender must be an admin to grant");
_grantRole(role, account); _grantRole(role, account);
} }
...@@ -150,9 +182,7 @@ abstract contract AccessControlUpgradeable is Initializable, ContextUpgradeable, ...@@ -150,9 +182,7 @@ abstract contract AccessControlUpgradeable is Initializable, ContextUpgradeable,
* *
* - the caller must have ``role``'s admin role. * - the caller must have ``role``'s admin role.
*/ */
function revokeRole(bytes32 role, address account) public virtual override { function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
require(hasRole(getRoleAdmin(role), _msgSender()), "AccessControl: sender must be an admin to revoke");
_revokeRole(role, account); _revokeRole(role, account);
} }
......
...@@ -88,8 +88,10 @@ contract TimelockControllerUpgradeable is Initializable, AccessControlUpgradeabl ...@@ -88,8 +88,10 @@ contract TimelockControllerUpgradeable is Initializable, AccessControlUpgradeabl
* considered. Granting a role to `address(0)` is equivalent to enabling * considered. Granting a role to `address(0)` is equivalent to enabling
* this role for everyone. * this role for everyone.
*/ */
modifier onlyRole(bytes32 role) { modifier onlyRoleOrOpenRole(bytes32 role) {
require(hasRole(role, _msgSender()) || hasRole(role, address(0)), "TimelockController: sender requires permission"); if (!hasRole(role, address(0))) {
_checkRole(role, _msgSender());
}
_; _;
} }
...@@ -230,7 +232,7 @@ contract TimelockControllerUpgradeable is Initializable, AccessControlUpgradeabl ...@@ -230,7 +232,7 @@ contract TimelockControllerUpgradeable is Initializable, AccessControlUpgradeabl
* *
* - the caller must have the 'executor' role. * - the caller must have the 'executor' role.
*/ */
function execute(address target, uint256 value, bytes calldata data, bytes32 predecessor, bytes32 salt) public payable virtual onlyRole(EXECUTOR_ROLE) { function execute(address target, uint256 value, bytes calldata data, bytes32 predecessor, bytes32 salt) public payable virtual onlyRoleOrOpenRole(EXECUTOR_ROLE) {
bytes32 id = hashOperation(target, value, data, predecessor, salt); bytes32 id = hashOperation(target, value, data, predecessor, salt);
_beforeCall(predecessor); _beforeCall(predecessor);
_call(id, 0, target, value, data); _call(id, 0, target, value, data);
...@@ -246,7 +248,7 @@ contract TimelockControllerUpgradeable is Initializable, AccessControlUpgradeabl ...@@ -246,7 +248,7 @@ contract TimelockControllerUpgradeable is Initializable, AccessControlUpgradeabl
* *
* - the caller must have the 'executor' role. * - the caller must have the 'executor' role.
*/ */
function executeBatch(address[] calldata targets, uint256[] calldata values, bytes[] calldata datas, bytes32 predecessor, bytes32 salt) public payable virtual onlyRole(EXECUTOR_ROLE) { function executeBatch(address[] calldata targets, uint256[] calldata values, bytes[] calldata datas, bytes32 predecessor, bytes32 salt) public payable virtual onlyRoleOrOpenRole(EXECUTOR_ROLE) {
require(targets.length == values.length, "TimelockController: length mismatch"); require(targets.length == values.length, "TimelockController: length mismatch");
require(targets.length == datas.length, "TimelockController: length mismatch"); require(targets.length == datas.length, "TimelockController: length mismatch");
......
...@@ -5,6 +5,8 @@ pragma solidity ^0.8.0; ...@@ -5,6 +5,8 @@ pragma solidity ^0.8.0;
/** /**
* @dev Interface of the ERC1271 standard signature validation method for * @dev Interface of the ERC1271 standard signature validation method for
* contracts as defined in https://eips.ethereum.org/EIPS/eip-1271[ERC-1271]. * contracts as defined in https://eips.ethereum.org/EIPS/eip-1271[ERC-1271].
*
* _Available since v4.1._
*/ */
interface IERC1271Upgradeable { interface IERC1271Upgradeable {
/** /**
......
...@@ -5,6 +5,8 @@ pragma solidity ^0.8.0; ...@@ -5,6 +5,8 @@ pragma solidity ^0.8.0;
/** /**
* @dev Interface of the ERC3156 FlashBorrower, as defined in * @dev Interface of the ERC3156 FlashBorrower, as defined in
* https://eips.ethereum.org/EIPS/eip-3156[ERC-3156]. * https://eips.ethereum.org/EIPS/eip-3156[ERC-3156].
*
* _Available since v4.1._
*/ */
interface IERC3156FlashBorrowerUpgradeable { interface IERC3156FlashBorrowerUpgradeable {
/** /**
......
...@@ -21,5 +21,7 @@ contract AccessControlEnumerableMockUpgradeable is Initializable, AccessControlE ...@@ -21,5 +21,7 @@ contract AccessControlEnumerableMockUpgradeable is Initializable, AccessControlE
function setRoleAdmin(bytes32 roleId, bytes32 adminRoleId) public { function setRoleAdmin(bytes32 roleId, bytes32 adminRoleId) public {
_setRoleAdmin(roleId, adminRoleId); _setRoleAdmin(roleId, adminRoleId);
} }
function senderProtected(bytes32 roleId) public onlyRole(roleId) {}
uint256[50] private __gap; uint256[50] private __gap;
} }
...@@ -20,5 +20,7 @@ contract AccessControlMockUpgradeable is Initializable, AccessControlUpgradeable ...@@ -20,5 +20,7 @@ contract AccessControlMockUpgradeable is Initializable, AccessControlUpgradeable
function setRoleAdmin(bytes32 roleId, bytes32 adminRoleId) public { function setRoleAdmin(bytes32 roleId, bytes32 adminRoleId) public {
_setRoleAdmin(roleId, adminRoleId); _setRoleAdmin(roleId, adminRoleId);
} }
function senderProtected(bytes32 roleId) public onlyRole(roleId) {}
uint256[50] private __gap; uint256[50] private __gap;
} }
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../utils/StorageSlotUpgradeable.sol";
import "../proxy/utils/Initializable.sol";
contract StorageSlotMockUpgradeable is Initializable {
function __StorageSlotMock_init() internal initializer {
__StorageSlotMock_init_unchained();
}
function __StorageSlotMock_init_unchained() internal initializer {
}
using StorageSlotUpgradeable for bytes32;
function setBoolean(bytes32 slot, bool value) public { slot.getBooleanSlot().value = value; }
function setAddress(bytes32 slot, address value) public { slot.getAddressSlot().value = value; }
function setBytes32(bytes32 slot, bytes32 value) public { slot.getBytes32Slot().value = value; }
function setUint256(bytes32 slot, uint256 value) public { slot.getUint256Slot().value = value; }
function getBoolean(bytes32 slot) public view returns (bool) { return slot.getBooleanSlot().value; }
function getAddress(bytes32 slot) public view returns (address) { return slot.getAddressSlot().value; }
function getBytes32(bytes32 slot) public view returns (bytes32) { return slot.getBytes32Slot().value; }
function getUint256(bytes32 slot) public view returns (uint256) { return slot.getUint256Slot().value; }
uint256[50] private __gap;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../CountersImplUpgradeable.sol";
import "../../proxy/utils/UUPSUpgradeable.sol";
import "../../proxy/utils/Initializable.sol";
contract UUPSUpgradeableMockUpgradeable is Initializable, CountersImplUpgradeable, UUPSUpgradeable {
function __UUPSUpgradeableMock_init() internal initializer {
__CountersImpl_init_unchained();
__ERC1967Storage_init_unchained();
__ERC1967Upgrade_init_unchained();
__UUPSUpgradeable_init_unchained();
__UUPSUpgradeableMock_init_unchained();
}
function __UUPSUpgradeableMock_init_unchained() internal initializer {
}
// Not having any checks in this function is dangerous! Do not do this outside tests!
function _authorizeUpgrade(address) internal virtual override {}
uint256[50] private __gap;
}
contract UUPSUpgradeableUnsafeMockUpgradeable is Initializable, UUPSUpgradeableMockUpgradeable {
function __UUPSUpgradeableUnsafeMock_init() internal initializer {
__CountersImpl_init_unchained();
__ERC1967Storage_init_unchained();
__ERC1967Upgrade_init_unchained();
__UUPSUpgradeable_init_unchained();
__UUPSUpgradeableMock_init_unchained();
__UUPSUpgradeableUnsafeMock_init_unchained();
}
function __UUPSUpgradeableUnsafeMock_init_unchained() internal initializer {
}
function upgradeTo(address newImplementation) external virtual override {
ERC1967UpgradeUpgradeable._upgradeToAndCall(newImplementation, bytes(""), false);
}
function upgradeToAndCall(address newImplementation, bytes memory data) external payable virtual override {
ERC1967UpgradeUpgradeable._upgradeToAndCall(newImplementation, data, false);
}
uint256[50] private __gap;
}
contract UUPSUpgradeableBrokenMockUpgradeable is Initializable, UUPSUpgradeableMockUpgradeable {
function __UUPSUpgradeableBrokenMock_init() internal initializer {
__CountersImpl_init_unchained();
__ERC1967Storage_init_unchained();
__ERC1967Upgrade_init_unchained();
__UUPSUpgradeable_init_unchained();
__UUPSUpgradeableMock_init_unchained();
__UUPSUpgradeableBrokenMock_init_unchained();
}
function __UUPSUpgradeableBrokenMock_init_unchained() internal initializer {
}
function upgradeTo(address) external virtual override {
// pass
}
function upgradeToAndCall(address, bytes memory) external payable virtual override {
// pass
}
uint256[50] private __gap;
}
...@@ -153,6 +153,41 @@ contract ERC1155PausableMockUpgradeableWithInit is ERC1155PausableMockUpgradeabl ...@@ -153,6 +153,41 @@ contract ERC1155PausableMockUpgradeableWithInit is ERC1155PausableMockUpgradeabl
__ERC1155PausableMock_init(uri); __ERC1155PausableMock_init(uri);
} }
} }
import "./StorageSlotMockUpgradeable.sol";
contract StorageSlotMockUpgradeableWithInit is StorageSlotMockUpgradeable {
constructor() public payable {
__StorageSlotMock_init();
}
}
import "./UUPS/TestInProdUpgradeable.sol";
contract UUPSUpgradeableMockUpgradeableWithInit is UUPSUpgradeableMockUpgradeable {
constructor() public payable {
__UUPSUpgradeableMock_init();
}
}
import "./UUPS/TestInProdUpgradeable.sol";
contract UUPSUpgradeableUnsafeMockUpgradeableWithInit is UUPSUpgradeableUnsafeMockUpgradeable {
constructor() public payable {
__UUPSUpgradeableUnsafeMock_init();
}
}
import "./UUPS/TestInProdUpgradeable.sol";
contract UUPSUpgradeableBrokenMockUpgradeableWithInit is UUPSUpgradeableBrokenMockUpgradeable {
constructor() public payable {
__UUPSUpgradeableBrokenMock_init();
}
}
import "./CountersImplUpgradeable.sol";
contract CountersImplUpgradeableWithInit is CountersImplUpgradeable {
constructor() public payable {
__CountersImpl_init();
}
}
import "./OwnableMockUpgradeable.sol"; import "./OwnableMockUpgradeable.sol";
contract OwnableMockUpgradeableWithInit is OwnableMockUpgradeable { contract OwnableMockUpgradeableWithInit is OwnableMockUpgradeable {
...@@ -561,13 +596,6 @@ contract ERC20SnapshotMockUpgradeableWithInit is ERC20SnapshotMockUpgradeable { ...@@ -561,13 +596,6 @@ contract ERC20SnapshotMockUpgradeableWithInit is ERC20SnapshotMockUpgradeable {
__ERC20SnapshotMock_init(name, symbol, initialAccount, initialBalance); __ERC20SnapshotMock_init(name, symbol, initialAccount, initialBalance);
} }
} }
import "./CountersImplUpgradeable.sol";
contract CountersImplUpgradeableWithInit is CountersImplUpgradeable {
constructor() public payable {
__CountersImpl_init();
}
}
import "./AccessControlEnumerableMockUpgradeable.sol"; import "./AccessControlEnumerableMockUpgradeable.sol";
contract AccessControlEnumerableMockUpgradeableWithInit is AccessControlEnumerableMockUpgradeable { contract AccessControlEnumerableMockUpgradeableWithInit is AccessControlEnumerableMockUpgradeable {
......
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../beacon/IBeaconUpgradeable.sol";
import "../../utils/AddressUpgradeable.sol";
import "../../utils/StorageSlotUpgradeable.sol";
import "../utils/Initializable.sol";
/**
* @dev This abstract contract provides setters and getters for the different
* https://eips.ethereum.org/EIPS/eip-1967[EIP1967] storage slots.
*
* _Available since v4.1._
*/
abstract contract ERC1967StorageUpgradeable is Initializable {
function __ERC1967Storage_init() internal initializer {
__ERC1967Storage_init_unchained();
}
function __ERC1967Storage_init_unchained() internal initializer {
}
/**
* @dev Storage slot with the address of the current implementation.
* This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is
* validated in the constructor.
*/
bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
/**
* @dev Returns the current implementation address.
*/
function _getImplementation() internal view returns (address) {
return StorageSlotUpgradeable.getAddressSlot(_IMPLEMENTATION_SLOT).value;
}
/**
* @dev Stores a new address in the EIP1967 implementation slot.
*/
function _setImplementation(address newImplementation) internal {
require(AddressUpgradeable.isContract(newImplementation), "ERC1967: new implementation is not a contract");
StorageSlotUpgradeable.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
}
/**
* @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy.
* This is bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1)) and is validated in the constructor.
*/
bytes32 internal constant _BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;
/**
* @dev Returns the current beacon.
*/
function _getBeacon() internal view returns (address) {
return StorageSlotUpgradeable.getAddressSlot(_BEACON_SLOT).value;
}
/**
* @dev Stores a new beacon in the EIP1967 beacon slot.
*/
function _setBeacon(address newBeacon) internal {
require(
AddressUpgradeable.isContract(newBeacon),
"ERC1967: new beacon is not a contract"
);
require(
AddressUpgradeable.isContract(IBeaconUpgradeable(newBeacon).implementation()),
"ERC1967: beacon implementation is not a contract"
);
StorageSlotUpgradeable.getAddressSlot(_BEACON_SLOT).value = newBeacon;
}
/**
* @dev Storage slot with the admin of the contract.
* This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is
* validated in the constructor.
*/
bytes32 internal constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
/**
* @dev Returns the current admin.
*/
function _getAdmin() internal view returns (address) {
return StorageSlotUpgradeable.getAddressSlot(_ADMIN_SLOT).value;
}
/**
* @dev Stores a new address in the EIP1967 admin slot.
*/
function _setAdmin(address newAdmin) internal {
require(newAdmin != address(0), "ERC1967: new admin is the zero address");
StorageSlotUpgradeable.getAddressSlot(_ADMIN_SLOT).value = newAdmin;
}
uint256[50] private __gap;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.2;
import "./ERC1967StorageUpgradeable.sol";
import "../utils/Initializable.sol";
/**
* @dev This abstract contract provides event emitting update functions for
* https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots.
*
* _Available since v4.1._
*
* @custom:oz-upgrades-unsafe-allow delegatecall
*/
abstract contract ERC1967UpgradeUpgradeable is Initializable, ERC1967StorageUpgradeable {
function __ERC1967Upgrade_init() internal initializer {
__ERC1967Storage_init_unchained();
__ERC1967Upgrade_init_unchained();
}
function __ERC1967Upgrade_init_unchained() internal initializer {
}
// This is the keccak-256 hash of "eip1967.proxy.rollback" subtracted by 1
bytes32 private constant _ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143;
/**
* @dev Emitted when the implementation is upgraded.
*/
event Upgraded(address indexed implementation);
/**
* @dev Emitted when the beacon is upgraded.
*/
event BeaconUpgraded(address indexed beacon);
/**
* @dev Emitted when the admin account has changed.
*/
event AdminChanged(address previousAdmin, address newAdmin);
/**
* @dev Perform implementation upgrade
*
* Emits an {Upgraded} event.
*/
function _upgradeTo(address newImplementation) internal {
_setImplementation(newImplementation);
emit Upgraded(newImplementation);
}
/**
* @dev Perform implementation upgrade with additional setup call.
*
* Emits an {Upgraded} event.
*/
function _upgradeToAndCall(address newImplementation, bytes memory data, bool forceCall) internal {
_setImplementation(newImplementation);
emit Upgraded(newImplementation);
if (data.length > 0 || forceCall) {
_functionDelegateCall(newImplementation, data);
}
}
/**
* @dev Perform implementation upgrade with security checks for UUPS proxies, and additional setup call.
*
* Emits an {Upgraded} event.
*/
function _upgradeToAndCallSecure(address newImplementation, bytes memory data, bool forceCall) internal {
address oldImplementation = _getImplementation();
// Initial upgrade and setup call
_setImplementation(newImplementation);
if (data.length > 0 || forceCall) {
_functionDelegateCall(newImplementation, data);
}
// Perform rollback test if not already in progress
StorageSlotUpgradeable.BooleanSlot storage rollbackTesting = StorageSlotUpgradeable.getBooleanSlot(_ROLLBACK_SLOT);
if (!rollbackTesting.value) {
// Trigger rollback using upgradeTo from the new implementation
rollbackTesting.value = true;
_functionDelegateCall(
newImplementation,
abi.encodeWithSignature(
"upgradeTo(address)",
oldImplementation
)
);
rollbackTesting.value = false;
// Check rollback was effective
require(oldImplementation == _getImplementation(), "ERC1967Upgrade: upgrade breaks further upgrades");
// Finally reset to the new implementation and log the upgrade
_setImplementation(newImplementation);
emit Upgraded(newImplementation);
}
}
/**
* @dev Perform beacon upgrade with additional setup call. Note: This upgrades the address of the beacon, it does
* not upgrade the implementation contained in the beacon (see {UpgradeableBeacon-_setImplementation} for that).
*
* Emits a {BeaconUpgraded} event.
*/
function _upgradeBeaconToAndCall(address newBeacon, bytes memory data, bool forceCall) internal {
_setBeacon(newBeacon);
emit BeaconUpgraded(newBeacon);
if (data.length > 0 || forceCall) {
_functionDelegateCall(IBeaconUpgradeable(newBeacon).implementation(), data);
}
}
/**
* @dev Changes the admin of the proxy.
*
* Emits an {AdminChanged} event.
*/
function _changeAdmin(address newAdmin) internal {
emit AdminChanged(_getAdmin(), newAdmin);
_setAdmin(newAdmin);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function _functionDelegateCall(address target, bytes memory data) private returns (bytes memory) {
require(AddressUpgradeable.isContract(target), "Address: delegate call to non-contract");
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory returndata) = target.delegatecall(data);
return _verifyCallResult(success, returndata, "Address: low-level delegate call failed");
}
function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private pure returns(bytes memory) {
if (success) {
return returndata;
} else {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
// solhint-disable-next-line no-inline-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
uint256[50] private __gap;
}
...@@ -9,12 +9,29 @@ The abstract {Proxy} contract implements the core delegation functionality. If t ...@@ -9,12 +9,29 @@ The abstract {Proxy} contract implements the core delegation functionality. If t
{ERC1967Proxy} provides a simple, fully functioning, proxy. While this proxy is not by itself upgradeable, it includes an internal upgrade interface. For an upgrade interface exposed externally to an admin, we provide {TransparentUpgradeableProxy}. Both of these contracts use the storage slots specified in https://eips.ethereum.org/EIPS/eip-1967[EIP1967] to avoid clashes with the storage of the implementation contract behind the proxy. {ERC1967Proxy} provides a simple, fully functioning, proxy. While this proxy is not by itself upgradeable, it includes an internal upgrade interface. For an upgrade interface exposed externally to an admin, we provide {TransparentUpgradeableProxy}. Both of these contracts use the storage slots specified in https://eips.ethereum.org/EIPS/eip-1967[EIP1967] to avoid clashes with the storage of the implementation contract behind the proxy.
An alternative upgradeability mechanism is provided in <<Beacon>>. This pattern, popularized by Dharma, allows multiple proxies to be upgraded to a different implementation in a single transaction. In this pattern, the proxy contract doesn't hold the implementation address in storage like {UpgradeableProxy}, but the address of a {UpgradeableBeacon} contract, which is where the implementation address is actually stored and retrieved from. The `upgrade` operations that change the implementation contract address are then sent to the beacon instead of to the proxy contract, and all proxies that follow that beacon are automatically upgraded. An alternative upgradeability mechanism is provided in <<Beacon>>. This pattern, popularized by Dharma, allows multiple proxies to be upgraded to a different implementation in a single transaction. In this pattern, the proxy contract doesn't hold the implementation address in storage like {ERC1967Proxy}, but the address of a {UpgradeableBeacon} contract, which is where the implementation address is actually stored and retrieved from. The `upgrade` operations that change the implementation contract address are then sent to the beacon instead of to the proxy contract, and all proxies that follow that beacon are automatically upgraded.
The {Clones} library provides a way to deploy minimal non-upgradeable proxies for cheap. This can be useful for applications that require deploying many instances of the same contract (for example one per user, or one per task). These instances are designed to be both cheap to deploy, and cheap to call. The drawback being that they are not upgradeable. The {Clones} library provides a way to deploy minimal non-upgradeable proxies for cheap. This can be useful for applications that require deploying many instances of the same contract (for example one per user, or one per task). These instances are designed to be both cheap to deploy, and cheap to call. The drawback being that they are not upgradeable.
CAUTION: Using upgradeable proxies correctly and securely is a difficult task that requires deep knowledge of the proxy pattern, Solidity, and the EVM. Unless you want a lot of low level control, we recommend using the xref:upgrades-plugins::index.adoc[OpenZeppelin Upgrades Plugins] for Truffle and Hardhat. CAUTION: Using upgradeable proxies correctly and securely is a difficult task that requires deep knowledge of the proxy pattern, Solidity, and the EVM. Unless you want a lot of low level control, we recommend using the xref:upgrades-plugins::index.adoc[OpenZeppelin Upgrades Plugins] for Truffle and Hardhat.
== UUPS Design: Upgradeability as an Implementation Feature
Upgradeable smart contracts rely on proxies to relay the calls in a way that is programmable. As discussed previously, we provide different proxy contracts that each come with a specific set of features. Other designs, not (yet) proposed as part of the OpenZeppelin products, also exist.
The most simple, and common, design is known as Universal Upgradeable Proxy Standard (UUPS). This design is both lightweight and versatile and is proposed through the {ERC1967Proxy} and the {UUPSUpgradeable} contract.
While {UUPSUpgradeable} uses the same interface as {TransparentUpgradeableProxy}, in the first case the upgrade is handled by the implementation, and can eventually be removed. {TransparentUpgradeableProxy}, on the other hand, includes the upgrade logic in the proxy. This means {TransparentUpgradeableProxy} is more expensive to deploy. Note that, since both proxies use the same storage slot for the implementation address, using a UUPS compliant implementation with a {TransparentUpgradeableProxy} might allow non-admins to perform upgrade operations.
According to this design, the {ERC1967Proxy} is only capable of forwarding calls to an implementation contract. Unlike the more complex and expensive to deploy {TransparentUpgradeableProxy}, the {ERC1967Proxy} doesn't by itself provide any upgradeability mechanism. It is the role of the implementation to include, alongside the contract's logic, all the code necessary to update the implementation's address that is stored at a specific slot in the proxy's storage space. This is where the {UUPSUpgradeable} contract comes in. Inheriting from it (and overriding the {_authorizeUpgrade} function with the relevant access control mechanism) will turn your contract into a UUPS complaint implementation.
By default, the upgrade mechanism included in {UUPSUpgradeable} contains a security mechanism that will prevent any upgrades to a non UUPS compliant implementation. This prevents upgrades to an implementation contract that wouldn't contain the necessary upgrade mechanism, as it would lock the proxy forever. This security mechanism can however be bypassed, either by:
- Adding a flag mechanism in the implementation that will disable the upgrade function when triggered;
- Upgrading to an implementation that features an upgrade mechanism without the additional security check, and then upgrade again to another implementation without the upgrade mechanism.
When doing an upgrade, the second parameter of the {upgradeToAndCall} function allows for the atomic execution of an optional initialization/migration call.
== Core == Core
{{Proxy}} {{Proxy}}
...@@ -23,6 +40,14 @@ CAUTION: Using upgradeable proxies correctly and securely is a difficult task th ...@@ -23,6 +40,14 @@ CAUTION: Using upgradeable proxies correctly and securely is a difficult task th
{{ERC1967Proxy}} {{ERC1967Proxy}}
{{ERC1967Storage}}
{{ERC1967Upgrade}}
== UUPS
{{UUPSUpgradeable}}
== Transparent Proxy == Transparent Proxy
{{TransparentUpgradeableProxy}} {{TransparentUpgradeableProxy}}
......
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @dev This is the interface that {BeaconProxy} expects of its beacon.
*/
interface IBeaconUpgradeable {
/**
* @dev Must return an address that can be used as a delegate call target.
*
* {BeaconProxy} will check that this address is a contract.
*/
function implementation() external view returns (address);
}
...@@ -3,8 +3,6 @@ ...@@ -3,8 +3,6 @@
// solhint-disable-next-line compiler-version // solhint-disable-next-line compiler-version
pragma solidity ^0.8.0; pragma solidity ^0.8.0;
import "../../utils/AddressUpgradeable.sol";
/** /**
* @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
* behind a proxy. Since a proxied contract can't have a constructor, it's common to move constructor logic to an * behind a proxy. Since a proxied contract can't have a constructor, it's common to move constructor logic to an
...@@ -12,7 +10,7 @@ import "../../utils/AddressUpgradeable.sol"; ...@@ -12,7 +10,7 @@ import "../../utils/AddressUpgradeable.sol";
* function so it can only be called once. The {initializer} modifier provided by this contract will have this effect. * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
* *
* TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
* possible by providing the encoded function call as the `_data` argument to {UpgradeableProxy-constructor}. * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
* *
* CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
* that all initializers are idempotent. This is not verified automatically as constructors are by Solidity. * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
......
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../ERC1967/ERC1967UpgradeUpgradeable.sol";
import "./Initializable.sol";
/**
* @dev Base contract for building openzeppelin-upgrades compatible implementations for the {ERC1967Proxy}. It includes
* publicly available upgrade functions that are called by the plugin and by the secure upgrade mechanism to verify
* continuation of the upgradability.
*
* The {_authorizeUpgrade} function MUST be overridden to include access restriction to the upgrade mechanism.
*
* _Available since v4.1._
*/
abstract contract UUPSUpgradeable is Initializable, ERC1967UpgradeUpgradeable {
function __UUPSUpgradeable_init() internal initializer {
__ERC1967Storage_init_unchained();
__ERC1967Upgrade_init_unchained();
__UUPSUpgradeable_init_unchained();
}
function __UUPSUpgradeable_init_unchained() internal initializer {
}
function upgradeTo(address newImplementation) external virtual {
_authorizeUpgrade(newImplementation);
_upgradeToAndCallSecure(newImplementation, bytes(""), false);
}
function upgradeToAndCall(address newImplementation, bytes memory data) external payable virtual {
_authorizeUpgrade(newImplementation);
_upgradeToAndCallSecure(newImplementation, data, true);
}
function _authorizeUpgrade(address newImplementation) internal virtual;
uint256[50] private __gap;
}
...@@ -137,11 +137,57 @@ contract ERC1155Upgradeable is Initializable, ContextUpgradeable, ERC165Upgradea ...@@ -137,11 +137,57 @@ contract ERC1155Upgradeable is Initializable, ContextUpgradeable, ERC165Upgradea
virtual virtual
override override
{ {
require(to != address(0), "ERC1155: transfer to the zero address");
require( require(
from == _msgSender() || isApprovedForAll(from, _msgSender()), from == _msgSender() || isApprovedForAll(from, _msgSender()),
"ERC1155: caller is not owner nor approved" "ERC1155: caller is not owner nor approved"
); );
_safeTransferFrom(from, to, id, amount, data);
}
/**
* @dev See {IERC1155-safeBatchTransferFrom}.
*/
function safeBatchTransferFrom(
address from,
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
)
public
virtual
override
{
require(
from == _msgSender() || isApprovedForAll(from, _msgSender()),
"ERC1155: transfer caller is not owner nor approved"
);
_safeBatchTransferFrom(from, to, ids, amounts, data);
}
/**
* @dev Transfers `amount` tokens of token type `id` from `from` to `to`.
*
* Emits a {TransferSingle} event.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - `from` must have a balance of tokens of type `id` of at least `amount`.
* - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
* acceptance magic value.
*/
function _safeTransferFrom(
address from,
address to,
uint256 id,
uint256 amount,
bytes memory data
)
internal
virtual
{
require(to != address(0), "ERC1155: transfer to the zero address");
address operator = _msgSender(); address operator = _msgSender();
...@@ -158,25 +204,27 @@ contract ERC1155Upgradeable is Initializable, ContextUpgradeable, ERC165Upgradea ...@@ -158,25 +204,27 @@ contract ERC1155Upgradeable is Initializable, ContextUpgradeable, ERC165Upgradea
} }
/** /**
* @dev See {IERC1155-safeBatchTransferFrom}. * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_safeTransferFrom}.
*
* Emits a {TransferBatch} event.
*
* Requirements:
*
* - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
* acceptance magic value.
*/ */
function safeBatchTransferFrom( function _safeBatchTransferFrom(
address from, address from,
address to, address to,
uint256[] memory ids, uint256[] memory ids,
uint256[] memory amounts, uint256[] memory amounts,
bytes memory data bytes memory data
) )
public internal
virtual virtual
override
{ {
require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch"); require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");
require(to != address(0), "ERC1155: transfer to the zero address"); require(to != address(0), "ERC1155: transfer to the zero address");
require(
from == _msgSender() || isApprovedForAll(from, _msgSender()),
"ERC1155: transfer caller is not owner nor approved"
);
address operator = _msgSender(); address operator = _msgSender();
......
...@@ -6,6 +6,8 @@ import "../IERC20Upgradeable.sol"; ...@@ -6,6 +6,8 @@ import "../IERC20Upgradeable.sol";
/** /**
* @dev Interface for the optional metadata functions from the ERC20 standard. * @dev Interface for the optional metadata functions from the ERC20 standard.
*
* _Available since v4.1._
*/ */
interface IERC20MetadataUpgradeable is IERC20Upgradeable { interface IERC20MetadataUpgradeable is IERC20Upgradeable {
/** /**
......
...@@ -12,6 +12,8 @@ import "../../../proxy/utils/Initializable.sol"; ...@@ -12,6 +12,8 @@ import "../../../proxy/utils/Initializable.sol";
* *
* Adds the {flashLoan} method, which provides flash loan support at the token * Adds the {flashLoan} method, which provides flash loan support at the token
* level. By default there is no fee, but this can be changed by overriding {flashFee}. * level. By default there is no fee, but this can be changed by overriding {flashFee}.
*
* _Available since v4.1._
*/ */
abstract contract ERC20FlashMintUpgradeable is Initializable, ERC20Upgradeable, IERC3156FlashLenderUpgradeable { abstract contract ERC20FlashMintUpgradeable is Initializable, ERC20Upgradeable, IERC3156FlashLenderUpgradeable {
function __ERC20FlashMint_init() internal initializer { function __ERC20FlashMint_init() internal initializer {
......
...@@ -85,6 +85,8 @@ abstract contract ERC20PermitUpgradeable is Initializable, ERC20Upgradeable, IER ...@@ -85,6 +85,8 @@ abstract contract ERC20PermitUpgradeable is Initializable, ERC20Upgradeable, IER
/** /**
* @dev "Consume a nonce": return the current value and increment. * @dev "Consume a nonce": return the current value and increment.
*
* _Available since v4.1._
*/ */
function _useNonce(address owner) internal virtual returns (uint256 current) { function _useNonce(address owner) internal virtual returns (uint256 current) {
CountersUpgradeable.Counter storage nonce = _nonces[owner]; CountersUpgradeable.Counter storage nonce = _nonces[owner];
......
...@@ -7,6 +7,8 @@ import "../proxy/utils/Initializable.sol"; ...@@ -7,6 +7,8 @@ import "../proxy/utils/Initializable.sol";
/** /**
* @dev Provides a function to batch together multiple calls in a single external call. * @dev Provides a function to batch together multiple calls in a single external call.
*
* _Available since v4.1._
*/ */
abstract contract MulticallUpgradeable is Initializable { abstract contract MulticallUpgradeable is Initializable {
function __Multicall_init() internal initializer { function __Multicall_init() internal initializer {
...@@ -26,6 +28,12 @@ abstract contract MulticallUpgradeable is Initializable { ...@@ -26,6 +28,12 @@ abstract contract MulticallUpgradeable is Initializable {
return results; return results;
} }
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function _functionDelegateCall(address target, bytes memory data) private returns (bytes memory) { function _functionDelegateCall(address target, bytes memory data) private returns (bytes memory) {
require(AddressUpgradeable.isContract(target), "Address: delegate call to non-contract"); require(AddressUpgradeable.isContract(target), "Address: delegate call to non-contract");
...@@ -52,6 +60,5 @@ abstract contract MulticallUpgradeable is Initializable { ...@@ -52,6 +60,5 @@ abstract contract MulticallUpgradeable is Initializable {
} }
} }
} }
uint256[50] private __gap; uint256[50] private __gap;
} }
...@@ -96,4 +96,6 @@ Note that, in all cases, accounts simply _declare_ their interfaces, but they ar ...@@ -96,4 +96,6 @@ Note that, in all cases, accounts simply _declare_ their interfaces, but they ar
{{Strings}} {{Strings}}
{{StorageSlot}}
{{Multicall}} {{Multicall}}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @dev Library for reading and writing primitive types to specific storage slots.
*
* Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
* This library helps with reading and writing to such slots without the need for inline assembly.
*
* The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
*
* Example usage to set ERC1967 implementation slot:
* ```
* contract ERC1967 {
* bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
*
* function _getImplementation() internal view returns (address) {
* return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
* }
*
* function _setImplementation(address newImplementation) internal {
* require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract");
* StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
* }
* }
* ```
*
* _Available since v4.1 for `address`, `bool`, `bytes32`, and `uint256`._
*/
library StorageSlotUpgradeable {
struct AddressSlot {
address value;
}
struct BooleanSlot {
bool value;
}
struct Bytes32Slot {
bytes32 value;
}
struct Uint256Slot {
uint256 value;
}
/**
* @dev Returns an `AddressSlot` with member `value` located at `slot`.
*/
function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `BooleanSlot` with member `value` located at `slot`.
*/
function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `Bytes32Slot` with member `value` located at `slot`.
*/
function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `Uint256Slot` with member `value` located at `slot`.
*/
function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
assembly {
r.slot := slot
}
}
}
...@@ -31,7 +31,7 @@ library ECDSAUpgradeable { ...@@ -31,7 +31,7 @@ library ECDSAUpgradeable {
// Check the signature length // Check the signature length
// - case 65: r,s,v signature (standard) // - case 65: r,s,v signature (standard)
// - case 64: r,vs signature (cf https://eips.ethereum.org/EIPS/eip-2098) // - case 64: r,vs signature (cf https://eips.ethereum.org/EIPS/eip-2098) _Available since v4.1._
if (signature.length == 65) { if (signature.length == 65) {
// ecrecover takes the signature parameters, and the only way to get them // ecrecover takes the signature parameters, and the only way to get them
// currently is to use assembly. // currently is to use assembly.
......
...@@ -13,6 +13,8 @@ import "../../interfaces/IERC1271Upgradeable.sol"; ...@@ -13,6 +13,8 @@ import "../../interfaces/IERC1271Upgradeable.sol";
* *
* Note: unlike ECDSA signatures, contract signature's are revocable, and the outcome of this function can thus change * Note: unlike ECDSA signatures, contract signature's are revocable, and the outcome of this function can thus change
* through time. It could return true at block N and false at block N+1 (or the opposite). * through time. It could return true at block N and false at block N+1 (or the opposite).
*
* _Available since v4.1._
*/ */
library SignatureCheckerUpgradeable { library SignatureCheckerUpgradeable {
function isValidSignatureNow(address signer, bytes32 hash, bytes memory signature) internal view returns (bool) { function isValidSignatureNow(address signer, bytes32 hash, bytes memory signature) internal view returns (bool) {
......
...@@ -81,15 +81,14 @@ library EnumerableSetUpgradeable { ...@@ -81,15 +81,14 @@ library EnumerableSetUpgradeable {
uint256 toDeleteIndex = valueIndex - 1; uint256 toDeleteIndex = valueIndex - 1;
uint256 lastIndex = set._values.length - 1; uint256 lastIndex = set._values.length - 1;
// When the value to delete is the last one, the swap operation is unnecessary. However, since this occurs if (lastIndex != toDeleteIndex) {
// so rarely, we still do the swap anyway to avoid the gas cost of adding an 'if' statement. bytes32 lastvalue = set._values[lastIndex];
bytes32 lastvalue = set._values[lastIndex]; // Move the last value to the index where the value to delete is
set._values[toDeleteIndex] = lastvalue;
// Move the last value to the index where the value to delete is // Update the index for the moved value
set._values[toDeleteIndex] = lastvalue; set._indexes[lastvalue] = valueIndex; // Replace lastvalue's index to valueIndex
// Update the index for the moved value }
set._indexes[lastvalue] = valueIndex; // Replace lastvalue's index to valueIndex
// Delete the slot where the moved value was stored // Delete the slot where the moved value was stored
set._values.pop(); set._values.pop();
...@@ -128,7 +127,6 @@ library EnumerableSetUpgradeable { ...@@ -128,7 +127,6 @@ library EnumerableSetUpgradeable {
* - `index` must be strictly less than {length}. * - `index` must be strictly less than {length}.
*/ */
function _at(Set storage set, uint256 index) private view returns (bytes32) { function _at(Set storage set, uint256 index) private view returns (bytes32) {
require(set._values.length > index, "EnumerableSet: index out of bounds");
return set._values[index]; return set._values[index];
} }
......
...@@ -46,7 +46,7 @@ In this way you can use _composability_ to add additional layers of access contr ...@@ -46,7 +46,7 @@ In this way you can use _composability_ to add additional layers of access contr
While the simplicity of _ownership_ can be useful for simple systems or quick prototyping, different levels of authorization are often needed. You may want for an account to have permission to ban users from a system, but not create new tokens. https://en.wikipedia.org/wiki/Role-based_access_control[_Role-Based Access Control (RBAC)_] offers flexibility in this regard. While the simplicity of _ownership_ can be useful for simple systems or quick prototyping, different levels of authorization are often needed. You may want for an account to have permission to ban users from a system, but not create new tokens. https://en.wikipedia.org/wiki/Role-based_access_control[_Role-Based Access Control (RBAC)_] offers flexibility in this regard.
In essence, we will be defining multiple _roles_, each allowed to perform different sets of actions. An account may have, for example, 'moderator', 'minter' or 'admin' roles, which you will then check for instead of simply using `onlyOwner`. Separately, you will be able to define rules for how accounts can be granted a role, have it revoked, and more. In essence, we will be defining multiple _roles_, each allowed to perform different sets of actions. An account may have, for example, 'moderator', 'minter' or 'admin' roles, which you will then check for instead of simply using `onlyOwner`. This check can be enforced through the `onlyRole` modifier. Separately, you will be able to define rules for how accounts can be granted a role, have it revoked, and more.
Most software uses access control systems that are role-based: some users are regular users, some may be supervisors or managers, and a few will often have administrative privileges. Most software uses access control systems that are role-based: some users are regular users, some may be supervisors or managers, and a few will often have administrative privileges.
...@@ -88,7 +88,7 @@ NOTE: Make sure you fully understand how xref:api:access.adoc#AccessControl[`Acc ...@@ -88,7 +88,7 @@ NOTE: Make sure you fully understand how xref:api:access.adoc#AccessControl[`Acc
While clear and explicit, this isn't anything we wouldn't have been able to achieve with `Ownable`. Indeed, where `AccessControl` shines is in scenarios where granular permissions are required, which can be implemented by defining _multiple_ roles. While clear and explicit, this isn't anything we wouldn't have been able to achieve with `Ownable`. Indeed, where `AccessControl` shines is in scenarios where granular permissions are required, which can be implemented by defining _multiple_ roles.
Let's augment our ERC20 token example by also defining a 'burner' role, which lets accounts destroy tokens: Let's augment our ERC20 token example by also defining a 'burner' role, which lets accounts destroy tokens, and by using the `onlyRole` modifier:
[source,solidity] [source,solidity]
---- ----
...@@ -108,13 +108,11 @@ contract MyToken is ERC20, AccessControl { ...@@ -108,13 +108,11 @@ contract MyToken is ERC20, AccessControl {
_setupRole(BURNER_ROLE, burner); _setupRole(BURNER_ROLE, burner);
} }
function mint(address to, uint256 amount) public { function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) {
require(hasRole(MINTER_ROLE, msg.sender), "Caller is not a minter");
_mint(to, amount); _mint(to, amount);
} }
function burn(address from, uint256 amount) public { function burn(address from, uint256 amount) public onlyRole(BURNER_ROLE) {
require(hasRole(BURNER_ROLE, msg.sender), "Caller is not a burner");
_burn(from, amount); _burn(from, amount);
} }
} }
...@@ -154,13 +152,11 @@ contract MyToken is ERC20, AccessControl { ...@@ -154,13 +152,11 @@ contract MyToken is ERC20, AccessControl {
_setupRole(DEFAULT_ADMIN_ROLE, msg.sender); _setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
} }
function mint(address to, uint256 amount) public { function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) {
require(hasRole(MINTER_ROLE, msg.sender), "Caller is not a minter");
_mint(to, amount); _mint(to, amount);
} }
function burn(address from, uint256 amount) public { function burn(address from, uint256 amount) public onlyRole(BURNER_ROLE) {
require(hasRole(BURNER_ROLE, msg.sender), "Caller is not a burner");
_burn(from, amount); _burn(from, amount);
} }
} }
......
...@@ -70,7 +70,7 @@ function decimals() public view virtual override returns (uint8) { ...@@ -70,7 +70,7 @@ function decimals() public view virtual override returns (uint8) {
} }
``` ```
So if you want to send `5` tokens using a token contract with 18 decimals, the the method to call will actually be: So if you want to send `5` tokens using a token contract with 18 decimals, the method to call will actually be:
```solidity ```solidity
transfer(recipient, 5 * 10^18); transfer(recipient, 5 * 10^18);
......
...@@ -16,7 +16,6 @@ Here's what a contract for tokenized items might look like: ...@@ -16,7 +16,6 @@ Here's what a contract for tokenized items might look like:
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity ^0.8.0; pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/utils/Counters.sol"; import "@openzeppelin/contracts/utils/Counters.sol";
...@@ -41,7 +40,7 @@ contract GameItem is ERC721URIStorage { ...@@ -41,7 +40,7 @@ contract GameItem is ERC721URIStorage {
} }
---- ----
The xref:api:token/ERC721.adoc#ERC721[`ERC721`] contract includes all standard extensions (xref:api:token/ERC721.adoc#IERC721Metadata[`IERC721Metadata`] and xref:api:token/ERC721.adoc#IERC721Enumerable[`IERC721Enumerable`]). That's where the xref:api:token/ERC721.adoc#ERC721-_setTokenURI-uint256-string-[`_setTokenURI`] method comes from: we use it to store an item's metadata. The xref:api:token/ERC721.adoc#ERC721URIStorage[`ERC721URIStorage`] contract is an implementation of ERC721 that includes all standard extensions (xref:api:token/ERC721.adoc#IERC721Metadata[`IERC721Metadata`] and xref:api:token/ERC721.adoc#IERC721Enumerable[`IERC721Enumerable`]). That's where the xref:api:token/ERC721.adoc#ERC721-_setTokenURI-uint256-string-[`_setTokenURI`] method comes from: we use it to store an item's metadata.
Also note that, unlike ERC20, ERC721 lacks a `decimals` field, since each token is distinct and cannot be partitioned. Also note that, unlike ERC20, ERC721 lacks a `decimals` field, since each token is distinct and cannot be partitioned.
......
diff --git a/contracts/mocks/AddressImplUpgradeable.sol b/contracts/mocks/AddressImplUpgradeable.sol
index 4e92f588..4cbcabb8 100644
--- a/contracts/mocks/AddressImplUpgradeable.sol
+++ b/contracts/mocks/AddressImplUpgradeable.sol
@@ -39,11 +39,6 @@ contract AddressImplUpgradeable is Initializable {
emit CallReturnValue(abi.decode(returnData, (string)));
}
- function functionDelegateCall(address target, bytes calldata data) external {
- bytes memory returnData = AddressUpgradeable.functionDelegateCall(target, data);
- emit CallReturnValue(abi.decode(returnData, (string)));
- }
-
// sendValue's tests require the contract to hold Ether
receive () external payable { }
uint256[49] private __gap;
diff --git a/contracts/utils/AddressUpgradeable.sol b/contracts/utils/AddressUpgradeable.sol
index 89241895..99c1ce78 100644
--- a/contracts/utils/AddressUpgradeable.sol
+++ b/contracts/utils/AddressUpgradeable.sol
@@ -144,30 +144,6 @@ library AddressUpgradeable {
return _verifyCallResult(success, returndata, errorMessage);
}
- /**
- * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
- * but performing a delegate call.
- *
- * _Available since v3.4._
- */
- function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
- return functionDelegateCall(target, data, "Address: low-level delegate call failed");
- }
-
- /**
- * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
- * but performing a delegate call.
- *
- * _Available since v3.4._
- */
- function functionDelegateCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
- require(isContract(target), "Address: delegate call to non-contract");
-
- // solhint-disable-next-line avoid-low-level-calls
- (bool success, bytes memory returndata) = target.delegatecall(data);
- return _verifyCallResult(success, returndata, errorMessage);
- }
-
function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private pure returns(bytes memory) {
if (success) {
return returndata;
diff --git a/contracts/utils/MulticallUpgradeable.sol b/contracts/utils/MulticallUpgradeable.sol
index 62d5047c..a535dfc5 100644
--- a/contracts/utils/MulticallUpgradeable.sol
+++ b/contracts/utils/MulticallUpgradeable.sol
@@ -21,9 +21,37 @@ abstract contract MulticallUpgradeable is Initializable {
function multicall(bytes[] calldata data) external returns (bytes[] memory results) {
results = new bytes[](data.length);
for (uint i = 0; i < data.length; i++) {
- results[i] = AddressUpgradeable.functionDelegateCall(address(this), data[i]);
+ results[i] = _functionDelegateCall(address(this), data[i]);
}
return results;
}
+
+ function _functionDelegateCall(address target, bytes memory data) private returns (bytes memory) {
+ require(AddressUpgradeable.isContract(target), "Address: delegate call to non-contract");
+
+ // solhint-disable-next-line avoid-low-level-calls
+ (bool success, bytes memory returndata) = target.delegatecall(data);
+ return _verifyCallResult(success, returndata, "Address: low-level delegate call failed");
+ }
+
+ function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private pure returns(bytes memory) {
+ if (success) {
+ return returndata;
+ } else {
+ // Look for revert reason and bubble it up if present
+ if (returndata.length > 0) {
+ // The easiest way to bubble the revert reason is using memory via assembly
+
+ // solhint-disable-next-line no-inline-assembly
+ assembly {
+ let returndata_size := mload(returndata)
+ revert(add(32, returndata), returndata_size)
+ }
+ } else {
+ revert(errorMessage);
+ }
+ }
+ }
+
uint256[50] private __gap;
}
...@@ -12,6 +12,9 @@ npx @openzeppelin/upgrade-safe-transpiler -D \ ...@@ -12,6 +12,9 @@ npx @openzeppelin/upgrade-safe-transpiler -D \
-i contracts/proxy/utils/Initializable.sol \ -i contracts/proxy/utils/Initializable.sol \
-x 'contracts/proxy/**/*' \ -x 'contracts/proxy/**/*' \
-x '!contracts/proxy/Clones.sol' \ -x '!contracts/proxy/Clones.sol' \
-x '!contracts/proxy/ERC1967/ERC1967{Storage,Upgrade}.sol' \
-x '!contracts/proxy/utils/UUPSUpgradeable.sol' \
-x '!contracts/proxy/beacon/IBeacon.sol' \
-p 'contracts/**/presets/**/*' -p 'contracts/**/presets/**/*'
for p in scripts/upgradeable/patch/*.patch; do for p in scripts/upgradeable/patch/*.patch; do
......
...@@ -25,17 +25,14 @@ function shouldBehaveLikeAccessControl (errorPrefix, admin, authorized, other, o ...@@ -25,17 +25,14 @@ function shouldBehaveLikeAccessControl (errorPrefix, admin, authorized, other, o
}); });
describe('granting', function () { describe('granting', function () {
it('admin can grant role to other accounts', async function () { beforeEach(async function () {
const receipt = await this.accessControl.grantRole(ROLE, authorized, { from: admin }); await this.accessControl.grantRole(ROLE, authorized, { from: admin });
expectEvent(receipt, 'RoleGranted', { account: authorized, role: ROLE, sender: admin });
expect(await this.accessControl.hasRole(ROLE, authorized)).to.equal(true);
}); });
it('non-admin cannot grant role to other accounts', async function () { it('non-admin cannot grant role to other accounts', async function () {
await expectRevert( await expectRevert(
this.accessControl.grantRole(ROLE, authorized, { from: other }), this.accessControl.grantRole(ROLE, authorized, { from: other }),
`${errorPrefix}: sender must be an admin to grant`, `${errorPrefix}: account ${other.toLowerCase()} is missing role ${DEFAULT_ADMIN_ROLE}`,
); );
}); });
...@@ -69,7 +66,7 @@ function shouldBehaveLikeAccessControl (errorPrefix, admin, authorized, other, o ...@@ -69,7 +66,7 @@ function shouldBehaveLikeAccessControl (errorPrefix, admin, authorized, other, o
it('non-admin cannot revoke role', async function () { it('non-admin cannot revoke role', async function () {
await expectRevert( await expectRevert(
this.accessControl.revokeRole(ROLE, authorized, { from: other }), this.accessControl.revokeRole(ROLE, authorized, { from: other }),
`${errorPrefix}: sender must be an admin to revoke`, `${errorPrefix}: account ${other.toLowerCase()} is missing role ${DEFAULT_ADMIN_ROLE}`,
); );
}); });
...@@ -146,14 +143,38 @@ function shouldBehaveLikeAccessControl (errorPrefix, admin, authorized, other, o ...@@ -146,14 +143,38 @@ function shouldBehaveLikeAccessControl (errorPrefix, admin, authorized, other, o
it('a role\'s previous admins no longer grant roles', async function () { it('a role\'s previous admins no longer grant roles', async function () {
await expectRevert( await expectRevert(
this.accessControl.grantRole(ROLE, authorized, { from: admin }), this.accessControl.grantRole(ROLE, authorized, { from: admin }),
'AccessControl: sender must be an admin to grant', `${errorPrefix}: account ${admin.toLowerCase()} is missing role ${OTHER_ROLE}`,
); );
}); });
it('a role\'s previous admins no longer revoke roles', async function () { it('a role\'s previous admins no longer revoke roles', async function () {
await expectRevert( await expectRevert(
this.accessControl.revokeRole(ROLE, authorized, { from: admin }), this.accessControl.revokeRole(ROLE, authorized, { from: admin }),
'AccessControl: sender must be an admin to revoke', `${errorPrefix}: account ${admin.toLowerCase()} is missing role ${OTHER_ROLE}`,
);
});
});
describe('onlyRole modifier', function () {
beforeEach(async function () {
await this.accessControl.grantRole(ROLE, authorized, { from: admin });
});
it('do not revert if sender has role', async function () {
await this.accessControl.senderProtected(ROLE, { from: authorized });
});
it('revert if sender doesn\'t have role #1', async function () {
await expectRevert(
this.accessControl.senderProtected(ROLE, { from: other }),
`${errorPrefix}: account ${other.toLowerCase()} is missing role ${ROLE}`,
);
});
it('revert if sender doesn\'t have role #2', async function () {
await expectRevert(
this.accessControl.senderProtected(OTHER_ROLE, { from: authorized }),
`${errorPrefix}: account ${authorized.toLowerCase()} is missing role ${OTHER_ROLE}`,
); );
}); });
}); });
......
...@@ -53,6 +53,9 @@ contract('TimelockController', function (accounts) { ...@@ -53,6 +53,9 @@ contract('TimelockController', function (accounts) {
[ executor ], [ executor ],
{ from: admin }, { from: admin },
); );
this.TIMELOCK_ADMIN_ROLE = await this.timelock.TIMELOCK_ADMIN_ROLE();
this.PROPOSER_ROLE = await this.timelock.PROPOSER_ROLE();
this.EXECUTOR_ROLE = await this.timelock.EXECUTOR_ROLE();
// Mocks // Mocks
this.callreceivermock = await CallReceiverMock.new({ from: admin }); this.callreceivermock = await CallReceiverMock.new({ from: admin });
this.implementation2 = await Implementation2.new({ from: admin }); this.implementation2 = await Implementation2.new({ from: admin });
...@@ -172,7 +175,7 @@ contract('TimelockController', function (accounts) { ...@@ -172,7 +175,7 @@ contract('TimelockController', function (accounts) {
MINDELAY, MINDELAY,
{ from: other }, { from: other },
), ),
'TimelockController: sender requires permission', `AccessControl: account ${other.toLowerCase()} is missing role ${this.PROPOSER_ROLE}`,
); );
}); });
...@@ -295,7 +298,7 @@ contract('TimelockController', function (accounts) { ...@@ -295,7 +298,7 @@ contract('TimelockController', function (accounts) {
this.operation.salt, this.operation.salt,
{ from: other }, { from: other },
), ),
'TimelockController: sender requires permission', `AccessControl: account ${other.toLowerCase()} is missing role ${this.EXECUTOR_ROLE}`,
); );
}); });
}); });
...@@ -409,7 +412,7 @@ contract('TimelockController', function (accounts) { ...@@ -409,7 +412,7 @@ contract('TimelockController', function (accounts) {
MINDELAY, MINDELAY,
{ from: other }, { from: other },
), ),
'TimelockController: sender requires permission', `AccessControl: account ${other.toLowerCase()} is missing role ${this.PROPOSER_ROLE}`,
); );
}); });
...@@ -534,7 +537,7 @@ contract('TimelockController', function (accounts) { ...@@ -534,7 +537,7 @@ contract('TimelockController', function (accounts) {
this.operation.salt, this.operation.salt,
{ from: other }, { from: other },
), ),
'TimelockController: sender requires permission', `AccessControl: account ${other.toLowerCase()} is missing role ${this.EXECUTOR_ROLE}`,
); );
}); });
...@@ -663,7 +666,7 @@ contract('TimelockController', function (accounts) { ...@@ -663,7 +666,7 @@ contract('TimelockController', function (accounts) {
it('prevent non-proposer from canceling', async function () { it('prevent non-proposer from canceling', async function () {
await expectRevert( await expectRevert(
this.timelock.cancel(this.operation.id, { from: other }), this.timelock.cancel(this.operation.id, { from: other }),
'TimelockController: sender requires permission', `AccessControl: account ${other.toLowerCase()} is missing role ${this.PROPOSER_ROLE}`,
); );
}); });
}); });
......
const { expectEvent, expectRevert } = require('@openzeppelin/test-helpers');
const ERC1967Proxy = artifacts.require('ERC1967Proxy');
const UUPSUpgradeableMock = artifacts.require('UUPSUpgradeableMock');
const UUPSUpgradeableUnsafeMock = artifacts.require('UUPSUpgradeableUnsafeMock');
const UUPSUpgradeableBrokenMock = artifacts.require('UUPSUpgradeableBrokenMock');
const CountersImpl = artifacts.require('CountersImpl');
contract('UUPSUpgradeable', function (accounts) {
before(async function () {
this.implInitial = await UUPSUpgradeableMock.new();
this.implUpgradeOk = await UUPSUpgradeableMock.new();
this.implUpgradeUnsafe = await UUPSUpgradeableUnsafeMock.new();
this.implUpgradeBroken = await UUPSUpgradeableBrokenMock.new();
this.implUpgradeNonUUPS = await CountersImpl.new();
});
beforeEach(async function () {
const { address } = await ERC1967Proxy.new(this.implInitial.address, '0x');
this.instance = await UUPSUpgradeableMock.at(address);
});
it('upgrade to upgradeable implementation', async function () {
const { receipt } = await this.instance.upgradeTo(this.implUpgradeOk.address);
expect(receipt.logs.filter(({ event }) => event === 'Upgraded').length).to.be.equal(1);
expectEvent(receipt, 'Upgraded', { implementation: this.implUpgradeOk.address });
});
it('upgrade to upgradeable implementation with call', async function () {
expect(await this.instance.current()).to.be.bignumber.equal('0');
const { receipt } = await this.instance.upgradeToAndCall(
this.implUpgradeOk.address,
this.implUpgradeOk.contract.methods.increment().encodeABI(),
);
expect(receipt.logs.filter(({ event }) => event === 'Upgraded').length).to.be.equal(1);
expectEvent(receipt, 'Upgraded', { implementation: this.implUpgradeOk.address });
expect(await this.instance.current()).to.be.bignumber.equal('1');
});
it('upgrade to and unsafe upgradeable implementation', async function () {
const { receipt } = await this.instance.upgradeTo(this.implUpgradeUnsafe.address);
expectEvent(receipt, 'Upgraded', { implementation: this.implUpgradeUnsafe.address });
});
it('reject upgrade to broken upgradeable implementation', async function () {
await expectRevert(
this.instance.upgradeTo(this.implUpgradeBroken.address),
'ERC1967Upgrade: upgrade breaks further upgrades',
);
});
// delegate to a non existing upgradeTo function causes a low level revert
it('reject upgrade to non uups implementation', async function () {
await expectRevert(
this.instance.upgradeTo(this.implUpgradeNonUUPS.address),
'Address: low-level delegate call failed',
);
});
it('reject proxy address as implementation', async function () {
const { address } = await ERC1967Proxy.new(this.implInitial.address, '0x');
const otherInstance = await UUPSUpgradeableMock.at(address);
// infinite loop reverts when a nested call is out-of-gas
await expectRevert(
this.instance.upgradeTo(otherInstance.address),
'Address: low-level delegate call failed',
);
});
});
...@@ -25,7 +25,7 @@ contract('BeaconProxy', function (accounts) { ...@@ -25,7 +25,7 @@ contract('BeaconProxy', function (accounts) {
it('non-contract beacon', async function () { it('non-contract beacon', async function () {
await expectRevert( await expectRevert(
BeaconProxy.new(anotherAccount, '0x'), BeaconProxy.new(anotherAccount, '0x'),
'BeaconProxy: beacon is not a contract', 'ERC1967: new beacon is not a contract',
); );
}); });
...@@ -40,7 +40,7 @@ contract('BeaconProxy', function (accounts) { ...@@ -40,7 +40,7 @@ contract('BeaconProxy', function (accounts) {
const beacon = await BadBeaconNotContract.new(); const beacon = await BadBeaconNotContract.new();
await expectRevert( await expectRevert(
BeaconProxy.new(beacon.address, '0x'), BeaconProxy.new(beacon.address, '0x'),
'BeaconProxy: beacon implementation is not a contract', 'ERC1967: beacon implementation is not a contract',
); );
}); });
}); });
......
...@@ -80,7 +80,7 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy (createPro ...@@ -80,7 +80,7 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy (createPro
it('reverts', async function () { it('reverts', async function () {
await expectRevert( await expectRevert(
this.proxy.upgradeTo(ZERO_ADDRESS, { from }), this.proxy.upgradeTo(ZERO_ADDRESS, { from }),
'ERC1967Proxy: new implementation is not a contract', 'ERC1967: new implementation is not a contract',
); );
}); });
}); });
...@@ -304,7 +304,7 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy (createPro ...@@ -304,7 +304,7 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy (createPro
it('reverts', async function () { it('reverts', async function () {
await expectRevert( await expectRevert(
this.proxy.changeAdmin(ZERO_ADDRESS, { from: proxyAdminAddress }), this.proxy.changeAdmin(ZERO_ADDRESS, { from: proxyAdminAddress }),
'TransparentUpgradeableProxy: new admin is the zero address', 'ERC1967: new admin is the zero address',
); );
}); });
}); });
......
const { constants, BN } = require('@openzeppelin/test-helpers');
const { expect } = require('chai');
const StorageSlotMock = artifacts.require('StorageSlotMock');
const slot = web3.utils.keccak256('some.storage.slot');
const otherSlot = web3.utils.keccak256('some.other.storage.slot');
contract('StorageSlot', function (accounts) {
beforeEach(async function () {
this.store = await StorageSlotMock.new();
});
describe('boolean storage slot', function () {
beforeEach(async function () {
this.value = true;
});
it('set', async function () {
await this.store.setBoolean(slot, this.value);
});
describe('get', function () {
beforeEach(async function () {
await this.store.setBoolean(slot, this.value);
});
it('from right slot', async function () {
expect(await this.store.getBoolean(slot)).to.be.equal(this.value);
});
it('from other slot', async function () {
expect(await this.store.getBoolean(otherSlot)).to.be.equal(false);
});
});
});
describe('address storage slot', function () {
beforeEach(async function () {
this.value = accounts[1];
});
it('set', async function () {
await this.store.setAddress(slot, this.value);
});
describe('get', function () {
beforeEach(async function () {
await this.store.setAddress(slot, this.value);
});
it('from right slot', async function () {
expect(await this.store.getAddress(slot)).to.be.equal(this.value);
});
it('from other slot', async function () {
expect(await this.store.getAddress(otherSlot)).to.be.equal(constants.ZERO_ADDRESS);
});
});
});
describe('bytes32 storage slot', function () {
beforeEach(async function () {
this.value = web3.utils.keccak256('some byte32 value');
});
it('set', async function () {
await this.store.setBytes32(slot, this.value);
});
describe('get', function () {
beforeEach(async function () {
await this.store.setBytes32(slot, this.value);
});
it('from right slot', async function () {
expect(await this.store.getBytes32(slot)).to.be.equal(this.value);
});
it('from other slot', async function () {
expect(await this.store.getBytes32(otherSlot)).to.be.equal(constants.ZERO_BYTES32);
});
});
});
describe('uint256 storage slot', function () {
beforeEach(async function () {
this.value = new BN(1742);
});
it('set', async function () {
await this.store.setUint256(slot, this.value);
});
describe('get', function () {
beforeEach(async function () {
await this.store.setUint256(slot, this.value);
});
it('from right slot', async function () {
expect(await this.store.getUint256(slot)).to.be.bignumber.equal(this.value);
});
it('from other slot', async function () {
expect(await this.store.getUint256(otherSlot)).to.be.bignumber.equal('0');
});
});
});
});
...@@ -51,7 +51,7 @@ function shouldBehaveLikeSet (valueA, valueB, valueC) { ...@@ -51,7 +51,7 @@ function shouldBehaveLikeSet (valueA, valueB, valueC) {
describe('at', function () { describe('at', function () {
it('reverts when retrieving non-existent elements', async function () { it('reverts when retrieving non-existent elements', async function () {
await expectRevert(this.set.at(0), 'EnumerableSet: index out of bounds'); await expectRevert.unspecified(this.set.at(0));
}); });
}); });
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment