Commit 6d202894 by Hadrien Croubois

Merge branch 'master'

parents b8187aa5 18c7efe8
comment: off comment: off
github_checks:
annotations: false
coverage: coverage:
range: "100...100" status:
patch:
default:
target: 95%
project:
default:
threshold: 1%
<!-- 0. 🎉 Thank you for submitting a PR! --> <!-- Thank you for your interest in contributing to OpenZeppelin! -->
<!-- 1. Does this close any open issues? Please list them below. --> <!-- Consider opening an issue for discussion prior to submitting a PR. -->
<!-- New features will be merged faster if they were first discussed and designed with the team. -->
<!-- Keep in mind that new features have a better chance of being merged fast if Fixes #???? <!-- Fill in with issue number -->
they were first discussed and designed with the maintainers. If there is no
corresponding issue, please consider opening one for discussion first! -->
Fixes # <!-- Describe the changes introduced in this pull request. -->
<!-- Include any context necessary for understanding the PR's purpose. -->
<!-- 2. Describe the changes introduced in this pull request. -->
<!-- Include any context necessary for understanding the PR's purpose. -->
<!-- 3. Before submitting, please make sure that you have: #### PR Checklist
- reviewed the OpenZeppelin Contributor Guidelines
(https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/CONTRIBUTING.md), <!-- Before merging the pull request all of the following must be complete. -->
- added tests where applicable to test new functionality, <!-- Feel free to submit a PR or Draft PR even if some items are pending. -->
- made sure that your contracts are well-documented, <!-- Some of the items may not apply. -->
- run the Solidity linter (`npm run lint:sol`) and fixed any issues,
- run the JS linter and fixed any issues (`npm run lint:fix`), and - [ ] Tests
- updated the changelog, if applicable. - [ ] Documentation
--> - [ ] Changelog entry
# Configuration for probot-stale - https://github.com/probot/stale
# Number of days of inactivity before an Issue or Pull Request becomes stale
daysUntilStale: 15
# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
daysUntilClose: 15
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
exemptLabels:
- on hold
- meta
# Set to true to ignore issues in a project (defaults to false)
exemptProjects: false
# Set to true to ignore issues in a milestone (defaults to false)
exemptMilestones: false
# Set to true to ignore issues with an assignee (defaults to false)
exemptAssignees: false
# Label to use when marking as stale
staleLabel: stale
# Comment to post when marking as stale. Set to `false` to disable
markComment: >
Hi all!
This Pull Request has not had any recent activity, is it still relevant? If so, what is blocking it?
Is there anything we can do to help move it forward?
Thanks!
# Comment to post when removing the stale label.
# unmarkComment: >
# Your comment here.
# Comment to post when closing a stale Issue or Pull Request.
closeComment: >
Hi folks!
This Pull Request is being closed as there was no response to the previous prompt.
However, please leave a comment whenever you're ready to resume, so it can be reopened.
Thanks again!
# Limit the number of actions per hour, from 1-30. Default is 30
limitPerRun: 30
# Limit to only `issues` or `pulls`
only: pulls
# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls':
# pulls:
# daysUntilStale: 30
# markComment: >
# This pull request has been automatically marked as stale because it has not had
# recent activity. It will be closed if no further activity occurs. Thank you
# for your contributions.
# issues:
# exemptLabels:
# - confirmed
...@@ -12,7 +12,7 @@ jobs: ...@@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions/setup-node@v1 - uses: actions/setup-node@v2
with: with:
node-version: 10.x node-version: 10.x
- uses: actions/cache@v2 - uses: actions/cache@v2
...@@ -30,7 +30,7 @@ jobs: ...@@ -30,7 +30,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions/setup-node@v1 - uses: actions/setup-node@v2
with: with:
node-version: 10.x node-version: 10.x
- uses: actions/cache@v2 - uses: actions/cache@v2
......
...@@ -8,4 +8,8 @@ module.exports = { ...@@ -8,4 +8,8 @@ module.exports = {
providerOptions: { providerOptions: {
default_balance_ether: '10000000000000000000000000', default_balance_ether: '10000000000000000000000000',
}, },
mocha: {
fgrep: '[skip-on-coverage]',
invert: true,
},
} }
...@@ -2,8 +2,32 @@ ...@@ -2,8 +2,32 @@
## Unreleased ## Unreleased
* `Address`: added `functionStaticCall` and `functionDelegateCall`, similar to the existing `functionCall`. ([#2333](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2333)) * `BeaconProxy`: added new kind of proxy that allows simultaneous atomic upgrades. ([#2411](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2411))
* `TimelockController`: added a contract to augment access control schemes with a delay. ([#2364](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2364)) * `EIP712`: added helpers to verify EIP712 typed data signatures on chain. ([#2418](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2418))
* `ERC20Permit`: added an implementation of the ERC20 permit extension for gasless token approvals. ([#2237](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2237))
* Presets: added token presets with preminted fixed supply `ERC20PresetFixedSupply` and `ERC777PresetFixedSupply`. ([#2399](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2399))
* `Address`: added `functionDelegateCall`, similar to the existing `functionCall`. ([#2333](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2333))
* `Clones`: added a library for deploying EIP 1167 minimal proxies. ([#2449](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2449))
* `Context`: moved from `contracts/GSN` to `contracts/utils`. ([#2453](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2453))
* `PaymentSplitter`: replace usage of `.transfer()` with `Address.sendValue` for improved compatibility with smart wallets. ([#2455](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2455))
* `UpgradeableProxy`: bubble revert reasons from initialization calls. ([#2454](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2454))
* `SafeMath`: fix a memory allocation issue by adding new `SafeMath.tryOp(uint,uint)→(bool,uint)` functions. `SafeMath.op(uint,uint,string)→uint` are now deprecated. ([#2462](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2462))
* `EnumerableMap`: fix a memory allocation issue by adding new `EnumerableMap.tryGet(uint)→(bool,address)` functions. `EnumerableMap.get(uint)→string` is now deprecated. ([#2462](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2462))
* `ERC165Checker`: added batch `getSupportedInterfaces`. ([#2469](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2469))
* `RefundEscrow`: `beneficiaryWithdraw` will forward all available gas to the beneficiary. ([#2480](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2480))
* Many view and pure functions have been made virtual to customize them via overrides. In many cases this will not imply that other functions in the contract will automatically adapt to the overridden definitions. People who wish to override should consult the source code to understand the impact and if they need to override any additional functions to achieve the desired behavior.
### Security Fixes
* `ERC777`: fix potential reentrancy issues for custom extensions to `ERC777`. ([#2483](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2483))
If you're using our implementation of ERC777 from version 3.3.0 or earlier, and you define a custom `_beforeTokenTransfer` function that writes to a storage variable, you may be vulnerable to a reentrancy attack. If you're affected and would like assistance please write to security@openzeppelin.com. [Read more in the pull request.](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2483)
## 3.3.0 (2020-11-26)
* Now supports both Solidity 0.6 and 0.7. Compiling with solc 0.7 will result in warnings. Install the `solc-0.7` tag to compile without warnings.
* `Address`: added `functionStaticCall`, similar to the existing `functionCall`. ([#2333](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2333))
* `TimelockController`: added a contract to augment access control schemes with a delay. ([#2354](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2354))
* `EnumerableSet`: added `Bytes32Set`, for sets of `bytes32`. ([#2395](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2395)) * `EnumerableSet`: added `Bytes32Set`, for sets of `bytes32`. ([#2395](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2395))
## 3.2.2-solc-0.7 (2020-10-28) ## 3.2.2-solc-0.7 (2020-10-28)
......
...@@ -2,23 +2,4 @@ ...@@ -2,23 +2,4 @@
pragma solidity ^0.7.0; pragma solidity ^0.7.0;
/* import "../utils/Context.sol";
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with GSN meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address payable) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes memory) {
this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
return msg.data;
}
}
...@@ -2,9 +2,9 @@ ...@@ -2,9 +2,9 @@
pragma solidity ^0.7.0; pragma solidity ^0.7.0;
import "../utils/Context.sol";
import "./IRelayRecipient.sol"; import "./IRelayRecipient.sol";
import "./IRelayHub.sol"; import "./IRelayHub.sol";
import "./Context.sol";
/** /**
* @dev Base GSN recipient contract: includes the {IRelayRecipient} interface * @dev Base GSN recipient contract: includes the {IRelayRecipient} interface
...@@ -35,7 +35,7 @@ abstract contract GSNRecipient is IRelayRecipient, Context { ...@@ -35,7 +35,7 @@ abstract contract GSNRecipient is IRelayRecipient, Context {
/** /**
* @dev Returns the address of the {IRelayHub} contract for this recipient. * @dev Returns the address of the {IRelayHub} contract for this recipient.
*/ */
function getHubAddr() public view override returns (address) { function getHubAddr() public view virtual override returns (address) {
return _relayHub; return _relayHub;
} }
...@@ -62,7 +62,7 @@ abstract contract GSNRecipient is IRelayRecipient, Context { ...@@ -62,7 +62,7 @@ abstract contract GSNRecipient is IRelayRecipient, Context {
*/ */
// This function is view for future-proofing, it may require reading from // This function is view for future-proofing, it may require reading from
// storage in the future. // storage in the future.
function relayHubVersion() public view returns (string memory) { function relayHubVersion() public view virtual returns (string memory) {
this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691 this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
return "1.0.0"; return "1.0.0";
} }
...@@ -73,7 +73,7 @@ abstract contract GSNRecipient is IRelayRecipient, Context { ...@@ -73,7 +73,7 @@ abstract contract GSNRecipient is IRelayRecipient, Context {
* Derived contracts should expose this in an external interface with proper access control. * Derived contracts should expose this in an external interface with proper access control.
*/ */
function _withdrawDeposits(uint256 amount, address payable payee) internal virtual { function _withdrawDeposits(uint256 amount, address payable payee) internal virtual {
IRelayHub(_relayHub).withdraw(amount, payee); IRelayHub(getHubAddr()).withdraw(amount, payee);
} }
// Overrides for Context's functions: when called from RelayHub, sender and // Overrides for Context's functions: when called from RelayHub, sender and
...@@ -88,7 +88,7 @@ abstract contract GSNRecipient is IRelayRecipient, Context { ...@@ -88,7 +88,7 @@ abstract contract GSNRecipient is IRelayRecipient, Context {
* IMPORTANT: Contracts derived from {GSNRecipient} should never use `msg.sender`, and use {_msgSender} instead. * IMPORTANT: Contracts derived from {GSNRecipient} should never use `msg.sender`, and use {_msgSender} instead.
*/ */
function _msgSender() internal view virtual override returns (address payable) { function _msgSender() internal view virtual override returns (address payable) {
if (msg.sender != _relayHub) { if (msg.sender != getHubAddr()) {
return msg.sender; return msg.sender;
} else { } else {
return _getRelayedCallSender(); return _getRelayedCallSender();
...@@ -102,7 +102,7 @@ abstract contract GSNRecipient is IRelayRecipient, Context { ...@@ -102,7 +102,7 @@ abstract contract GSNRecipient is IRelayRecipient, Context {
* IMPORTANT: Contracts derived from {GSNRecipient} should never use `msg.data`, and use {_msgData} instead. * IMPORTANT: Contracts derived from {GSNRecipient} should never use `msg.data`, and use {_msgData} instead.
*/ */
function _msgData() internal view virtual override returns (bytes memory) { function _msgData() internal view virtual override returns (bytes memory) {
if (msg.sender != _relayHub) { if (msg.sender != getHubAddr()) {
return msg.data; return msg.data;
} else { } else {
return _getRelayedCallData(); return _getRelayedCallData();
...@@ -162,7 +162,7 @@ abstract contract GSNRecipient is IRelayRecipient, Context { ...@@ -162,7 +162,7 @@ abstract contract GSNRecipient is IRelayRecipient, Context {
* @dev Return this in acceptRelayedCall to proceed with the execution of a relayed call. Note that this contract * @dev Return this in acceptRelayedCall to proceed with the execution of a relayed call. Note that this contract
* will be charged a fee by RelayHub * will be charged a fee by RelayHub
*/ */
function _approveRelayedCall() internal pure returns (uint256, bytes memory) { function _approveRelayedCall() internal pure virtual returns (uint256, bytes memory) {
return _approveRelayedCall(""); return _approveRelayedCall("");
} }
...@@ -171,14 +171,14 @@ abstract contract GSNRecipient is IRelayRecipient, Context { ...@@ -171,14 +171,14 @@ abstract contract GSNRecipient is IRelayRecipient, Context {
* *
* This overload forwards `context` to _preRelayedCall and _postRelayedCall. * This overload forwards `context` to _preRelayedCall and _postRelayedCall.
*/ */
function _approveRelayedCall(bytes memory context) internal pure returns (uint256, bytes memory) { function _approveRelayedCall(bytes memory context) internal pure virtual returns (uint256, bytes memory) {
return (_RELAYED_CALL_ACCEPTED, context); return (_RELAYED_CALL_ACCEPTED, context);
} }
/** /**
* @dev Return this in acceptRelayedCall to impede execution of a relayed call. No fees will be charged. * @dev Return this in acceptRelayedCall to impede execution of a relayed call. No fees will be charged.
*/ */
function _rejectRelayedCall(uint256 errorCode) internal pure returns (uint256, bytes memory) { function _rejectRelayedCall(uint256 errorCode) internal pure virtual returns (uint256, bytes memory) {
return (_RELAYED_CALL_REJECTED + errorCode, ""); return (_RELAYED_CALL_REJECTED + errorCode, "");
} }
...@@ -186,7 +186,7 @@ abstract contract GSNRecipient is IRelayRecipient, Context { ...@@ -186,7 +186,7 @@ abstract contract GSNRecipient is IRelayRecipient, Context {
* @dev Calculates how much RelayHub will charge a recipient for using `gas` at a `gasPrice`, given a relayer's * @dev Calculates how much RelayHub will charge a recipient for using `gas` at a `gasPrice`, given a relayer's
* `serviceFee`. * `serviceFee`.
*/ */
function _computeCharge(uint256 gas, uint256 gasPrice, uint256 serviceFee) internal pure returns (uint256) { function _computeCharge(uint256 gas, uint256 gasPrice, uint256 serviceFee) internal pure virtual returns (uint256) {
// The fee is expressed as a percentage. E.g. a value of 40 stands for a 40% fee, so the recipient will be // The fee is expressed as a percentage. E.g. a value of 40 stands for a 40% fee, so the recipient will be
// charged for 1.4 times the spent amount. // charged for 1.4 times the spent amount.
return (gas * gasPrice * (100 + serviceFee)) / 100; return (gas * gasPrice * (100 + serviceFee)) / 100;
......
...@@ -37,15 +37,15 @@ contract GSNRecipientERC20Fee is GSNRecipient { ...@@ -37,15 +37,15 @@ contract GSNRecipientERC20Fee is GSNRecipient {
/** /**
* @dev Returns the gas payment token. * @dev Returns the gas payment token.
*/ */
function token() public view returns (IERC20) { function token() public view virtual returns (__unstable__ERC20Owned) {
return IERC20(_token); return _token;
} }
/** /**
* @dev Internal function that mints the gas payment token. Derived contracts should expose this function in their public API, with proper access control mechanisms. * @dev Internal function that mints the gas payment token. Derived contracts should expose this function in their public API, with proper access control mechanisms.
*/ */
function _mint(address account, uint256 amount) internal virtual { function _mint(address account, uint256 amount) internal virtual {
_token.mint(account, amount); token().mint(account, amount);
} }
/** /**
...@@ -68,7 +68,7 @@ contract GSNRecipientERC20Fee is GSNRecipient { ...@@ -68,7 +68,7 @@ contract GSNRecipientERC20Fee is GSNRecipient {
override override
returns (uint256, bytes memory) returns (uint256, bytes memory)
{ {
if (_token.balanceOf(from) < maxPossibleCharge) { if (token().balanceOf(from) < maxPossibleCharge) {
return _rejectRelayedCall(uint256(GSNRecipientERC20FeeErrorCodes.INSUFFICIENT_BALANCE)); return _rejectRelayedCall(uint256(GSNRecipientERC20FeeErrorCodes.INSUFFICIENT_BALANCE));
} }
...@@ -85,7 +85,7 @@ contract GSNRecipientERC20Fee is GSNRecipient { ...@@ -85,7 +85,7 @@ contract GSNRecipientERC20Fee is GSNRecipient {
(address from, uint256 maxPossibleCharge) = abi.decode(context, (address, uint256)); (address from, uint256 maxPossibleCharge) = abi.decode(context, (address, uint256));
// The maximum token charge is pre-charged from the user // The maximum token charge is pre-charged from the user
_token.safeTransferFrom(from, address(this), maxPossibleCharge); token().safeTransferFrom(from, address(this), maxPossibleCharge);
return 0; return 0;
} }
...@@ -104,7 +104,7 @@ contract GSNRecipientERC20Fee is GSNRecipient { ...@@ -104,7 +104,7 @@ contract GSNRecipientERC20Fee is GSNRecipient {
actualCharge = actualCharge.sub(overestimation); actualCharge = actualCharge.sub(overestimation);
// After the relayed call has been executed and the actual charge estimated, the excess pre-charge is returned // After the relayed call has been executed and the actual charge estimated, the excess pre-charge is returned
_token.safeTransfer(from, maxPossibleCharge.sub(actualCharge)); token().safeTransfer(from, maxPossibleCharge.sub(actualCharge));
} }
} }
...@@ -121,12 +121,12 @@ contract __unstable__ERC20Owned is ERC20, Ownable { ...@@ -121,12 +121,12 @@ contract __unstable__ERC20Owned is ERC20, Ownable {
constructor(string memory name, string memory symbol) ERC20(name, symbol) { } constructor(string memory name, string memory symbol) ERC20(name, symbol) { }
// The owner (GSNRecipientERC20Fee) can mint tokens // The owner (GSNRecipientERC20Fee) can mint tokens
function mint(address account, uint256 amount) public onlyOwner { function mint(address account, uint256 amount) public virtual onlyOwner {
_mint(account, amount); _mint(account, amount);
} }
// The owner has 'infinite' allowance for all token holders // The owner has 'infinite' allowance for all token holders
function allowance(address tokenOwner, address spender) public view override returns (uint256) { function allowance(address tokenOwner, address spender) public view virtual override returns (uint256) {
if (spender == owner()) { if (spender == owner()) {
return _UINT256_MAX; return _UINT256_MAX;
} else { } else {
...@@ -135,7 +135,7 @@ contract __unstable__ERC20Owned is ERC20, Ownable { ...@@ -135,7 +135,7 @@ contract __unstable__ERC20Owned is ERC20, Ownable {
} }
// Allowance for the owner cannot be changed (it is always 'infinite') // Allowance for the owner cannot be changed (it is always 'infinite')
function _approve(address tokenOwner, address spender, uint256 value) internal override { function _approve(address tokenOwner, address spender, uint256 value) internal virtual override {
if (spender == owner()) { if (spender == owner()) {
return; return;
} else { } else {
...@@ -143,7 +143,7 @@ contract __unstable__ERC20Owned is ERC20, Ownable { ...@@ -143,7 +143,7 @@ contract __unstable__ERC20Owned is ERC20, Ownable {
} }
} }
function transferFrom(address sender, address recipient, uint256 amount) public override returns (bool) { function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) {
if (recipient == owner()) { if (recipient == owner()) {
_transfer(sender, recipient, amount); _transfer(sender, recipient, amount);
return true; return true;
......
...@@ -4,7 +4,7 @@ pragma solidity ^0.7.0; ...@@ -4,7 +4,7 @@ pragma solidity ^0.7.0;
import "../utils/EnumerableSet.sol"; import "../utils/EnumerableSet.sol";
import "../utils/Address.sol"; import "../utils/Address.sol";
import "../GSN/Context.sol"; import "../utils/Context.sol";
/** /**
* @dev Contract module that allows children to implement role-based access * @dev Contract module that allows children to implement role-based access
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
pragma solidity ^0.7.0; pragma solidity ^0.7.0;
import "../GSN/Context.sol"; import "../utils/Context.sol";
/** /**
* @dev Contract module which provides a basic access control mechanism, where * @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to * there is an account (an owner) that can be granted exclusive access to
...@@ -32,7 +32,7 @@ abstract contract Ownable is Context { ...@@ -32,7 +32,7 @@ abstract contract Ownable is Context {
/** /**
* @dev Returns the address of the current owner. * @dev Returns the address of the current owner.
*/ */
function owner() public view returns (address) { function owner() public view virtual returns (address) {
return _owner; return _owner;
} }
...@@ -40,7 +40,7 @@ abstract contract Ownable is Context { ...@@ -40,7 +40,7 @@ abstract contract Ownable is Context {
* @dev Throws if called by any account other than the owner. * @dev Throws if called by any account other than the owner.
*/ */
modifier onlyOwner() { modifier onlyOwner() {
require(_owner == _msgSender(), "Ownable: caller is not the owner"); require(owner() == _msgSender(), "Ownable: caller is not the owner");
_; _;
} }
......
...@@ -3,17 +3,23 @@ ...@@ -3,17 +3,23 @@
[.readme-notice] [.readme-notice]
NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/api/access NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/api/access
Contract modules for authorization and access control mechanisms. This directory provides ways to restrict who can access the functions of a contract or when they can do it.
- {AccessControl} provides a general role based access control mechanism. Multiple hierarchical roles can be created and assigned each to multiple accounts.
- {Ownable} is a simpler mechanism with a single owner "role" that can be assigned to a single account. This simpler mechanism can be useful for quick tests but projects with production concerns are likely to outgrow it.
- {TimelockController} is used in combination with one of the above two mechanisms. By assigning a role to an instance of the `TimelockController` contract, the access to the functions controlled by that role will be delayed by some amount of time.
== Authorization == Authorization
{{Ownable}} {{Ownable}}
{{AccessControl}} {{AccessControl}}
== Timelock == Timelock
{{TimelockController}} {{TimelockController}}
[[timelock-terminology]]
==== Terminology ==== Terminology
* *Operation:* A transaction (or a set of transactions) that is the subject of the timelock. It has to be scheduled by a proposer and executed by an executor. The timelock enforces a minimum delay between the proposition and the execution (see xref:access-control.adoc#operation_lifecycle[operation lifecycle]). If the operation contains multiple transactions (batch mode), they are executed atomically. Operations are identified by the hash of their content. * *Operation:* A transaction (or a set of transactions) that is the subject of the timelock. It has to be scheduled by a proposer and executed by an executor. The timelock enforces a minimum delay between the proposition and the execution (see xref:access-control.adoc#operation_lifecycle[operation lifecycle]). If the operation contains multiple transactions (batch mode), they are executed atomically. Operations are identified by the hash of their content.
...@@ -27,6 +33,7 @@ Contract modules for authorization and access control mechanisms. ...@@ -27,6 +33,7 @@ Contract modules for authorization and access control mechanisms.
** *Proposer:* An address (smart contract or EOA) that is in charge of scheduling (and cancelling) operations. ** *Proposer:* An address (smart contract or EOA) that is in charge of scheduling (and cancelling) operations.
** *Executor:* An address (smart contract or EOA) that is in charge of executing operations. ** *Executor:* An address (smart contract or EOA) that is in charge of executing operations.
[[timelock-operation]]
==== Operation structure ==== Operation structure
Operation executed by the xref:api:access.adoc#TimelockController[`TimelockControler`] can contain one or multiple subsequent calls. Depending on whether you need to multiple calls to be executed atomically, you can either use simple or batched operations. Operation executed by the xref:api:access.adoc#TimelockController[`TimelockControler`] can contain one or multiple subsequent calls. Depending on whether you need to multiple calls to be executed atomically, you can either use simple or batched operations.
...@@ -46,6 +53,7 @@ const data = timelock.contract.methods.grantRole(ROLE, ACCOUNT).encodeABI() ...@@ -46,6 +53,7 @@ const data = timelock.contract.methods.grantRole(ROLE, ACCOUNT).encodeABI()
In the case of batched operations, `target`, `value` and `data` are specified as arrays, which must be of the same length. In the case of batched operations, `target`, `value` and `data` are specified as arrays, which must be of the same length.
[[timelock-operation-lifecycle]]
==== Operation lifecycle ==== Operation lifecycle
Timelocked operations are identified by a unique id (their hash) and follow a specific lifecycle: Timelocked operations are identified by a unique id (their hash) and follow a specific lifecycle:
...@@ -63,14 +71,17 @@ Operations status can be queried using the functions: ...@@ -63,14 +71,17 @@ Operations status can be queried using the functions:
* xref:api:access.adoc#TimelockController-isOperationReady-bytes32-[`isOperationReady(bytes32)`] * xref:api:access.adoc#TimelockController-isOperationReady-bytes32-[`isOperationReady(bytes32)`]
* xref:api:access.adoc#TimelockController-isOperationDone-bytes32-[`isOperationDone(bytes32)`] * xref:api:access.adoc#TimelockController-isOperationDone-bytes32-[`isOperationDone(bytes32)`]
[[timelock-roles]]
==== Roles ==== Roles
[[timelock-admin]]
===== Admin ===== Admin
The admins are in charge of managing proposers and executors. For the timelock to be self-governed, this role should only be given to the timelock itself. Upon deployment, both the timelock and the deployer have this role. After further configuration and testing, the deployer can renounce this role such that all further maintenance operations have to go through the timelock process. The admins are in charge of managing proposers and executors. For the timelock to be self-governed, this role should only be given to the timelock itself. Upon deployment, both the timelock and the deployer have this role. After further configuration and testing, the deployer can renounce this role such that all further maintenance operations have to go through the timelock process.
This role is identified by the *TIMELOCK_ADMIN_ROLE* value: `0x5f58e3a2316349923ce3780f8d587db2d72378aed66a8261c916544fa6846ca5` This role is identified by the *TIMELOCK_ADMIN_ROLE* value: `0x5f58e3a2316349923ce3780f8d587db2d72378aed66a8261c916544fa6846ca5`
[[timelock-proposer]]
===== Proposer ===== Proposer
The proposers are in charge of scheduling (and cancelling) operations. This is a critical role, that should be given to governing entities. This could be an EOA, a multisig, or a DAO. The proposers are in charge of scheduling (and cancelling) operations. This is a critical role, that should be given to governing entities. This could be an EOA, a multisig, or a DAO.
...@@ -79,6 +90,7 @@ WARNING: *Proposer fight:* Having multiple proposers, while providing redundancy ...@@ -79,6 +90,7 @@ WARNING: *Proposer fight:* Having multiple proposers, while providing redundancy
This role is identified by the *PROPOSER_ROLE* value: `0xb09aa5aeb3702cfd50b6b62bc4532604938f21248a27a1d5ca736082b6819cc1` This role is identified by the *PROPOSER_ROLE* value: `0xb09aa5aeb3702cfd50b6b62bc4532604938f21248a27a1d5ca736082b6819cc1`
[[timelock-executor]]
===== Executor ===== Executor
The executors are in charge of executing the operations scheduled by the proposers once the timelock expires. Logic dictates that multisig or DAO that are proposers should also be executors in order to guarantee operations that have been scheduled will eventually be executed. However, having additional executor can reduce the cost (the executing transaction does not require validation by the multisig or DAO that proposed it), while ensuring whoever is in charge of execution cannot trigger actions that have not been scheduled by the proposers. The executors are in charge of executing the operations scheduled by the proposers once the timelock expires. Logic dictates that multisig or DAO that are proposers should also be executors in order to guarantee operations that have been scheduled will eventually be executed. However, having additional executor can reduce the cost (the executing transaction does not require validation by the multisig or DAO that proposed it), while ensuring whoever is in charge of execution cannot trigger actions that have not been scheduled by the proposers.
......
...@@ -18,6 +18,8 @@ import "./AccessControl.sol"; ...@@ -18,6 +18,8 @@ import "./AccessControl.sol";
* is in charge of proposing (resp executing) operations. A common use case is * is in charge of proposing (resp executing) operations. A common use case is
* to position this {TimelockController} as the owner of a smart contract, with * to position this {TimelockController} as the owner of a smart contract, with
* a multisig or a DAO as the sole proposer. * a multisig or a DAO as the sole proposer.
*
* _Available since v3.3._
*/ */
contract TimelockController is AccessControl { contract TimelockController is AccessControl {
...@@ -86,45 +88,56 @@ contract TimelockController is AccessControl { ...@@ -86,45 +88,56 @@ contract TimelockController is AccessControl {
_; _;
} }
/* /**
* @dev Contract might receive/hold ETH as part of the maintenance process. * @dev Contract might receive/hold ETH as part of the maintenance process.
*/ */
receive() external payable {} receive() external payable {}
/** /**
* @dev Returns whether an id correspond to a registered operation. This
* includes both Pending, Ready and Done operations.
*/
function isOperation(bytes32 id) public view virtual returns (bool pending) {
return getTimestamp(id) > 0;
}
/**
* @dev Returns whether an operation is pending or not. * @dev Returns whether an operation is pending or not.
*/ */
function isOperationPending(bytes32 id) public view returns (bool pending) { function isOperationPending(bytes32 id) public view virtual returns (bool pending) {
return _timestamps[id] > _DONE_TIMESTAMP; return getTimestamp(id) > _DONE_TIMESTAMP;
} }
/** /**
* @dev Returns whether an operation is ready or not. * @dev Returns whether an operation is ready or not.
*/ */
function isOperationReady(bytes32 id) public view returns (bool ready) { function isOperationReady(bytes32 id) public view virtual returns (bool ready) {
uint256 timestamp = getTimestamp(id);
// solhint-disable-next-line not-rely-on-time // solhint-disable-next-line not-rely-on-time
return _timestamps[id] > _DONE_TIMESTAMP && _timestamps[id] <= block.timestamp; return timestamp > _DONE_TIMESTAMP && timestamp <= block.timestamp;
} }
/** /**
* @dev Returns whether an operation is done or not. * @dev Returns whether an operation is done or not.
*/ */
function isOperationDone(bytes32 id) public view returns (bool done) { function isOperationDone(bytes32 id) public view virtual returns (bool done) {
return _timestamps[id] == _DONE_TIMESTAMP; return getTimestamp(id) == _DONE_TIMESTAMP;
} }
/** /**
* @dev Returns the timestamp at with an operation becomes ready (0 for * @dev Returns the timestamp at with an operation becomes ready (0 for
* unset operations, 1 for done operations). * unset operations, 1 for done operations).
*/ */
function getTimestamp(bytes32 id) public view returns (uint256 timestamp) { function getTimestamp(bytes32 id) public view virtual returns (uint256 timestamp) {
return _timestamps[id]; return _timestamps[id];
} }
/** /**
* @dev Returns the minimum delay for an operation to become valid. * @dev Returns the minimum delay for an operation to become valid.
*
* This value can be changed by executing an operation that calls `updateDelay`.
*/ */
function getMinDelay() public view returns (uint256 duration) { function getMinDelay() public view virtual returns (uint256 duration) {
return _minDelay; return _minDelay;
} }
...@@ -132,7 +145,7 @@ contract TimelockController is AccessControl { ...@@ -132,7 +145,7 @@ contract TimelockController is AccessControl {
* @dev Returns the identifier of an operation containing a single * @dev Returns the identifier of an operation containing a single
* transaction. * transaction.
*/ */
function hashOperation(address target, uint256 value, bytes calldata data, bytes32 predecessor, bytes32 salt) public pure returns (bytes32 hash) { function hashOperation(address target, uint256 value, bytes calldata data, bytes32 predecessor, bytes32 salt) public pure virtual returns (bytes32 hash) {
return keccak256(abi.encode(target, value, data, predecessor, salt)); return keccak256(abi.encode(target, value, data, predecessor, salt));
} }
...@@ -140,7 +153,7 @@ contract TimelockController is AccessControl { ...@@ -140,7 +153,7 @@ contract TimelockController is AccessControl {
* @dev Returns the identifier of an operation containing a batch of * @dev Returns the identifier of an operation containing a batch of
* transactions. * transactions.
*/ */
function hashOperationBatch(address[] calldata targets, uint256[] calldata values, bytes[] calldata datas, bytes32 predecessor, bytes32 salt) public pure returns (bytes32 hash) { function hashOperationBatch(address[] calldata targets, uint256[] calldata values, bytes[] calldata datas, bytes32 predecessor, bytes32 salt) public pure virtual returns (bytes32 hash) {
return keccak256(abi.encode(targets, values, datas, predecessor, salt)); return keccak256(abi.encode(targets, values, datas, predecessor, salt));
} }
...@@ -183,8 +196,8 @@ contract TimelockController is AccessControl { ...@@ -183,8 +196,8 @@ contract TimelockController is AccessControl {
* @dev Schedule an operation that is to becomes valid after a given delay. * @dev Schedule an operation that is to becomes valid after a given delay.
*/ */
function _schedule(bytes32 id, uint256 delay) private { function _schedule(bytes32 id, uint256 delay) private {
require(_timestamps[id] == 0, "TimelockController: operation already scheduled"); require(!isOperation(id), "TimelockController: operation already scheduled");
require(delay >= _minDelay, "TimelockController: insufficient delay"); require(delay >= getMinDelay(), "TimelockController: insufficient delay");
// solhint-disable-next-line not-rely-on-time // solhint-disable-next-line not-rely-on-time
_timestamps[id] = SafeMath.add(block.timestamp, delay); _timestamps[id] = SafeMath.add(block.timestamp, delay);
} }
...@@ -269,9 +282,14 @@ contract TimelockController is AccessControl { ...@@ -269,9 +282,14 @@ contract TimelockController is AccessControl {
} }
/** /**
* @dev Changes the timelock duration for future operations. * @dev Changes the minimum timelock duration for future operations.
* *
* Emits a {MinDelayChange} event. * Emits a {MinDelayChange} event.
*
* Requirements:
*
* - the caller must be the timelock itself. This can only be achieved by scheduling and later executing
* an operation where the timelock is the target and the data is the ABI-encoded call to this function.
*/ */
function updateDelay(uint256 newDelay) external virtual { function updateDelay(uint256 newDelay) external virtual {
require(msg.sender == address(this), "TimelockController: caller must be timelock"); require(msg.sender == address(this), "TimelockController: caller must be timelock");
......
...@@ -43,6 +43,14 @@ library ECDSA { ...@@ -43,6 +43,14 @@ library ECDSA {
v := byte(0, mload(add(signature, 0x60))) v := byte(0, mload(add(signature, 0x60)))
} }
return recover(hash, v, r, s);
}
/**
* @dev Overload of {ECDSA-recover-bytes32-bytes-} that receives the `v`,
* `r` and `s` signature fields separately.
*/
function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) {
// EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
// unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
// the valid range for s in (281): 0 < s < secp256k1n ÷ 2 + 1, and for v in (282): v ∈ {27, 28}. Most // the valid range for s in (281): 0 < s < secp256k1n ÷ 2 + 1, and for v in (282): v ∈ {27, 28}. Most
......
...@@ -5,6 +5,10 @@ NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/ ...@@ -5,6 +5,10 @@ NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/
This collection of libraries provides simple and safe ways to use different cryptographic primitives. This collection of libraries provides simple and safe ways to use different cryptographic primitives.
The following related EIPs are in draft status and can be found in the drafts directory.
- {EIP712}
== Libraries == Libraries
{{ECDSA}} {{ECDSA}}
......
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
/**
* @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data.
*
* The encoding specified in the EIP is very generic, and such a generic implementation in Solidity is not feasible,
* thus this contract does not implement the encoding itself. Protocols need to implement the type-specific encoding
* they need in their contracts using a combination of `abi.encode` and `keccak256`.
*
* This contract implements the EIP 712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding
* scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA
* ({_hashTypedDataV4}).
*
* The implementation of the domain separator was designed to be as efficient as possible while still properly updating
* the chain id to protect against replay attacks on an eventual fork of the chain.
*
* NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method
* https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask].
*/
abstract contract EIP712 {
/* solhint-disable var-name-mixedcase */
// Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to
// invalidate the cached domain separator if the chain id changes.
bytes32 private immutable _CACHED_DOMAIN_SEPARATOR;
uint256 private immutable _CACHED_CHAIN_ID;
bytes32 private immutable _HASHED_NAME;
bytes32 private immutable _HASHED_VERSION;
bytes32 private immutable _TYPE_HASH;
/* solhint-enable var-name-mixedcase */
/**
* @dev Initializes the domain separator and parameter caches.
*
* The meaning of `name` and `version` is specified in
* https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP 712]:
*
* - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol.
* - `version`: the current major version of the signing domain.
*
* NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart
* contract upgrade].
*/
constructor(string memory name, string memory version) {
bytes32 hashedName = keccak256(bytes(name));
bytes32 hashedVersion = keccak256(bytes(version));
bytes32 typeHash = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
_HASHED_NAME = hashedName;
_HASHED_VERSION = hashedVersion;
_CACHED_CHAIN_ID = _getChainId();
_CACHED_DOMAIN_SEPARATOR = _buildDomainSeparator(typeHash, hashedName, hashedVersion);
_TYPE_HASH = typeHash;
}
/**
* @dev Returns the domain separator for the current chain.
*/
function _domainSeparatorV4() internal view virtual returns (bytes32) {
if (_getChainId() == _CACHED_CHAIN_ID) {
return _CACHED_DOMAIN_SEPARATOR;
} else {
return _buildDomainSeparator(_TYPE_HASH, _HASHED_NAME, _HASHED_VERSION);
}
}
function _buildDomainSeparator(bytes32 typeHash, bytes32 name, bytes32 version) private view returns (bytes32) {
return keccak256(
abi.encode(
typeHash,
name,
version,
_getChainId(),
address(this)
)
);
}
/**
* @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this
* function returns the hash of the fully encoded EIP712 message for this domain.
*
* This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example:
*
* ```solidity
* bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(
* keccak256("Mail(address to,string contents)"),
* mailTo,
* keccak256(bytes(mailContents))
* )));
* address signer = ECDSA.recover(digest, signature);
* ```
*/
function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) {
return keccak256(abi.encodePacked("\x19\x01", _domainSeparatorV4(), structHash));
}
function _getChainId() private view returns (uint256 chainId) {
this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
// solhint-disable-next-line no-inline-assembly
assembly {
chainId := chainid()
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.5 <0.8.0;
import "../token/ERC20/ERC20.sol";
import "./IERC20Permit.sol";
import "../cryptography/ECDSA.sol";
import "../utils/Counters.sol";
import "./EIP712.sol";
/**
* @dev Implementation of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on `{IERC20-approve}`, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*/
abstract contract ERC20Permit is ERC20, IERC20Permit, EIP712 {
using Counters for Counters.Counter;
mapping (address => Counters.Counter) private _nonces;
// solhint-disable-next-line var-name-mixedcase
bytes32 private immutable _PERMIT_TYPEHASH = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
/**
* @dev Initializes the {EIP712} domain separator using the `name` parameter, and setting `version` to `"1"`.
*
* It's a good idea to use the same `name` that is defined as the ERC20 token name.
*/
constructor(string memory name) EIP712(name, "1") {
}
/**
* @dev See {IERC20Permit-permit}.
*/
function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public virtual override {
// solhint-disable-next-line not-rely-on-time
require(block.timestamp <= deadline, "ERC20Permit: expired deadline");
bytes32 structHash = keccak256(
abi.encode(
_PERMIT_TYPEHASH,
owner,
spender,
value,
_nonces[owner].current(),
deadline
)
);
bytes32 hash = _hashTypedDataV4(structHash);
address signer = ECDSA.recover(hash, v, r, s);
require(signer == owner, "ERC20Permit: invalid signature");
_nonces[owner].increment();
_approve(owner, spender, value);
}
/**
* @dev See {IERC20Permit-nonces}.
*/
function nonces(address owner) public view override returns (uint256) {
return _nonces[owner].current();
}
/**
* @dev See {IERC20Permit-DOMAIN_SEPARATOR}.
*/
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view override returns (bytes32) {
return _domainSeparatorV4();
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
/**
* @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on `{IERC20-approve}`, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*/
interface IERC20Permit {
/**
* @dev Sets `value` as the allowance of `spender` over `owner`'s tokens,
* given `owner`'s signed approval.
*
* IMPORTANT: The same issues {IERC20-approve} has related to transaction
* ordering also apply here.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `deadline` must be a timestamp in the future.
* - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
* over the EIP712-formatted function arguments.
* - the signature must use ``owner``'s current nonce (see {nonces}).
*
* For more information on the signature format, see the
* https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
* section].
*/
function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external;
/**
* @dev Returns the current nonce for `owner`. This value must be
* included whenever a signature is generated for {permit}.
*
* Every successful call to {permit} increases ``owner``'s nonce by one. This
* prevents a signature from being used multiple times.
*/
function nonces(address owner) external view returns (uint256);
/**
* @dev Returns the domain separator used in the encoding of the signature for `permit`, as defined by {EIP712}.
*/
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view returns (bytes32);
}
= Draft EIPs
This directory contains implementations of EIPs that are still in Draft status.
Due to their nature as drafts, the details of these contracts may change and we cannot guarantee their xref:ROOT:releases-stability.adoc[stability]. Minor releases of OpenZeppelin Contracts may contain breaking changes for the contracts in this directory, which will be duly announced in the https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/CHANGELOG.md[changelog]. The EIPs included here are used by projects in production and this may make them less likely to change significantly.
== Cryptography
{{EIP712}}
== ERC 20
{{IERC20Permit}}
{{ERC20Permit}}
...@@ -32,7 +32,7 @@ abstract contract ERC165 is IERC165 { ...@@ -32,7 +32,7 @@ abstract contract ERC165 is IERC165 {
* *
* Time complexity O(1), guaranteed to always use less than 30 000 gas. * Time complexity O(1), guaranteed to always use less than 30 000 gas.
*/ */
function supportsInterface(bytes4 interfaceId) public view override returns (bool) { function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return _supportedInterfaces[interfaceId]; return _supportedInterfaces[interfaceId];
} }
......
...@@ -41,6 +41,29 @@ library ERC165Checker { ...@@ -41,6 +41,29 @@ library ERC165Checker {
} }
/** /**
* @dev Returns a boolean array where each value corresponds to the
* interfaces passed in and whether they're supported or not. This allows
* you to batch check interfaces for a contract where your expectation
* is that some interfaces may not be supported.
*
* See {IERC165-supportsInterface}.
*/
function getSupportedInterfaces(address account, bytes4[] memory interfaceIds) internal view returns (bool[] memory) {
// an array of booleans corresponding to interfaceIds and whether they're supported or not
bool[] memory interfaceIdsSupported = new bool[](interfaceIds.length);
// query support of ERC165 itself
if (supportsERC165(account)) {
// query support of each interface in interfaceIds
for (uint256 i = 0; i < interfaceIds.length; i++) {
interfaceIdsSupported[i] = _supportsERC165Interface(account, interfaceIds[i]);
}
}
return interfaceIdsSupported;
}
/**
* @dev Returns true if `account` supports all the interfaces defined in * @dev Returns true if `account` supports all the interfaces defined in
* `interfaceIds`. Support for {IERC165} itself is queried automatically. * `interfaceIds`. Support for {IERC165} itself is queried automatically.
* *
......
...@@ -20,7 +20,7 @@ contract ERC1820Implementer is IERC1820Implementer { ...@@ -20,7 +20,7 @@ contract ERC1820Implementer is IERC1820Implementer {
/** /**
* See {IERC1820Implementer-canImplementInterfaceForAddress}. * See {IERC1820Implementer-canImplementInterfaceForAddress}.
*/ */
function canImplementInterfaceForAddress(bytes32 interfaceHash, address account) public view override returns (bytes32) { function canImplementInterfaceForAddress(bytes32 interfaceHash, address account) public view virtual override returns (bytes32) {
return _supportedInterfaces[interfaceHash][account] ? _ERC1820_ACCEPT_MAGIC : bytes32(0x00); return _supportedInterfaces[interfaceHash][account] ? _ERC1820_ACCEPT_MAGIC : bytes32(0x00);
} }
......
...@@ -17,6 +17,52 @@ pragma solidity ^0.7.0; ...@@ -17,6 +17,52 @@ pragma solidity ^0.7.0;
*/ */
library SafeMath { library SafeMath {
/** /**
* @dev Returns the addition of two unsigned integers, with an overflow flag.
*/
function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
uint256 c = a + b;
if (c < a) return (false, 0);
return (true, c);
}
/**
* @dev Returns the substraction of two unsigned integers, with an overflow flag.
*/
function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
if (b > a) return (false, 0);
return (true, a - b);
}
/**
* @dev Returns the multiplication of two unsigned integers, with an overflow flag.
*/
function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
if (a == 0) return (true, 0);
uint256 c = a * b;
if (c / a != b) return (false, 0);
return (true, c);
}
/**
* @dev Returns the division of two unsigned integers, with a division by zero flag.
*/
function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
if (b == 0) return (false, 0);
return (true, a / b);
}
/**
* @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
*/
function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
if (b == 0) return (false, 0);
return (true, a % b);
}
/**
* @dev Returns the addition of two unsigned integers, reverting on * @dev Returns the addition of two unsigned integers, reverting on
* overflow. * overflow.
* *
...@@ -29,7 +75,6 @@ library SafeMath { ...@@ -29,7 +75,6 @@ library SafeMath {
function add(uint256 a, uint256 b) internal pure returns (uint256) { function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b; uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow"); require(c >= a, "SafeMath: addition overflow");
return c; return c;
} }
...@@ -44,24 +89,8 @@ library SafeMath { ...@@ -44,24 +89,8 @@ library SafeMath {
* - Subtraction cannot overflow. * - Subtraction cannot overflow.
*/ */
function sub(uint256 a, uint256 b) internal pure returns (uint256) { function sub(uint256 a, uint256 b) internal pure returns (uint256) {
return sub(a, b, "SafeMath: subtraction overflow"); require(b <= a, "SafeMath: subtraction overflow");
} return a - b;
/**
* @dev Returns the subtraction of two unsigned integers, reverting with custom message on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
*
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b <= a, errorMessage);
uint256 c = a - b;
return c;
} }
/** /**
...@@ -75,21 +104,14 @@ library SafeMath { ...@@ -75,21 +104,14 @@ library SafeMath {
* - Multiplication cannot overflow. * - Multiplication cannot overflow.
*/ */
function mul(uint256 a, uint256 b) internal pure returns (uint256) { function mul(uint256 a, uint256 b) internal pure returns (uint256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the if (a == 0) return 0;
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
if (a == 0) {
return 0;
}
uint256 c = a * b; uint256 c = a * b;
require(c / a == b, "SafeMath: multiplication overflow"); require(c / a == b, "SafeMath: multiplication overflow");
return c; return c;
} }
/** /**
* @dev Returns the integer division of two unsigned integers. Reverts on * @dev Returns the integer division of two unsigned integers, reverting on
* division by zero. The result is rounded towards zero. * division by zero. The result is rounded towards zero.
* *
* Counterpart to Solidity's `/` operator. Note: this function uses a * Counterpart to Solidity's `/` operator. Note: this function uses a
...@@ -101,48 +123,71 @@ library SafeMath { ...@@ -101,48 +123,71 @@ library SafeMath {
* - The divisor cannot be zero. * - The divisor cannot be zero.
*/ */
function div(uint256 a, uint256 b) internal pure returns (uint256) { function div(uint256 a, uint256 b) internal pure returns (uint256) {
return div(a, b, "SafeMath: division by zero"); require(b > 0, "SafeMath: division by zero");
return a / b;
} }
/** /**
* @dev Returns the integer division of two unsigned integers. Reverts with custom message on * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* division by zero. The result is rounded towards zero. * reverting when dividing by zero.
* *
* Counterpart to Solidity's `/` operator. Note: this function uses a * Counterpart to Solidity's `%` operator. This function uses a `revert`
* `revert` opcode (which leaves remaining gas untouched) while Solidity * opcode (which leaves remaining gas untouched) while Solidity uses an
* uses an invalid opcode to revert (consuming all remaining gas). * invalid opcode to revert (consuming all remaining gas).
* *
* Requirements: * Requirements:
* *
* - The divisor cannot be zero. * - The divisor cannot be zero.
*/ */
function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { function mod(uint256 a, uint256 b) internal pure returns (uint256) {
require(b > 0, errorMessage); require(b > 0, "SafeMath: modulo by zero");
uint256 c = a / b; return a % b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold }
return c; /**
* @dev Returns the subtraction of two unsigned integers, reverting with custom message on
* overflow (when the result is negative).
*
* CAUTION: This function is deprecated because it requires allocating memory for the error
* message unnecessarily. For custom revert reasons use {trySub}.
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
*
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b <= a, errorMessage);
return a - b;
} }
/** /**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), * @dev Returns the integer division of two unsigned integers, reverting with custom message on
* Reverts when dividing by zero. * division by zero. The result is rounded towards zero.
* *
* Counterpart to Solidity's `%` operator. This function uses a `revert` * CAUTION: This function is deprecated because it requires allocating memory for the error
* opcode (which leaves remaining gas untouched) while Solidity uses an * message unnecessarily. For custom revert reasons use {tryDiv}.
* invalid opcode to revert (consuming all remaining gas). *
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
* *
* Requirements: * Requirements:
* *
* - The divisor cannot be zero. * - The divisor cannot be zero.
*/ */
function mod(uint256 a, uint256 b) internal pure returns (uint256) { function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
return mod(a, b, "SafeMath: modulo by zero"); require(b > 0, errorMessage);
return a / b;
} }
/** /**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* Reverts with custom message when dividing by zero. * reverting with custom message when dividing by zero.
*
* CAUTION: This function is deprecated because it requires allocating memory for the error
* message unnecessarily. For custom revert reasons use {tryMod}.
* *
* Counterpart to Solidity's `%` operator. This function uses a `revert` * Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an * opcode (which leaves remaining gas untouched) while Solidity uses an
...@@ -153,7 +198,7 @@ library SafeMath { ...@@ -153,7 +198,7 @@ library SafeMath {
* - The divisor cannot be zero. * - The divisor cannot be zero.
*/ */
function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b != 0, errorMessage); require(b > 0, errorMessage);
return a % b; return a % b;
} }
} }
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
contract BadBeaconNoImpl {
}
contract BadBeaconNotContract {
function implementation() external pure returns (address) {
return address(0x1);
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
import "../proxy/Clones.sol";
import "../utils/Address.sol";
contract ClonesMock {
using Address for address;
using Clones for address;
event NewInstance(address instance);
function clone(address master, bytes calldata initdata) public payable {
_initAndEmit(master.clone(), initdata);
}
function cloneDeterministic(address master, bytes32 salt, bytes calldata initdata) public payable {
_initAndEmit(master.cloneDeterministic(salt), initdata);
}
function predictDeterministicAddress(address master, bytes32 salt) public view returns (address predicted) {
return master.predictDeterministicAddress(salt);
}
function _initAndEmit(address instance, bytes memory initdata) private {
if (initdata.length > 0) {
instance.functionCallWithValue(initdata, msg.value);
}
emit NewInstance(instance);
}
}
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
pragma solidity ^0.7.0; pragma solidity ^0.7.0;
import "../GSN/Context.sol"; import "../utils/Context.sol";
contract ContextMock is Context { contract ContextMock is Context {
event Sender(address sender); event Sender(address sender);
......
...@@ -19,11 +19,11 @@ contract DummyImplementation { ...@@ -19,11 +19,11 @@ contract DummyImplementation {
value = 100; value = 100;
} }
function initializeNonPayable(uint256 _value) public { function initializeNonPayableWithValue(uint256 _value) public {
value = _value; value = _value;
} }
function initializePayable(uint256 _value) public payable { function initializePayableWithValue(uint256 _value) public payable {
value = _value; value = _value;
} }
...@@ -42,7 +42,7 @@ contract DummyImplementation { ...@@ -42,7 +42,7 @@ contract DummyImplementation {
} }
function reverts() public pure { function reverts() public pure {
require(false); require(false, "DummyImplementation reverted");
} }
} }
......
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
import "../drafts/EIP712.sol";
import "../cryptography/ECDSA.sol";
contract EIP712External is EIP712 {
constructor(string memory name, string memory version) EIP712(name, version) {}
function domainSeparator() external view returns (bytes32) {
return _domainSeparatorV4();
}
function verify(bytes memory signature, address signer, address mailTo, string memory mailContents) external view {
bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(
keccak256("Mail(address to,string contents)"),
mailTo,
keccak256(bytes(mailContents))
)));
address recoveredSigner = ECDSA.recover(digest, signature);
require(recoveredSigner == signer);
}
function getChainId() external view returns (uint256 chainId) {
this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
// solhint-disable-next-line no-inline-assembly
assembly {
chainId := chainid()
}
}
}
...@@ -18,4 +18,8 @@ contract ERC165CheckerMock { ...@@ -18,4 +18,8 @@ contract ERC165CheckerMock {
function supportsAllInterfaces(address account, bytes4[] memory interfaceIds) public view returns (bool) { function supportsAllInterfaces(address account, bytes4[] memory interfaceIds) public view returns (bool) {
return account.supportsAllInterfaces(interfaceIds); return account.supportsAllInterfaces(interfaceIds);
} }
function getSupportedInterfaces(address account, bytes4[] memory interfaceIds) public view returns (bool[] memory) {
return account.getSupportedInterfaces(interfaceIds);
}
} }
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
import "../drafts/ERC20Permit.sol";
contract ERC20PermitMock is ERC20Permit {
constructor (
string memory name,
string memory symbol,
address initialAccount,
uint256 initialBalance
) payable ERC20(name, symbol) ERC20Permit(name) {
_mint(initialAccount, initialBalance);
}
function getChainId() external view returns (uint256 chainId) {
this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
// solhint-disable-next-line no-inline-assembly
assembly {
chainId := chainid()
}
}
}
...@@ -2,10 +2,12 @@ ...@@ -2,10 +2,12 @@
pragma solidity ^0.7.0; pragma solidity ^0.7.0;
import "../GSN/Context.sol"; import "../utils/Context.sol";
import "../token/ERC777/ERC777.sol"; import "../token/ERC777/ERC777.sol";
contract ERC777Mock is Context, ERC777 { contract ERC777Mock is Context, ERC777 {
event BeforeTokenTransfer();
constructor( constructor(
address initialHolder, address initialHolder,
uint256 initialBalance, uint256 initialBalance,
...@@ -28,4 +30,8 @@ contract ERC777Mock is Context, ERC777 { ...@@ -28,4 +30,8 @@ contract ERC777Mock is Context, ERC777 {
function approveInternal(address holder, address spender, uint256 value) public { function approveInternal(address holder, address spender, uint256 value) public {
_approve(holder, spender, value); _approve(holder, spender, value);
} }
function _beforeTokenTransfer(address, address, address, uint256) internal override {
emit BeforeTokenTransfer();
}
} }
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
pragma solidity ^0.7.0; pragma solidity ^0.7.0;
import "../GSN/Context.sol"; import "../utils/Context.sol";
import "../token/ERC777/IERC777.sol"; import "../token/ERC777/IERC777.sol";
import "../token/ERC777/IERC777Sender.sol"; import "../token/ERC777/IERC777Sender.sol";
import "../token/ERC777/IERC777Recipient.sol"; import "../token/ERC777/IERC777Recipient.sol";
...@@ -34,6 +34,9 @@ contract ERC777SenderRecipientMock is Context, IERC777Sender, IERC777Recipient, ...@@ -34,6 +34,9 @@ contract ERC777SenderRecipientMock is Context, IERC777Sender, IERC777Recipient,
uint256 toBalance uint256 toBalance
); );
// Emitted in ERC777Mock. Here for easier decoding
event BeforeTokenTransfer();
bool private _shouldRevertSend; bool private _shouldRevertSend;
bool private _shouldRevertReceive; bool private _shouldRevertReceive;
......
...@@ -34,7 +34,15 @@ contract EnumerableMapMock { ...@@ -34,7 +34,15 @@ contract EnumerableMapMock {
} }
function tryGet(uint256 key) public view returns (bool, address) {
return _map.tryGet(key);
}
function get(uint256 key) public view returns (address) { function get(uint256 key) public view returns (address) {
return _map.get(key); return _map.get(key);
} }
function getWithMessage(uint256 key, string calldata errorMessage) public view returns (address) {
return _map.get(key, errorMessage);
}
} }
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
pragma solidity ^0.7.0; pragma solidity ^0.7.0;
import "../GSN/Context.sol"; import "../utils/Context.sol";
contract ReentrancyAttack is Context { contract ReentrancyAttack is Context {
function callSender(bytes4 data) public { function callSender(bytes4 data) public {
// solhint-disable-next-line avoid-low-level-calls // solhint-disable-next-line avoid-low-level-calls
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
pragma solidity ^0.7.0; pragma solidity ^0.7.0;
import "../GSN/Context.sol"; import "../utils/Context.sol";
import "../token/ERC20/IERC20.sol"; import "../token/ERC20/IERC20.sol";
import "../token/ERC20/SafeERC20.sol"; import "../token/ERC20/SafeERC20.sol";
......
...@@ -5,23 +5,101 @@ pragma solidity ^0.7.0; ...@@ -5,23 +5,101 @@ pragma solidity ^0.7.0;
import "../math/SafeMath.sol"; import "../math/SafeMath.sol";
contract SafeMathMock { contract SafeMathMock {
function mul(uint256 a, uint256 b) public pure returns (uint256) { function tryAdd(uint256 a, uint256 b) public pure returns (bool flag, uint256 value) {
return SafeMath.mul(a, b); return SafeMath.tryAdd(a, b);
} }
function div(uint256 a, uint256 b) public pure returns (uint256) { function trySub(uint256 a, uint256 b) public pure returns (bool flag, uint256 value) {
return SafeMath.div(a, b); return SafeMath.trySub(a, b);
} }
function sub(uint256 a, uint256 b) public pure returns (uint256) { function tryMul(uint256 a, uint256 b) public pure returns (bool flag, uint256 value) {
return SafeMath.sub(a, b); return SafeMath.tryMul(a, b);
}
function tryDiv(uint256 a, uint256 b) public pure returns (bool flag, uint256 value) {
return SafeMath.tryDiv(a, b);
}
function tryMod(uint256 a, uint256 b) public pure returns (bool flag, uint256 value) {
return SafeMath.tryMod(a, b);
} }
function add(uint256 a, uint256 b) public pure returns (uint256) { function add(uint256 a, uint256 b) public pure returns (uint256) {
return SafeMath.add(a, b); return SafeMath.add(a, b);
} }
function sub(uint256 a, uint256 b) public pure returns (uint256) {
return SafeMath.sub(a, b);
}
function mul(uint256 a, uint256 b) public pure returns (uint256) {
return SafeMath.mul(a, b);
}
function div(uint256 a, uint256 b) public pure returns (uint256) {
return SafeMath.div(a, b);
}
function mod(uint256 a, uint256 b) public pure returns (uint256) { function mod(uint256 a, uint256 b) public pure returns (uint256) {
return SafeMath.mod(a, b); return SafeMath.mod(a, b);
} }
function subWithMessage(uint256 a, uint256 b, string memory errorMessage) public pure returns (uint256) {
return SafeMath.sub(a, b, errorMessage);
}
function divWithMessage(uint256 a, uint256 b, string memory errorMessage) public pure returns (uint256) {
return SafeMath.div(a, b, errorMessage);
}
function modWithMessage(uint256 a, uint256 b, string memory errorMessage) public pure returns (uint256) {
return SafeMath.mod(a, b, errorMessage);
}
function addMemoryCheck() public pure returns (uint256 mem) {
uint256 length = 32;
// solhint-disable-next-line no-inline-assembly
assembly { mem := mload(0x40) }
for (uint256 i = 0; i < length; ++i) { SafeMath.add(1, 1); }
// solhint-disable-next-line no-inline-assembly
assembly { mem := sub(mload(0x40), mem) }
}
function subMemoryCheck() public pure returns (uint256 mem) {
uint256 length = 32;
// solhint-disable-next-line no-inline-assembly
assembly { mem := mload(0x40) }
for (uint256 i = 0; i < length; ++i) { SafeMath.sub(1, 1); }
// solhint-disable-next-line no-inline-assembly
assembly { mem := sub(mload(0x40), mem) }
}
function mulMemoryCheck() public pure returns (uint256 mem) {
uint256 length = 32;
// solhint-disable-next-line no-inline-assembly
assembly { mem := mload(0x40) }
for (uint256 i = 0; i < length; ++i) { SafeMath.mul(1, 1); }
// solhint-disable-next-line no-inline-assembly
assembly { mem := sub(mload(0x40), mem) }
}
function divMemoryCheck() public pure returns (uint256 mem) {
uint256 length = 32;
// solhint-disable-next-line no-inline-assembly
assembly { mem := mload(0x40) }
for (uint256 i = 0; i < length; ++i) { SafeMath.div(1, 1); }
// solhint-disable-next-line no-inline-assembly
assembly { mem := sub(mload(0x40), mem) }
}
function modMemoryCheck() public pure returns (uint256 mem) {
uint256 length = 32;
// solhint-disable-next-line no-inline-assembly
assembly { mem := mload(0x40) }
for (uint256 i = 0; i < length; ++i) { SafeMath.mod(1, 1); }
// solhint-disable-next-line no-inline-assembly
assembly { mem := sub(mload(0x40), mem) }
}
} }
{ {
"name": "@openzeppelin/contracts", "name": "@openzeppelin/contracts",
"description": "Secure Smart Contract library for Solidity", "description": "Secure Smart Contract library for Solidity",
"version": "3.2.2-solc-0.7", "version": "3.3.0-solc-0.7",
"files": [ "files": [
"**/*.sol", "**/*.sol",
"/build/contracts/*.json", "/build/contracts/*.json",
......
...@@ -2,8 +2,9 @@ ...@@ -2,8 +2,9 @@
pragma solidity ^0.7.0; pragma solidity ^0.7.0;
import "../GSN/Context.sol"; import "../utils/Context.sol";
import "../math/SafeMath.sol"; import "../math/SafeMath.sol";
import "../utils/Address.sol";
/** /**
* @title PaymentSplitter * @title PaymentSplitter
...@@ -112,7 +113,7 @@ contract PaymentSplitter is Context { ...@@ -112,7 +113,7 @@ contract PaymentSplitter is Context {
_released[account] = _released[account].add(payment); _released[account] = _released[account].add(payment);
_totalReleased = _totalReleased.add(payment); _totalReleased = _totalReleased.add(payment);
account.transfer(payment); Address.sendValue(account, payment);
emit PaymentReleased(account, payment); emit PaymentReleased(account, payment);
} }
......
...@@ -36,7 +36,7 @@ contract Escrow is Ownable { ...@@ -36,7 +36,7 @@ contract Escrow is Ownable {
* @dev Stores the sent amount as credit to be withdrawn. * @dev Stores the sent amount as credit to be withdrawn.
* @param payee The destination address of the funds. * @param payee The destination address of the funds.
*/ */
function deposit(address payee) public virtual payable onlyOwner { function deposit(address payee) public payable virtual onlyOwner {
uint256 amount = msg.value; uint256 amount = msg.value;
_deposits[payee] = _deposits[payee].add(amount); _deposits[payee] = _deposits[payee].add(amount);
......
...@@ -15,6 +15,8 @@ import "./ConditionalEscrow.sol"; ...@@ -15,6 +15,8 @@ import "./ConditionalEscrow.sol";
* with `RefundEscrow` will be made through the owner contract. * with `RefundEscrow` will be made through the owner contract.
*/ */
contract RefundEscrow is ConditionalEscrow { contract RefundEscrow is ConditionalEscrow {
using Address for address payable;
enum State { Active, Refunding, Closed } enum State { Active, Refunding, Closed }
event RefundsClosed(); event RefundsClosed();
...@@ -36,14 +38,14 @@ contract RefundEscrow is ConditionalEscrow { ...@@ -36,14 +38,14 @@ contract RefundEscrow is ConditionalEscrow {
/** /**
* @return The current state of the escrow. * @return The current state of the escrow.
*/ */
function state() public view returns (State) { function state() public view virtual returns (State) {
return _state; return _state;
} }
/** /**
* @return The beneficiary of the escrow. * @return The beneficiary of the escrow.
*/ */
function beneficiary() public view returns (address) { function beneficiary() public view virtual returns (address payable) {
return _beneficiary; return _beneficiary;
} }
...@@ -52,7 +54,7 @@ contract RefundEscrow is ConditionalEscrow { ...@@ -52,7 +54,7 @@ contract RefundEscrow is ConditionalEscrow {
* @param refundee The address funds will be sent to if a refund occurs. * @param refundee The address funds will be sent to if a refund occurs.
*/ */
function deposit(address refundee) public payable virtual override { function deposit(address refundee) public payable virtual override {
require(_state == State.Active, "RefundEscrow: can only deposit while active"); require(state() == State.Active, "RefundEscrow: can only deposit while active");
super.deposit(refundee); super.deposit(refundee);
} }
...@@ -60,8 +62,8 @@ contract RefundEscrow is ConditionalEscrow { ...@@ -60,8 +62,8 @@ contract RefundEscrow is ConditionalEscrow {
* @dev Allows for the beneficiary to withdraw their funds, rejecting * @dev Allows for the beneficiary to withdraw their funds, rejecting
* further deposits. * further deposits.
*/ */
function close() public onlyOwner virtual { function close() public virtual onlyOwner {
require(_state == State.Active, "RefundEscrow: can only close while active"); require(state() == State.Active, "RefundEscrow: can only close while active");
_state = State.Closed; _state = State.Closed;
emit RefundsClosed(); emit RefundsClosed();
} }
...@@ -70,7 +72,7 @@ contract RefundEscrow is ConditionalEscrow { ...@@ -70,7 +72,7 @@ contract RefundEscrow is ConditionalEscrow {
* @dev Allows for refunds to take place, rejecting further deposits. * @dev Allows for refunds to take place, rejecting further deposits.
*/ */
function enableRefunds() public onlyOwner virtual { function enableRefunds() public onlyOwner virtual {
require(_state == State.Active, "RefundEscrow: can only enable refunds while active"); require(state() == State.Active, "RefundEscrow: can only enable refunds while active");
_state = State.Refunding; _state = State.Refunding;
emit RefundsEnabled(); emit RefundsEnabled();
} }
...@@ -79,8 +81,8 @@ contract RefundEscrow is ConditionalEscrow { ...@@ -79,8 +81,8 @@ contract RefundEscrow is ConditionalEscrow {
* @dev Withdraws the beneficiary's funds. * @dev Withdraws the beneficiary's funds.
*/ */
function beneficiaryWithdraw() public virtual { function beneficiaryWithdraw() public virtual {
require(_state == State.Closed, "RefundEscrow: beneficiary can only withdraw while closed"); require(state() == State.Closed, "RefundEscrow: beneficiary can only withdraw while closed");
_beneficiary.transfer(address(this).balance); beneficiary().sendValue(address(this).balance);
} }
/** /**
...@@ -88,6 +90,6 @@ contract RefundEscrow is ConditionalEscrow { ...@@ -88,6 +90,6 @@ contract RefundEscrow is ConditionalEscrow {
* 'payee' argument, but we ignore it here since the condition is global, not per-payee. * 'payee' argument, but we ignore it here since the condition is global, not per-payee.
*/ */
function withdrawalAllowed(address) public view override returns (bool) { function withdrawalAllowed(address) public view override returns (bool) {
return _state == State.Refunding; return state() == State.Refunding;
} }
} }
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
pragma solidity ^0.7.0; pragma solidity ^0.7.0;
import "../access/AccessControl.sol"; import "../access/AccessControl.sol";
import "../GSN/Context.sol"; import "../utils/Context.sol";
import "../token/ERC1155/ERC1155.sol"; import "../token/ERC1155/ERC1155.sol";
import "../token/ERC1155/ERC1155Burnable.sol"; import "../token/ERC1155/ERC1155Burnable.sol";
import "../token/ERC1155/ERC1155Pausable.sol"; import "../token/ERC1155/ERC1155Pausable.sol";
......
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;
import "../token/ERC20/ERC20Burnable.sol";
/**
* @dev {ERC20} token, including:
*
* - Preminted initial supply
* - Ability for holders to burn (destroy) their tokens
* - No access control mechanism (for minting/pausing) and hence no governance
*
* This contract uses {ERC20Burnable} to include burn capabilities - head to
* its documentation for details.
*/
contract ERC20PresetFixedSupply is ERC20Burnable {
/**
* @dev Mints `initialSupply` amount of token and transfers them to `owner`.
*
* See {ERC20-constructor}.
*/
constructor(
string memory name,
string memory symbol,
uint256 initialSupply,
address owner
) ERC20(name, symbol) {
_mint(owner, initialSupply);
}
}
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
pragma solidity ^0.7.0; pragma solidity ^0.7.0;
import "../access/AccessControl.sol"; import "../access/AccessControl.sol";
import "../GSN/Context.sol"; import "../utils/Context.sol";
import "../token/ERC20/ERC20.sol"; import "../token/ERC20/ERC20.sol";
import "../token/ERC20/ERC20Burnable.sol"; import "../token/ERC20/ERC20Burnable.sol";
import "../token/ERC20/ERC20Pausable.sol"; import "../token/ERC20/ERC20Pausable.sol";
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
pragma solidity ^0.7.0; pragma solidity ^0.7.0;
import "../access/AccessControl.sol"; import "../access/AccessControl.sol";
import "../GSN/Context.sol"; import "../utils/Context.sol";
import "../utils/Counters.sol"; import "../utils/Counters.sol";
import "../token/ERC721/ERC721.sol"; import "../token/ERC721/ERC721.sol";
import "../token/ERC721/ERC721Burnable.sol"; import "../token/ERC721/ERC721Burnable.sol";
......
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;
import "../token/ERC777/ERC777.sol";
/**
* @dev {ERC777} token, including:
*
* - Preminted initial supply
* - No access control mechanism (for minting/pausing) and hence no governance
*/
contract ERC777PresetFixedSupply is ERC777 {
/**
* @dev Mints `initialSupply` amount of token and transfers them to `owner`.
*
* See {ERC777-constructor}.
*/
constructor(
string memory name,
string memory symbol,
address[] memory defaultOperators,
uint256 initialSupply,
address owner
) ERC777(name, symbol, defaultOperators) {
_mint(owner, initialSupply, "", "");
}
}
...@@ -16,3 +16,7 @@ TIP: Intermediate and advanced users can use these as starting points when writi ...@@ -16,3 +16,7 @@ TIP: Intermediate and advanced users can use these as starting points when writi
{{ERC721PresetMinterPauserAutoId}} {{ERC721PresetMinterPauserAutoId}}
{{ERC1155PresetMinterPauser}} {{ERC1155PresetMinterPauser}}
{{ERC20PresetFixedSupply}}
{{ERC777PresetFixedSupply}}
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
import "./Proxy.sol";
import "../utils/Address.sol";
import "./IBeacon.sol";
/**
* @dev This contract implements a proxy that gets the implementation address for each call from a {UpgradeableBeacon}.
*
* The beacon address is stored in storage slot `uint256(keccak256('eip1967.proxy.beacon')) - 1`, so that it doesn't
* conflict with the storage layout of the implementation behind the proxy.
*/
contract BeaconProxy is Proxy {
/**
* @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 private constant _BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;
/**
* @dev Initializes the proxy with `beacon`.
*
* If `data` is nonempty, it's used as data in a delegate call to the implementation returned by the beacon. This
* will typically be an encoded function call, and allows initializating the storage of the proxy like a Solidity
* constructor.
*
* Requirements:
*
* - `beacon` must be a contract with the interface {IBeacon}.
*/
constructor(address beacon, bytes memory data) payable {
assert(_BEACON_SLOT == bytes32(uint256(keccak256("eip1967.proxy.beacon")) - 1));
_setBeacon(beacon, data);
}
/**
* @dev Returns the current beacon address.
*/
function _beacon() internal view virtual returns (address beacon) {
bytes32 slot = _BEACON_SLOT;
// solhint-disable-next-line no-inline-assembly
assembly {
beacon := sload(slot)
}
}
/**
* @dev Returns the current implementation address of the associated beacon.
*/
function _implementation() internal view virtual override returns (address) {
return IBeacon(_beacon()).implementation();
}
/**
* @dev Changes the proxy to use a new beacon.
*
* If `data` is nonempty, it's used as data in a delegate call to the implementation returned by the beacon.
*
* Requirements:
*
* - `beacon` must be a contract.
* - The implementation returned by `beacon` must be a contract.
*/
function _setBeacon(address beacon, bytes memory data) internal virtual {
require(
Address.isContract(beacon),
"BeaconProxy: beacon is not a contract"
);
require(
Address.isContract(IBeacon(beacon).implementation()),
"BeaconProxy: beacon implementation is not a contract"
);
bytes32 slot = _BEACON_SLOT;
// solhint-disable-next-line no-inline-assembly
assembly {
sstore(slot, beacon)
}
if (data.length > 0) {
Address.functionDelegateCall(_implementation(), data, "BeaconProxy: function call failed");
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
/**
* @dev https://eips.ethereum.org/EIPS/eip-1167[EIP 1167] is a standard for
* deploying minimal proxy contracts, also known as "clones".
*
* > To simply and cheaply clone contract functionality in an immutable way, this standard specifies
* > a minimal bytecode implementation that delegates all calls to a known, fixed address.
*
* The library includes functions to deploy a proxy using either `create` (traditional deployment) or `create2`
* (salted deterministic deployment). It also includes functions to predict the addresses of clones deployed using the
* deterministic method.
*/
library Clones {
/**
* @dev Deploys and returns the address of a clone that mimics the behaviour of `master`.
*
* This function uses the create opcode, which should never revert.
*/
function clone(address master) internal returns (address instance) {
// solhint-disable-next-line no-inline-assembly
assembly {
let ptr := mload(0x40)
mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000)
mstore(add(ptr, 0x14), shl(0x60, master))
mstore(add(ptr, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000)
instance := create(0, ptr, 0x37)
}
require(instance != address(0), "ERC1167: create failed");
}
/**
* @dev Deploys and returns the address of a clone that mimics the behaviour of `master`.
*
* This function uses the create2 opcode and a `salt` to deterministically deploy
* the clone. Using the same `master` and `salt` multiple time will revert, since
* the clones cannot be deployed twice at the same address.
*/
function cloneDeterministic(address master, bytes32 salt) internal returns (address instance) {
// solhint-disable-next-line no-inline-assembly
assembly {
let ptr := mload(0x40)
mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000)
mstore(add(ptr, 0x14), shl(0x60, master))
mstore(add(ptr, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000)
instance := create2(0, ptr, 0x37, salt)
}
require(instance != address(0), "ERC1167: create2 failed");
}
/**
* @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
*/
function predictDeterministicAddress(address master, bytes32 salt, address deployer) internal pure returns (address predicted) {
// solhint-disable-next-line no-inline-assembly
assembly {
let ptr := mload(0x40)
mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000)
mstore(add(ptr, 0x14), shl(0x60, master))
mstore(add(ptr, 0x28), 0x5af43d82803e903d91602b57fd5bf3ff00000000000000000000000000000000)
mstore(add(ptr, 0x38), shl(0x60, deployer))
mstore(add(ptr, 0x4c), salt)
mstore(add(ptr, 0x6c), keccak256(ptr, 0x37))
predicted := keccak256(add(ptr, 0x37), 0x55)
}
}
/**
* @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
*/
function predictDeterministicAddress(address master, bytes32 salt) internal view returns (address predicted) {
return predictDeterministicAddress(master, salt, address(this));
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
/**
* @dev This is the interface that {BeaconProxy} expects of its beacon.
*/
interface IBeacon {
/**
* @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,16 +3,17 @@ ...@@ -3,16 +3,17 @@
// solhint-disable-next-line compiler-version // solhint-disable-next-line compiler-version
pragma solidity >=0.4.24 <0.8.0; pragma solidity >=0.4.24 <0.8.0;
import "../utils/Address.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
* external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
* 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 {UpgradeableProxy-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.
*/ */
...@@ -49,15 +50,6 @@ abstract contract Initializable { ...@@ -49,15 +50,6 @@ abstract contract Initializable {
/// @dev Returns true if and only if the function is running in the constructor /// @dev Returns true if and only if the function is running in the constructor
function _isConstructor() private view returns (bool) { function _isConstructor() private view returns (bool) {
// extcodesize checks the size of the code stored in an address, and return !Address.isContract(address(this));
// address returns the current address. Since the code is still not
// deployed when running a constructor, any checks on its code size will
// yield zero, making it an effective way to detect if a contract is
// under construction or not.
address self = address(this);
uint256 cs;
// solhint-disable-next-line no-inline-assembly
assembly { cs := extcodesize(self) }
return cs == 0;
} }
} }
...@@ -6,19 +6,19 @@ pragma solidity ^0.7.0; ...@@ -6,19 +6,19 @@ pragma solidity ^0.7.0;
* @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM * @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM
* instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to * instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to
* be specified by overriding the virtual {_implementation} function. * be specified by overriding the virtual {_implementation} function.
* *
* Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a * Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a
* different contract through the {_delegate} function. * different contract through the {_delegate} function.
* *
* The success and return data of the delegated call will be returned back to the caller of the proxy. * The success and return data of the delegated call will be returned back to the caller of the proxy.
*/ */
abstract contract Proxy { abstract contract Proxy {
/** /**
* @dev Delegates the current call to `implementation`. * @dev Delegates the current call to `implementation`.
* *
* This function does not return to its internall call site, it will return directly to the external caller. * This function does not return to its internall call site, it will return directly to the external caller.
*/ */
function _delegate(address implementation) internal { function _delegate(address implementation) internal virtual {
// solhint-disable-next-line no-inline-assembly // solhint-disable-next-line no-inline-assembly
assembly { assembly {
// Copy msg.data. We take full control of memory in this inline assembly // Copy msg.data. We take full control of memory in this inline assembly
...@@ -44,14 +44,14 @@ abstract contract Proxy { ...@@ -44,14 +44,14 @@ abstract contract Proxy {
* @dev This is a virtual function that should be overriden so it returns the address to which the fallback function * @dev This is a virtual function that should be overriden so it returns the address to which the fallback function
* and {_fallback} should delegate. * and {_fallback} should delegate.
*/ */
function _implementation() internal virtual view returns (address); function _implementation() internal view virtual returns (address);
/** /**
* @dev Delegates the current call to the address returned by `_implementation()`. * @dev Delegates the current call to the address returned by `_implementation()`.
* *
* This function does not return to its internall call site, it will return directly to the external caller. * This function does not return to its internall call site, it will return directly to the external caller.
*/ */
function _fallback() internal { function _fallback() internal virtual {
_beforeFallback(); _beforeFallback();
_delegate(_implementation()); _delegate(_implementation());
} }
...@@ -60,7 +60,7 @@ abstract contract Proxy { ...@@ -60,7 +60,7 @@ abstract contract Proxy {
* @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other
* function in the contract matches the call data. * function in the contract matches the call data.
*/ */
fallback () external payable { fallback () external payable virtual {
_fallback(); _fallback();
} }
...@@ -68,14 +68,14 @@ abstract contract Proxy { ...@@ -68,14 +68,14 @@ abstract contract Proxy {
* @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if call data * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if call data
* is empty. * is empty.
*/ */
receive () external payable { receive () external payable virtual {
_fallback(); _fallback();
} }
/** /**
* @dev Hook that is called before falling back to the implementation. Can happen as part of a manual `_fallback` * @dev Hook that is called before falling back to the implementation. Can happen as part of a manual `_fallback`
* call, or as part of the Solidity `fallback` or `receive` functions. * call, or as part of the Solidity `fallback` or `receive` functions.
* *
* If overriden should call `super._beforeFallback()`. * If overriden should call `super._beforeFallback()`.
*/ */
function _beforeFallback() internal virtual { function _beforeFallback() internal virtual {
......
...@@ -13,12 +13,12 @@ contract ProxyAdmin is Ownable { ...@@ -13,12 +13,12 @@ contract ProxyAdmin is Ownable {
/** /**
* @dev Returns the current implementation of `proxy`. * @dev Returns the current implementation of `proxy`.
* *
* Requirements: * Requirements:
* *
* - This contract must be the admin of `proxy`. * - This contract must be the admin of `proxy`.
*/ */
function getProxyImplementation(TransparentUpgradeableProxy proxy) public view returns (address) { function getProxyImplementation(TransparentUpgradeableProxy proxy) public view virtual returns (address) {
// We need to manually run the static call since the getter cannot be flagged as view // We need to manually run the static call since the getter cannot be flagged as view
// bytes4(keccak256("implementation()")) == 0x5c60da1b // bytes4(keccak256("implementation()")) == 0x5c60da1b
(bool success, bytes memory returndata) = address(proxy).staticcall(hex"5c60da1b"); (bool success, bytes memory returndata) = address(proxy).staticcall(hex"5c60da1b");
...@@ -28,12 +28,12 @@ contract ProxyAdmin is Ownable { ...@@ -28,12 +28,12 @@ contract ProxyAdmin is Ownable {
/** /**
* @dev Returns the current admin of `proxy`. * @dev Returns the current admin of `proxy`.
* *
* Requirements: * Requirements:
* *
* - This contract must be the admin of `proxy`. * - This contract must be the admin of `proxy`.
*/ */
function getProxyAdmin(TransparentUpgradeableProxy proxy) public view returns (address) { function getProxyAdmin(TransparentUpgradeableProxy proxy) public view virtual returns (address) {
// We need to manually run the static call since the getter cannot be flagged as view // We need to manually run the static call since the getter cannot be flagged as view
// bytes4(keccak256("admin()")) == 0xf851a440 // bytes4(keccak256("admin()")) == 0xf851a440
(bool success, bytes memory returndata) = address(proxy).staticcall(hex"f851a440"); (bool success, bytes memory returndata) = address(proxy).staticcall(hex"f851a440");
...@@ -43,35 +43,35 @@ contract ProxyAdmin is Ownable { ...@@ -43,35 +43,35 @@ contract ProxyAdmin is Ownable {
/** /**
* @dev Changes the admin of `proxy` to `newAdmin`. * @dev Changes the admin of `proxy` to `newAdmin`.
* *
* Requirements: * Requirements:
* *
* - This contract must be the current admin of `proxy`. * - This contract must be the current admin of `proxy`.
*/ */
function changeProxyAdmin(TransparentUpgradeableProxy proxy, address newAdmin) public onlyOwner { function changeProxyAdmin(TransparentUpgradeableProxy proxy, address newAdmin) public virtual onlyOwner {
proxy.changeAdmin(newAdmin); proxy.changeAdmin(newAdmin);
} }
/** /**
* @dev Upgrades `proxy` to `implementation`. See {TransparentUpgradeableProxy-upgradeTo}. * @dev Upgrades `proxy` to `implementation`. See {TransparentUpgradeableProxy-upgradeTo}.
* *
* Requirements: * Requirements:
* *
* - This contract must be the admin of `proxy`. * - This contract must be the admin of `proxy`.
*/ */
function upgrade(TransparentUpgradeableProxy proxy, address implementation) public onlyOwner { function upgrade(TransparentUpgradeableProxy proxy, address implementation) public virtual onlyOwner {
proxy.upgradeTo(implementation); proxy.upgradeTo(implementation);
} }
/** /**
* @dev Upgrades `proxy` to `implementation` and calls a function on the new implementation. See * @dev Upgrades `proxy` to `implementation` and calls a function on the new implementation. See
* {TransparentUpgradeableProxy-upgradeToAndCall}. * {TransparentUpgradeableProxy-upgradeToAndCall}.
* *
* Requirements: * Requirements:
* *
* - This contract must be the admin of `proxy`. * - This contract must be the admin of `proxy`.
*/ */
function upgradeAndCall(TransparentUpgradeableProxy proxy, address implementation, bytes memory data) public payable onlyOwner { function upgradeAndCall(TransparentUpgradeableProxy proxy, address implementation, bytes memory data) public payable virtual onlyOwner {
proxy.upgradeToAndCall{value: msg.value}(implementation, data); proxy.upgradeToAndCall{value: msg.value}(implementation, data);
} }
} }
...@@ -3,12 +3,16 @@ ...@@ -3,12 +3,16 @@
[.readme-notice] [.readme-notice]
NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/api/proxy NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/api/proxy
This is a low-level set of contracts implementing the proxy pattern for upgradeability. For an in-depth overview of this pattern check out the xref:upgrades-plugins::proxies.adoc[Proxy Upgrade Pattern] page. This is a low-level set of contracts implementing different proxy patterns with and without upgradeability. For an in-depth overview of this pattern check out the xref:upgrades-plugins::proxies.adoc[Proxy Upgrade Pattern] page.
The abstract {Proxy} contract implements the core delegation functionality. If the concrete proxies that we provide below are not suitable, we encourage building on top of this base contract since it contains an assembly block that may be hard to get right. The abstract {Proxy} contract implements the core delegation functionality. If the concrete proxies that we provide below are not suitable, we encourage building on top of this base contract since it contains an assembly block that may be hard to get right.
Upgradeability is implemented in the {UpgradeableProxy} contract, although it provides only 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. Upgradeability is implemented in the {UpgradeableProxy} contract, although it provides only 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.
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 Buidler. 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 Buidler.
== Core == Core
...@@ -19,6 +23,18 @@ CAUTION: Using upgradeable proxies correctly and securely is a difficult task th ...@@ -19,6 +23,18 @@ CAUTION: Using upgradeable proxies correctly and securely is a difficult task th
{{TransparentUpgradeableProxy}} {{TransparentUpgradeableProxy}}
== Beacon
{{BeaconProxy}}
{{IBeacon}}
{{UpgradeableBeacon}}
== Minimal Clones
{{Clones}}
== Utilities == Utilities
{{Initializable}} {{Initializable}}
......
...@@ -6,22 +6,22 @@ import "./UpgradeableProxy.sol"; ...@@ -6,22 +6,22 @@ import "./UpgradeableProxy.sol";
/** /**
* @dev This contract implements a proxy that is upgradeable by an admin. * @dev This contract implements a proxy that is upgradeable by an admin.
* *
* To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector * To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector
* clashing], which can potentially be used in an attack, this contract uses the * clashing], which can potentially be used in an attack, this contract uses the
* https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two * https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two
* things that go hand in hand: * things that go hand in hand:
* *
* 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if * 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if
* that call matches one of the admin functions exposed by the proxy itself. * that call matches one of the admin functions exposed by the proxy itself.
* 2. If the admin calls the proxy, it can access the admin functions, but its calls will never be forwarded to the * 2. If the admin calls the proxy, it can access the admin functions, but its calls will never be forwarded to the
* implementation. If the admin tries to call a function on the implementation it will fail with an error that says * implementation. If the admin tries to call a function on the implementation it will fail with an error that says
* "admin cannot fallback to proxy target". * "admin cannot fallback to proxy target".
* *
* These properties mean that the admin account can only be used for admin actions like upgrading the proxy or changing * These properties mean that the admin account can only be used for admin actions like upgrading the proxy or changing
* the admin, so it's best if it's a dedicated account that is not used for anything else. This will avoid headaches due * the admin, so it's best if it's a dedicated account that is not used for anything else. This will avoid headaches due
* to sudden errors when trying to call a function from the proxy implementation. * to sudden errors when trying to call a function from the proxy implementation.
* *
* Our recommendation is for the dedicated account to be an instance of the {ProxyAdmin} contract. If set up this way, * Our recommendation is for the dedicated account to be an instance of the {ProxyAdmin} contract. If set up this way,
* you should think of the `ProxyAdmin` instance as the real administrative interface of your proxy. * you should think of the `ProxyAdmin` instance as the real administrative interface of your proxy.
*/ */
...@@ -60,9 +60,9 @@ contract TransparentUpgradeableProxy is UpgradeableProxy { ...@@ -60,9 +60,9 @@ contract TransparentUpgradeableProxy is UpgradeableProxy {
/** /**
* @dev Returns the current admin. * @dev Returns the current admin.
* *
* NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyAdmin}. * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyAdmin}.
* *
* TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the
* https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call. * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
* `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103` * `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`
...@@ -73,9 +73,9 @@ contract TransparentUpgradeableProxy is UpgradeableProxy { ...@@ -73,9 +73,9 @@ contract TransparentUpgradeableProxy is UpgradeableProxy {
/** /**
* @dev Returns the current implementation. * @dev Returns the current implementation.
* *
* NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyImplementation}. * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyImplementation}.
* *
* TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the
* https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call. * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
* `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc` * `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc`
...@@ -86,12 +86,12 @@ contract TransparentUpgradeableProxy is UpgradeableProxy { ...@@ -86,12 +86,12 @@ contract TransparentUpgradeableProxy is UpgradeableProxy {
/** /**
* @dev Changes the admin of the proxy. * @dev Changes the admin of the proxy.
* *
* Emits an {AdminChanged} event. * Emits an {AdminChanged} event.
* *
* NOTE: Only the admin can call this function. See {ProxyAdmin-changeProxyAdmin}. * NOTE: Only the admin can call this function. See {ProxyAdmin-changeProxyAdmin}.
*/ */
function changeAdmin(address newAdmin) external ifAdmin { function changeAdmin(address newAdmin) external virtual ifAdmin {
require(newAdmin != address(0), "TransparentUpgradeableProxy: new admin is the zero address"); require(newAdmin != address(0), "TransparentUpgradeableProxy: new admin is the zero address");
emit AdminChanged(_admin(), newAdmin); emit AdminChanged(_admin(), newAdmin);
_setAdmin(newAdmin); _setAdmin(newAdmin);
...@@ -99,10 +99,10 @@ contract TransparentUpgradeableProxy is UpgradeableProxy { ...@@ -99,10 +99,10 @@ contract TransparentUpgradeableProxy is UpgradeableProxy {
/** /**
* @dev Upgrade the implementation of the proxy. * @dev Upgrade the implementation of the proxy.
* *
* NOTE: Only the admin can call this function. See {ProxyAdmin-upgrade}. * NOTE: Only the admin can call this function. See {ProxyAdmin-upgrade}.
*/ */
function upgradeTo(address newImplementation) external ifAdmin { function upgradeTo(address newImplementation) external virtual ifAdmin {
_upgradeTo(newImplementation); _upgradeTo(newImplementation);
} }
...@@ -110,20 +110,18 @@ contract TransparentUpgradeableProxy is UpgradeableProxy { ...@@ -110,20 +110,18 @@ contract TransparentUpgradeableProxy is UpgradeableProxy {
* @dev Upgrade the implementation of the proxy, and then call a function from the new implementation as specified * @dev Upgrade the implementation of the proxy, and then call a function from the new implementation as specified
* by `data`, which should be an encoded function call. This is useful to initialize new storage variables in the * by `data`, which should be an encoded function call. This is useful to initialize new storage variables in the
* proxied contract. * proxied contract.
* *
* NOTE: Only the admin can call this function. See {ProxyAdmin-upgradeAndCall}. * NOTE: Only the admin can call this function. See {ProxyAdmin-upgradeAndCall}.
*/ */
function upgradeToAndCall(address newImplementation, bytes calldata data) external payable ifAdmin { function upgradeToAndCall(address newImplementation, bytes calldata data) external payable virtual ifAdmin {
_upgradeTo(newImplementation); _upgradeTo(newImplementation);
// solhint-disable-next-line avoid-low-level-calls Address.functionDelegateCall(newImplementation, data);
(bool success,) = newImplementation.delegatecall(data);
require(success);
} }
/** /**
* @dev Returns the current admin. * @dev Returns the current admin.
*/ */
function _admin() internal view returns (address adm) { function _admin() internal view virtual returns (address adm) {
bytes32 slot = _ADMIN_SLOT; bytes32 slot = _ADMIN_SLOT;
// solhint-disable-next-line no-inline-assembly // solhint-disable-next-line no-inline-assembly
assembly { assembly {
...@@ -146,7 +144,7 @@ contract TransparentUpgradeableProxy is UpgradeableProxy { ...@@ -146,7 +144,7 @@ contract TransparentUpgradeableProxy is UpgradeableProxy {
/** /**
* @dev Makes sure the admin cannot access the fallback function. See {Proxy-_beforeFallback}. * @dev Makes sure the admin cannot access the fallback function. See {Proxy-_beforeFallback}.
*/ */
function _beforeFallback() internal override virtual { function _beforeFallback() internal virtual override {
require(msg.sender != _admin(), "TransparentUpgradeableProxy: admin cannot fallback to proxy target"); require(msg.sender != _admin(), "TransparentUpgradeableProxy: admin cannot fallback to proxy target");
super._beforeFallback(); super._beforeFallback();
} }
......
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
import "./IBeacon.sol";
import "../access/Ownable.sol";
import "../utils/Address.sol";
/**
* @dev This contract is used in conjunction with one or more instances of {BeaconProxy} to determine their
* implementation contract, which is where they will delegate all function calls.
*
* An owner is able to change the implementation the beacon points to, thus upgrading the proxies that use this beacon.
*/
contract UpgradeableBeacon is IBeacon, Ownable {
address private _implementation;
/**
* @dev Emitted when the implementation returned by the beacon is changed.
*/
event Upgraded(address indexed implementation);
/**
* @dev Sets the address of the initial implementation, and the deployer account as the owner who can upgrade the
* beacon.
*/
constructor(address implementation_) {
_setImplementation(implementation_);
}
/**
* @dev Returns the current implementation address.
*/
function implementation() public view virtual override returns (address) {
return _implementation;
}
/**
* @dev Upgrades the beacon to a new implementation.
*
* Emits an {Upgraded} event.
*
* Requirements:
*
* - msg.sender must be the owner of the contract.
* - `newImplementation` must be a contract.
*/
function upgradeTo(address newImplementation) public virtual onlyOwner {
_setImplementation(newImplementation);
emit Upgraded(newImplementation);
}
/**
* @dev Sets the implementation contract address for this beacon
*
* Requirements:
*
* - `newImplementation` must be a contract.
*/
function _setImplementation(address newImplementation) private {
require(Address.isContract(newImplementation), "UpgradeableBeacon: implementation is not a contract");
_implementation = newImplementation;
}
}
...@@ -10,14 +10,14 @@ import "../utils/Address.sol"; ...@@ -10,14 +10,14 @@ import "../utils/Address.sol";
* implementation address that can be changed. This address is stored in storage in the location specified by * implementation address that can be changed. This address is stored in storage in the location specified by
* https://eips.ethereum.org/EIPS/eip-1967[EIP1967], so that it doesn't conflict with the storage layout of the * https://eips.ethereum.org/EIPS/eip-1967[EIP1967], so that it doesn't conflict with the storage layout of the
* implementation behind the proxy. * implementation behind the proxy.
* *
* Upgradeability is only provided internally through {_upgradeTo}. For an externally upgradeable proxy see * Upgradeability is only provided internally through {_upgradeTo}. For an externally upgradeable proxy see
* {TransparentUpgradeableProxy}. * {TransparentUpgradeableProxy}.
*/ */
contract UpgradeableProxy is Proxy { contract UpgradeableProxy is Proxy {
/** /**
* @dev Initializes the upgradeable proxy with an initial implementation specified by `_logic`. * @dev Initializes the upgradeable proxy with an initial implementation specified by `_logic`.
* *
* If `_data` is nonempty, it's used as data in a delegate call to `_logic`. This will typically be an encoded * If `_data` is nonempty, it's used as data in a delegate call to `_logic`. This will typically be an encoded
* function call, and allows initializating the storage of the proxy like a Solidity constructor. * function call, and allows initializating the storage of the proxy like a Solidity constructor.
*/ */
...@@ -25,9 +25,7 @@ contract UpgradeableProxy is Proxy { ...@@ -25,9 +25,7 @@ contract UpgradeableProxy is Proxy {
assert(_IMPLEMENTATION_SLOT == bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1)); assert(_IMPLEMENTATION_SLOT == bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1));
_setImplementation(_logic); _setImplementation(_logic);
if(_data.length > 0) { if(_data.length > 0) {
// solhint-disable-next-line avoid-low-level-calls Address.functionDelegateCall(_logic, _data);
(bool success,) = _logic.delegatecall(_data);
require(success);
} }
} }
...@@ -46,7 +44,7 @@ contract UpgradeableProxy is Proxy { ...@@ -46,7 +44,7 @@ contract UpgradeableProxy is Proxy {
/** /**
* @dev Returns the current implementation address. * @dev Returns the current implementation address.
*/ */
function _implementation() internal override view returns (address impl) { function _implementation() internal view virtual override returns (address impl) {
bytes32 slot = _IMPLEMENTATION_SLOT; bytes32 slot = _IMPLEMENTATION_SLOT;
// solhint-disable-next-line no-inline-assembly // solhint-disable-next-line no-inline-assembly
assembly { assembly {
...@@ -56,10 +54,10 @@ contract UpgradeableProxy is Proxy { ...@@ -56,10 +54,10 @@ contract UpgradeableProxy is Proxy {
/** /**
* @dev Upgrades the proxy to a new implementation. * @dev Upgrades the proxy to a new implementation.
* *
* Emits an {Upgraded} event. * Emits an {Upgraded} event.
*/ */
function _upgradeTo(address newImplementation) internal { function _upgradeTo(address newImplementation) internal virtual {
_setImplementation(newImplementation); _setImplementation(newImplementation);
emit Upgraded(newImplementation); emit Upgraded(newImplementation);
} }
......
...@@ -5,7 +5,7 @@ pragma solidity ^0.7.0; ...@@ -5,7 +5,7 @@ pragma solidity ^0.7.0;
import "./IERC1155.sol"; import "./IERC1155.sol";
import "./IERC1155MetadataURI.sol"; import "./IERC1155MetadataURI.sol";
import "./IERC1155Receiver.sol"; import "./IERC1155Receiver.sol";
import "../../GSN/Context.sol"; import "../../utils/Context.sol";
import "../../introspection/ERC165.sol"; import "../../introspection/ERC165.sol";
import "../../math/SafeMath.sol"; import "../../math/SafeMath.sol";
import "../../utils/Address.sol"; import "../../utils/Address.sol";
...@@ -72,7 +72,7 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { ...@@ -72,7 +72,7 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI {
* Clients calling this function must replace the `\{id\}` substring with the * Clients calling this function must replace the `\{id\}` substring with the
* actual token type ID. * actual token type ID.
*/ */
function uri(uint256) external view override returns (string memory) { function uri(uint256) external view virtual override returns (string memory) {
return _uri; return _uri;
} }
...@@ -83,7 +83,7 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { ...@@ -83,7 +83,7 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI {
* *
* - `account` cannot be the zero address. * - `account` cannot be the zero address.
*/ */
function balanceOf(address account, uint256 id) public view override returns (uint256) { function balanceOf(address account, uint256 id) public view virtual override returns (uint256) {
require(account != address(0), "ERC1155: balance query for the zero address"); require(account != address(0), "ERC1155: balance query for the zero address");
return _balances[id][account]; return _balances[id][account];
} }
...@@ -101,6 +101,7 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { ...@@ -101,6 +101,7 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI {
) )
public public
view view
virtual
override override
returns (uint256[] memory) returns (uint256[] memory)
{ {
...@@ -109,8 +110,7 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { ...@@ -109,8 +110,7 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI {
uint256[] memory batchBalances = new uint256[](accounts.length); uint256[] memory batchBalances = new uint256[](accounts.length);
for (uint256 i = 0; i < accounts.length; ++i) { for (uint256 i = 0; i < accounts.length; ++i) {
require(accounts[i] != address(0), "ERC1155: batch balance query for the zero address"); batchBalances[i] = balanceOf(accounts[i], ids[i]);
batchBalances[i] = _balances[ids[i]][accounts[i]];
} }
return batchBalances; return batchBalances;
...@@ -129,7 +129,7 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { ...@@ -129,7 +129,7 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI {
/** /**
* @dev See {IERC1155-isApprovedForAll}. * @dev See {IERC1155-isApprovedForAll}.
*/ */
function isApprovedForAll(address account, address operator) public view override returns (bool) { function isApprovedForAll(address account, address operator) public view virtual override returns (bool) {
return _operatorApprovals[account][operator]; return _operatorApprovals[account][operator];
} }
...@@ -237,7 +237,7 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { ...@@ -237,7 +237,7 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI {
* Requirements: * Requirements:
* *
* - `account` cannot be the zero address. * - `account` cannot be the zero address.
* - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the * - If `account` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
* acceptance magic value. * acceptance magic value.
*/ */
function _mint(address account, uint256 id, uint256 amount, bytes memory data) internal virtual { function _mint(address account, uint256 id, uint256 amount, bytes memory data) internal virtual {
...@@ -355,7 +355,8 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { ...@@ -355,7 +355,8 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI {
uint256[] memory amounts, uint256[] memory amounts,
bytes memory data bytes memory data
) )
internal virtual internal
virtual
{ } { }
function _doSafeTransferAcceptanceCheck( function _doSafeTransferAcceptanceCheck(
......
...@@ -30,7 +30,9 @@ abstract contract ERC1155Pausable is ERC1155, Pausable { ...@@ -30,7 +30,9 @@ abstract contract ERC1155Pausable is ERC1155, Pausable {
uint256[] memory amounts, uint256[] memory amounts,
bytes memory data bytes memory data
) )
internal virtual override internal
virtual
override
{ {
super._beforeTokenTransfer(operator, from, to, ids, amounts, data); super._beforeTokenTransfer(operator, from, to, ids, amounts, data);
......
...@@ -11,8 +11,8 @@ import "../../introspection/ERC165.sol"; ...@@ -11,8 +11,8 @@ import "../../introspection/ERC165.sol";
abstract contract ERC1155Receiver is ERC165, IERC1155Receiver { abstract contract ERC1155Receiver is ERC165, IERC1155Receiver {
constructor() { constructor() {
_registerInterface( _registerInterface(
ERC1155Receiver(0).onERC1155Received.selector ^ ERC1155Receiver(address(0)).onERC1155Received.selector ^
ERC1155Receiver(0).onERC1155BatchReceived.selector ERC1155Receiver(address(0)).onERC1155BatchReceived.selector
); );
} }
} }
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
pragma solidity ^0.7.0; pragma solidity ^0.7.0;
import "../../GSN/Context.sol"; import "../../utils/Context.sol";
import "./IERC20.sol"; import "./IERC20.sol";
import "../../math/SafeMath.sol"; import "../../math/SafeMath.sol";
...@@ -61,7 +61,7 @@ contract ERC20 is Context, IERC20 { ...@@ -61,7 +61,7 @@ contract ERC20 is Context, IERC20 {
/** /**
* @dev Returns the name of the token. * @dev Returns the name of the token.
*/ */
function name() public view returns (string memory) { function name() public view virtual returns (string memory) {
return _name; return _name;
} }
...@@ -69,7 +69,7 @@ contract ERC20 is Context, IERC20 { ...@@ -69,7 +69,7 @@ contract ERC20 is Context, IERC20 {
* @dev Returns the symbol of the token, usually a shorter version of the * @dev Returns the symbol of the token, usually a shorter version of the
* name. * name.
*/ */
function symbol() public view returns (string memory) { function symbol() public view virtual returns (string memory) {
return _symbol; return _symbol;
} }
...@@ -86,21 +86,21 @@ contract ERC20 is Context, IERC20 { ...@@ -86,21 +86,21 @@ contract ERC20 is Context, IERC20 {
* no way affects any of the arithmetic of the contract, including * no way affects any of the arithmetic of the contract, including
* {IERC20-balanceOf} and {IERC20-transfer}. * {IERC20-balanceOf} and {IERC20-transfer}.
*/ */
function decimals() public view returns (uint8) { function decimals() public view virtual returns (uint8) {
return _decimals; return _decimals;
} }
/** /**
* @dev See {IERC20-totalSupply}. * @dev See {IERC20-totalSupply}.
*/ */
function totalSupply() public view override returns (uint256) { function totalSupply() public view virtual override returns (uint256) {
return _totalSupply; return _totalSupply;
} }
/** /**
* @dev See {IERC20-balanceOf}. * @dev See {IERC20-balanceOf}.
*/ */
function balanceOf(address account) public view override returns (uint256) { function balanceOf(address account) public view virtual override returns (uint256) {
return _balances[account]; return _balances[account];
} }
...@@ -284,7 +284,7 @@ contract ERC20 is Context, IERC20 { ...@@ -284,7 +284,7 @@ contract ERC20 is Context, IERC20 {
* applications that interact with token contracts will not expect * applications that interact with token contracts will not expect
* {decimals} to ever change, and may work incorrectly if it does. * {decimals} to ever change, and may work incorrectly if it does.
*/ */
function _setupDecimals(uint8 decimals_) internal { function _setupDecimals(uint8 decimals_) internal virtual {
_decimals = decimals_; _decimals = decimals_;
} }
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
pragma solidity ^0.7.0; pragma solidity ^0.7.0;
import "../../GSN/Context.sol"; import "../../utils/Context.sol";
import "./ERC20.sol"; import "./ERC20.sol";
/** /**
......
...@@ -24,7 +24,7 @@ abstract contract ERC20Capped is ERC20 { ...@@ -24,7 +24,7 @@ abstract contract ERC20Capped is ERC20 {
/** /**
* @dev Returns the cap on the token's total supply. * @dev Returns the cap on the token's total supply.
*/ */
function cap() public view returns (uint256) { function cap() public view virtual returns (uint256) {
return _cap; return _cap;
} }
...@@ -39,7 +39,7 @@ abstract contract ERC20Capped is ERC20 { ...@@ -39,7 +39,7 @@ abstract contract ERC20Capped is ERC20 {
super._beforeTokenTransfer(from, to, amount); super._beforeTokenTransfer(from, to, amount);
if (from == address(0)) { // When minting tokens if (from == address(0)) { // When minting tokens
require(totalSupply().add(amount) <= _cap, "ERC20Capped: cap exceeded"); require(totalSupply().add(amount) <= cap(), "ERC20Capped: cap exceeded");
} }
} }
} }
...@@ -89,7 +89,7 @@ abstract contract ERC20Snapshot is ERC20 { ...@@ -89,7 +89,7 @@ abstract contract ERC20Snapshot is ERC20 {
/** /**
* @dev Retrieves the balance of `account` at the time `snapshotId` was created. * @dev Retrieves the balance of `account` at the time `snapshotId` was created.
*/ */
function balanceOfAt(address account, uint256 snapshotId) public view returns (uint256) { function balanceOfAt(address account, uint256 snapshotId) public view virtual returns (uint256) {
(bool snapshotted, uint256 value) = _valueAt(snapshotId, _accountBalanceSnapshots[account]); (bool snapshotted, uint256 value) = _valueAt(snapshotId, _accountBalanceSnapshots[account]);
return snapshotted ? value : balanceOf(account); return snapshotted ? value : balanceOf(account);
...@@ -98,7 +98,7 @@ abstract contract ERC20Snapshot is ERC20 { ...@@ -98,7 +98,7 @@ abstract contract ERC20Snapshot is ERC20 {
/** /**
* @dev Retrieves the total supply at the time `snapshotId` was created. * @dev Retrieves the total supply at the time `snapshotId` was created.
*/ */
function totalSupplyAt(uint256 snapshotId) public view returns(uint256) { function totalSupplyAt(uint256 snapshotId) public view virtual returns(uint256) {
(bool snapshotted, uint256 value) = _valueAt(snapshotId, _totalSupplySnapshots); (bool snapshotted, uint256 value) = _valueAt(snapshotId, _totalSupplySnapshots);
return snapshotted ? value : totalSupply(); return snapshotted ? value : totalSupply();
......
...@@ -14,15 +14,21 @@ There a few core contracts that implement the behavior specified in the EIP: ...@@ -14,15 +14,21 @@ There a few core contracts that implement the behavior specified in the EIP:
Additionally there are multiple custom extensions, including: Additionally there are multiple custom extensions, including:
* designation of addresses that can pause token transfers for all users ({ERC20Pausable}). * {ERC20Permit}: gasless approval of tokens.
* efficient storage of past token balances to be later queried at any point in time ({ERC20Snapshot}). * {ERC20Snapshot}: efficient storage of past token balances to be later queried at any point in time.
* destruction of own tokens ({ERC20Burnable}). * {ERC20Burnable}: destruction of own tokens.
* enforcement of a cap to the total supply when minting tokens ({ERC20Capped}). * {ERC20Capped}: enforcement of a cap to the total supply when minting tokens.
* {ERC20Pausable}: ability to pause token transfers.
Finally, there are some utilities to interact with ERC20 contracts in various ways. Finally, there are some utilities to interact with ERC20 contracts in various ways.
* {SafeERC20} is a wrapper around the interface that eliminates the need to handle boolean return values. * {SafeERC20}: a wrapper around the interface that eliminates the need to handle boolean return values.
* {TokenTimelock} can hold tokens for a beneficiary until a specified time. * {TokenTimelock}: hold tokens for a beneficiary until a specified time.
The following related EIPs are in draft status and can be found in the drafts directory.
- {IERC20Permit}
- {ERC20Permit}
NOTE: This core set of contracts is designed to be unopinionated, allowing developers to access the internal functions in ERC20 (such as <<ERC20-_mint-address-uint256-,`_mint`>>) and expose them as external functions in the way they prefer. On the other hand, xref:ROOT:erc20.adoc#Presets[ERC20 Presets] (such as {ERC20PresetMinterPauser}) are designed using opinionated patterns to provide developers with ready to use, deployable contracts. NOTE: This core set of contracts is designed to be unopinionated, allowing developers to access the internal functions in ERC20 (such as <<ERC20-_mint-address-uint256-,`_mint`>>) and expose them as external functions in the way they prefer. On the other hand, xref:ROOT:erc20.adoc#Presets[ERC20 Presets] (such as {ERC20PresetMinterPauser}) are designed using opinionated patterns to provide developers with ready to use, deployable contracts.
......
...@@ -34,21 +34,21 @@ contract TokenTimelock { ...@@ -34,21 +34,21 @@ contract TokenTimelock {
/** /**
* @return the token being held. * @return the token being held.
*/ */
function token() public view returns (IERC20) { function token() public view virtual returns (IERC20) {
return _token; return _token;
} }
/** /**
* @return the beneficiary of the tokens. * @return the beneficiary of the tokens.
*/ */
function beneficiary() public view returns (address) { function beneficiary() public view virtual returns (address) {
return _beneficiary; return _beneficiary;
} }
/** /**
* @return the time when the tokens are released. * @return the time when the tokens are released.
*/ */
function releaseTime() public view returns (uint256) { function releaseTime() public view virtual returns (uint256) {
return _releaseTime; return _releaseTime;
} }
...@@ -57,11 +57,11 @@ contract TokenTimelock { ...@@ -57,11 +57,11 @@ contract TokenTimelock {
*/ */
function release() public virtual { function release() public virtual {
// solhint-disable-next-line not-rely-on-time // solhint-disable-next-line not-rely-on-time
require(block.timestamp >= _releaseTime, "TokenTimelock: current time is before release time"); require(block.timestamp >= releaseTime(), "TokenTimelock: current time is before release time");
uint256 amount = _token.balanceOf(address(this)); uint256 amount = token().balanceOf(address(this));
require(amount > 0, "TokenTimelock: no tokens to release"); require(amount > 0, "TokenTimelock: no tokens to release");
_token.safeTransfer(_beneficiary, amount); token().safeTransfer(beneficiary(), amount);
} }
} }
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
pragma solidity ^0.7.0; pragma solidity ^0.7.0;
import "../../GSN/Context.sol"; import "../../utils/Context.sol";
import "./IERC721.sol"; import "./IERC721.sol";
import "./IERC721Metadata.sol"; import "./IERC721Metadata.sol";
import "./IERC721Enumerable.sol"; import "./IERC721Enumerable.sol";
...@@ -103,51 +103,51 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Enumerable ...@@ -103,51 +103,51 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Enumerable
/** /**
* @dev See {IERC721-balanceOf}. * @dev See {IERC721-balanceOf}.
*/ */
function balanceOf(address owner) public view override returns (uint256) { function balanceOf(address owner) public view virtual override returns (uint256) {
require(owner != address(0), "ERC721: balance query for the zero address"); require(owner != address(0), "ERC721: balance query for the zero address");
return _holderTokens[owner].length(); return _holderTokens[owner].length();
} }
/** /**
* @dev See {IERC721-ownerOf}. * @dev See {IERC721-ownerOf}.
*/ */
function ownerOf(uint256 tokenId) public view override returns (address) { function ownerOf(uint256 tokenId) public view virtual override returns (address) {
return _tokenOwners.get(tokenId, "ERC721: owner query for nonexistent token"); return _tokenOwners.get(tokenId, "ERC721: owner query for nonexistent token");
} }
/** /**
* @dev See {IERC721Metadata-name}. * @dev See {IERC721Metadata-name}.
*/ */
function name() public view override returns (string memory) { function name() public view virtual override returns (string memory) {
return _name; return _name;
} }
/** /**
* @dev See {IERC721Metadata-symbol}. * @dev See {IERC721Metadata-symbol}.
*/ */
function symbol() public view override returns (string memory) { function symbol() public view virtual override returns (string memory) {
return _symbol; return _symbol;
} }
/** /**
* @dev See {IERC721Metadata-tokenURI}. * @dev See {IERC721Metadata-tokenURI}.
*/ */
function tokenURI(uint256 tokenId) public view override returns (string memory) { function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token"); require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");
string memory _tokenURI = _tokenURIs[tokenId]; string memory _tokenURI = _tokenURIs[tokenId];
string memory base = baseURI();
// If there is no base URI, return the token URI. // If there is no base URI, return the token URI.
if (bytes(_baseURI).length == 0) { if (bytes(base).length == 0) {
return _tokenURI; return _tokenURI;
} }
// If both are set, concatenate the baseURI and tokenURI (via abi.encodePacked). // If both are set, concatenate the baseURI and tokenURI (via abi.encodePacked).
if (bytes(_tokenURI).length > 0) { if (bytes(_tokenURI).length > 0) {
return string(abi.encodePacked(_baseURI, _tokenURI)); return string(abi.encodePacked(base, _tokenURI));
} }
// If there is a baseURI but no tokenURI, concatenate the tokenID to the baseURI. // If there is a baseURI but no tokenURI, concatenate the tokenID to the baseURI.
return string(abi.encodePacked(_baseURI, tokenId.toString())); return string(abi.encodePacked(base, tokenId.toString()));
} }
/** /**
...@@ -155,21 +155,21 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Enumerable ...@@ -155,21 +155,21 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Enumerable
* automatically added as a prefix in {tokenURI} to each token's URI, or * automatically added as a prefix in {tokenURI} to each token's URI, or
* to the token ID if no specific URI is set for that token ID. * to the token ID if no specific URI is set for that token ID.
*/ */
function baseURI() public view returns (string memory) { function baseURI() public view virtual returns (string memory) {
return _baseURI; return _baseURI;
} }
/** /**
* @dev See {IERC721Enumerable-tokenOfOwnerByIndex}. * @dev See {IERC721Enumerable-tokenOfOwnerByIndex}.
*/ */
function tokenOfOwnerByIndex(address owner, uint256 index) public view override returns (uint256) { function tokenOfOwnerByIndex(address owner, uint256 index) public view virtual override returns (uint256) {
return _holderTokens[owner].at(index); return _holderTokens[owner].at(index);
} }
/** /**
* @dev See {IERC721Enumerable-totalSupply}. * @dev See {IERC721Enumerable-totalSupply}.
*/ */
function totalSupply() public view override returns (uint256) { function totalSupply() public view virtual override returns (uint256) {
// _tokenOwners are indexed by tokenIds, so .length() returns the number of tokenIds // _tokenOwners are indexed by tokenIds, so .length() returns the number of tokenIds
return _tokenOwners.length(); return _tokenOwners.length();
} }
...@@ -177,7 +177,7 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Enumerable ...@@ -177,7 +177,7 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Enumerable
/** /**
* @dev See {IERC721Enumerable-tokenByIndex}. * @dev See {IERC721Enumerable-tokenByIndex}.
*/ */
function tokenByIndex(uint256 index) public view override returns (uint256) { function tokenByIndex(uint256 index) public view virtual override returns (uint256) {
(uint256 tokenId, ) = _tokenOwners.at(index); (uint256 tokenId, ) = _tokenOwners.at(index);
return tokenId; return tokenId;
} }
...@@ -186,10 +186,10 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Enumerable ...@@ -186,10 +186,10 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Enumerable
* @dev See {IERC721-approve}. * @dev See {IERC721-approve}.
*/ */
function approve(address to, uint256 tokenId) public virtual override { function approve(address to, uint256 tokenId) public virtual override {
address owner = ownerOf(tokenId); address owner = ERC721.ownerOf(tokenId);
require(to != owner, "ERC721: approval to current owner"); require(to != owner, "ERC721: approval to current owner");
require(_msgSender() == owner || isApprovedForAll(owner, _msgSender()), require(_msgSender() == owner || ERC721.isApprovedForAll(owner, _msgSender()),
"ERC721: approve caller is not owner nor approved for all" "ERC721: approve caller is not owner nor approved for all"
); );
...@@ -199,7 +199,7 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Enumerable ...@@ -199,7 +199,7 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Enumerable
/** /**
* @dev See {IERC721-getApproved}. * @dev See {IERC721-getApproved}.
*/ */
function getApproved(uint256 tokenId) public view override returns (address) { function getApproved(uint256 tokenId) public view virtual override returns (address) {
require(_exists(tokenId), "ERC721: approved query for nonexistent token"); require(_exists(tokenId), "ERC721: approved query for nonexistent token");
return _tokenApprovals[tokenId]; return _tokenApprovals[tokenId];
...@@ -218,7 +218,7 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Enumerable ...@@ -218,7 +218,7 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Enumerable
/** /**
* @dev See {IERC721-isApprovedForAll}. * @dev See {IERC721-isApprovedForAll}.
*/ */
function isApprovedForAll(address owner, address operator) public view override returns (bool) { function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) {
return _operatorApprovals[owner][operator]; return _operatorApprovals[owner][operator];
} }
...@@ -278,7 +278,7 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Enumerable ...@@ -278,7 +278,7 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Enumerable
* Tokens start existing when they are minted (`_mint`), * Tokens start existing when they are minted (`_mint`),
* and stop existing when they are burned (`_burn`). * and stop existing when they are burned (`_burn`).
*/ */
function _exists(uint256 tokenId) internal view returns (bool) { function _exists(uint256 tokenId) internal view virtual returns (bool) {
return _tokenOwners.contains(tokenId); return _tokenOwners.contains(tokenId);
} }
...@@ -289,10 +289,10 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Enumerable ...@@ -289,10 +289,10 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Enumerable
* *
* - `tokenId` must exist. * - `tokenId` must exist.
*/ */
function _isApprovedOrOwner(address spender, uint256 tokenId) internal view returns (bool) { function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) {
require(_exists(tokenId), "ERC721: operator query for nonexistent token"); require(_exists(tokenId), "ERC721: operator query for nonexistent token");
address owner = ownerOf(tokenId); address owner = ERC721.ownerOf(tokenId);
return (spender == owner || getApproved(tokenId) == spender || isApprovedForAll(owner, spender)); return (spender == owner || getApproved(tokenId) == spender || ERC721.isApprovedForAll(owner, spender));
} }
/** /**
...@@ -354,7 +354,7 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Enumerable ...@@ -354,7 +354,7 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Enumerable
* Emits a {Transfer} event. * Emits a {Transfer} event.
*/ */
function _burn(uint256 tokenId) internal virtual { function _burn(uint256 tokenId) internal virtual {
address owner = ownerOf(tokenId); address owner = ERC721.ownerOf(tokenId); // internal owner
_beforeTokenTransfer(owner, address(0), tokenId); _beforeTokenTransfer(owner, address(0), tokenId);
...@@ -385,7 +385,7 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Enumerable ...@@ -385,7 +385,7 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Enumerable
* Emits a {Transfer} event. * Emits a {Transfer} event.
*/ */
function _transfer(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(ERC721.ownerOf(tokenId) == from, "ERC721: transfer of token that is not own"); // internal owner
require(to != address(0), "ERC721: transfer to the zero address"); require(to != address(0), "ERC721: transfer to the zero address");
_beforeTokenTransfer(from, to, tokenId); _beforeTokenTransfer(from, to, tokenId);
...@@ -451,7 +451,7 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Enumerable ...@@ -451,7 +451,7 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Enumerable
function _approve(address to, uint256 tokenId) private { function _approve(address to, uint256 tokenId) private {
_tokenApprovals[tokenId] = to; _tokenApprovals[tokenId] = to;
emit Approval(ownerOf(tokenId), to, tokenId); emit Approval(ERC721.ownerOf(tokenId), to, tokenId); // internal owner
} }
/** /**
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
pragma solidity ^0.7.0; pragma solidity ^0.7.0;
import "../../GSN/Context.sol"; import "../../utils/Context.sol";
import "./ERC721.sol"; import "./ERC721.sol";
/** /**
......
...@@ -117,8 +117,8 @@ interface IERC721 is IERC165 { ...@@ -117,8 +117,8 @@ interface IERC721 is IERC165 {
* *
* Requirements: * Requirements:
* *
* - `from` cannot be the zero address. * - `from` cannot be the zero address.
* - `to` cannot be the zero address. * - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`. * - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
pragma solidity ^0.7.0; pragma solidity ^0.7.0;
import "../../GSN/Context.sol"; import "../../utils/Context.sol";
import "./IERC777.sol"; import "./IERC777.sol";
import "./IERC777Recipient.sol"; import "./IERC777Recipient.sol";
import "./IERC777Sender.sol"; import "./IERC777Sender.sol";
...@@ -87,14 +87,14 @@ contract ERC777 is Context, IERC777, IERC20 { ...@@ -87,14 +87,14 @@ contract ERC777 is Context, IERC777, IERC20 {
/** /**
* @dev See {IERC777-name}. * @dev See {IERC777-name}.
*/ */
function name() public view override returns (string memory) { function name() public view virtual override returns (string memory) {
return _name; return _name;
} }
/** /**
* @dev See {IERC777-symbol}. * @dev See {IERC777-symbol}.
*/ */
function symbol() public view override returns (string memory) { function symbol() public view virtual override returns (string memory) {
return _symbol; return _symbol;
} }
...@@ -104,7 +104,7 @@ contract ERC777 is Context, IERC777, IERC20 { ...@@ -104,7 +104,7 @@ contract ERC777 is Context, IERC777, IERC20 {
* Always returns 18, as per the * Always returns 18, as per the
* [ERC777 EIP](https://eips.ethereum.org/EIPS/eip-777#backward-compatibility). * [ERC777 EIP](https://eips.ethereum.org/EIPS/eip-777#backward-compatibility).
*/ */
function decimals() public pure returns (uint8) { function decimals() public pure virtual returns (uint8) {
return 18; return 18;
} }
...@@ -113,21 +113,21 @@ contract ERC777 is Context, IERC777, IERC20 { ...@@ -113,21 +113,21 @@ contract ERC777 is Context, IERC777, IERC20 {
* *
* This implementation always returns `1`. * This implementation always returns `1`.
*/ */
function granularity() public pure override returns (uint256) { function granularity() public view virtual override returns (uint256) {
return 1; return 1;
} }
/** /**
* @dev See {IERC777-totalSupply}. * @dev See {IERC777-totalSupply}.
*/ */
function totalSupply() public view override(IERC20, IERC777) returns (uint256) { function totalSupply() public view virtual override(IERC20, IERC777) returns (uint256) {
return _totalSupply; return _totalSupply;
} }
/** /**
* @dev Returns the amount of tokens owned by an account (`tokenHolder`). * @dev Returns the amount of tokens owned by an account (`tokenHolder`).
*/ */
function balanceOf(address tokenHolder) public view override(IERC20, IERC777) returns (uint256) { function balanceOf(address tokenHolder) public view virtual override(IERC20, IERC777) returns (uint256) {
return _balances[tokenHolder]; return _balances[tokenHolder];
} }
...@@ -136,7 +136,7 @@ contract ERC777 is Context, IERC777, IERC20 { ...@@ -136,7 +136,7 @@ contract ERC777 is Context, IERC777, IERC20 {
* *
* Also emits a {IERC20-Transfer} event for ERC20 compatibility. * Also emits a {IERC20-Transfer} event for ERC20 compatibility.
*/ */
function send(address recipient, uint256 amount, bytes memory data) public override { function send(address recipient, uint256 amount, bytes memory data) public virtual override {
_send(_msgSender(), recipient, amount, data, "", true); _send(_msgSender(), recipient, amount, data, "", true);
} }
...@@ -148,7 +148,7 @@ contract ERC777 is Context, IERC777, IERC20 { ...@@ -148,7 +148,7 @@ contract ERC777 is Context, IERC777, IERC20 {
* *
* Also emits a {Sent} event. * Also emits a {Sent} event.
*/ */
function transfer(address recipient, uint256 amount) public override returns (bool) { function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
require(recipient != address(0), "ERC777: transfer to the zero address"); require(recipient != address(0), "ERC777: transfer to the zero address");
address from = _msgSender(); address from = _msgSender();
...@@ -167,17 +167,14 @@ contract ERC777 is Context, IERC777, IERC20 { ...@@ -167,17 +167,14 @@ contract ERC777 is Context, IERC777, IERC20 {
* *
* Also emits a {IERC20-Transfer} event for ERC20 compatibility. * Also emits a {IERC20-Transfer} event for ERC20 compatibility.
*/ */
function burn(uint256 amount, bytes memory data) public override { function burn(uint256 amount, bytes memory data) public virtual override {
_burn(_msgSender(), amount, data, ""); _burn(_msgSender(), amount, data, "");
} }
/** /**
* @dev See {IERC777-isOperatorFor}. * @dev See {IERC777-isOperatorFor}.
*/ */
function isOperatorFor( function isOperatorFor(address operator, address tokenHolder) public view virtual override returns (bool) {
address operator,
address tokenHolder
) public view override returns (bool) {
return operator == tokenHolder || return operator == tokenHolder ||
(_defaultOperators[operator] && !_revokedDefaultOperators[tokenHolder][operator]) || (_defaultOperators[operator] && !_revokedDefaultOperators[tokenHolder][operator]) ||
_operators[tokenHolder][operator]; _operators[tokenHolder][operator];
...@@ -186,7 +183,7 @@ contract ERC777 is Context, IERC777, IERC20 { ...@@ -186,7 +183,7 @@ contract ERC777 is Context, IERC777, IERC20 {
/** /**
* @dev See {IERC777-authorizeOperator}. * @dev See {IERC777-authorizeOperator}.
*/ */
function authorizeOperator(address operator) public override { function authorizeOperator(address operator) public virtual override {
require(_msgSender() != operator, "ERC777: authorizing self as operator"); require(_msgSender() != operator, "ERC777: authorizing self as operator");
if (_defaultOperators[operator]) { if (_defaultOperators[operator]) {
...@@ -201,7 +198,7 @@ contract ERC777 is Context, IERC777, IERC20 { ...@@ -201,7 +198,7 @@ contract ERC777 is Context, IERC777, IERC20 {
/** /**
* @dev See {IERC777-revokeOperator}. * @dev See {IERC777-revokeOperator}.
*/ */
function revokeOperator(address operator) public override { function revokeOperator(address operator) public virtual override {
require(operator != _msgSender(), "ERC777: revoking self as operator"); require(operator != _msgSender(), "ERC777: revoking self as operator");
if (_defaultOperators[operator]) { if (_defaultOperators[operator]) {
...@@ -216,7 +213,7 @@ contract ERC777 is Context, IERC777, IERC20 { ...@@ -216,7 +213,7 @@ contract ERC777 is Context, IERC777, IERC20 {
/** /**
* @dev See {IERC777-defaultOperators}. * @dev See {IERC777-defaultOperators}.
*/ */
function defaultOperators() public view override returns (address[] memory) { function defaultOperators() public view virtual override returns (address[] memory) {
return _defaultOperatorsArray; return _defaultOperatorsArray;
} }
...@@ -232,7 +229,9 @@ contract ERC777 is Context, IERC777, IERC20 { ...@@ -232,7 +229,9 @@ contract ERC777 is Context, IERC777, IERC20 {
bytes memory data, bytes memory data,
bytes memory operatorData bytes memory operatorData
) )
public override public
virtual
override
{ {
require(isOperatorFor(_msgSender(), sender), "ERC777: caller is not an operator for holder"); require(isOperatorFor(_msgSender(), sender), "ERC777: caller is not an operator for holder");
_send(sender, recipient, amount, data, operatorData, true); _send(sender, recipient, amount, data, operatorData, true);
...@@ -243,7 +242,7 @@ contract ERC777 is Context, IERC777, IERC20 { ...@@ -243,7 +242,7 @@ contract ERC777 is Context, IERC777, IERC20 {
* *
* Emits {Burned} and {IERC20-Transfer} events. * Emits {Burned} and {IERC20-Transfer} events.
*/ */
function operatorBurn(address account, uint256 amount, bytes memory data, bytes memory operatorData) public override { function operatorBurn(address account, uint256 amount, bytes memory data, bytes memory operatorData) public virtual override {
require(isOperatorFor(_msgSender(), account), "ERC777: caller is not an operator for holder"); require(isOperatorFor(_msgSender(), account), "ERC777: caller is not an operator for holder");
_burn(account, amount, data, operatorData); _burn(account, amount, data, operatorData);
} }
...@@ -255,7 +254,7 @@ contract ERC777 is Context, IERC777, IERC20 { ...@@ -255,7 +254,7 @@ contract ERC777 is Context, IERC777, IERC20 {
* not have allowance, and accounts with allowance may not be operators * not have allowance, and accounts with allowance may not be operators
* themselves. * themselves.
*/ */
function allowance(address holder, address spender) public view override returns (uint256) { function allowance(address holder, address spender) public view virtual override returns (uint256) {
return _allowances[holder][spender]; return _allowances[holder][spender];
} }
...@@ -264,7 +263,7 @@ contract ERC777 is Context, IERC777, IERC20 { ...@@ -264,7 +263,7 @@ contract ERC777 is Context, IERC777, IERC20 {
* *
* Note that accounts cannot have allowance issued by their operators. * Note that accounts cannot have allowance issued by their operators.
*/ */
function approve(address spender, uint256 value) public override returns (bool) { function approve(address spender, uint256 value) public virtual override returns (bool) {
address holder = _msgSender(); address holder = _msgSender();
_approve(holder, spender, value); _approve(holder, spender, value);
return true; return true;
...@@ -279,7 +278,7 @@ contract ERC777 is Context, IERC777, IERC20 { ...@@ -279,7 +278,7 @@ contract ERC777 is Context, IERC777, IERC20 {
* *
* Emits {Sent}, {IERC20-Transfer} and {IERC20-Approval} events. * Emits {Sent}, {IERC20-Transfer} and {IERC20-Approval} events.
*/ */
function transferFrom(address holder, address recipient, uint256 amount) public override returns (bool) { function transferFrom(address holder, address recipient, uint256 amount) public virtual override returns (bool) {
require(recipient != address(0), "ERC777: transfer to the zero address"); require(recipient != address(0), "ERC777: transfer to the zero address");
require(holder != address(0), "ERC777: transfer from the zero address"); require(holder != address(0), "ERC777: transfer from the zero address");
...@@ -318,7 +317,8 @@ contract ERC777 is Context, IERC777, IERC20 { ...@@ -318,7 +317,8 @@ contract ERC777 is Context, IERC777, IERC20 {
bytes memory userData, bytes memory userData,
bytes memory operatorData bytes memory operatorData
) )
internal virtual internal
virtual
{ {
require(account != address(0), "ERC777: mint to the zero address"); require(account != address(0), "ERC777: mint to the zero address");
...@@ -354,6 +354,7 @@ contract ERC777 is Context, IERC777, IERC20 { ...@@ -354,6 +354,7 @@ contract ERC777 is Context, IERC777, IERC20 {
bool requireReceptionAck bool requireReceptionAck
) )
internal internal
virtual
{ {
require(from != address(0), "ERC777: send from the zero address"); require(from != address(0), "ERC777: send from the zero address");
require(to != address(0), "ERC777: send to the zero address"); require(to != address(0), "ERC777: send to the zero address");
...@@ -380,16 +381,17 @@ contract ERC777 is Context, IERC777, IERC20 { ...@@ -380,16 +381,17 @@ contract ERC777 is Context, IERC777, IERC20 {
bytes memory data, bytes memory data,
bytes memory operatorData bytes memory operatorData
) )
internal virtual internal
virtual
{ {
require(from != address(0), "ERC777: burn from the zero address"); require(from != address(0), "ERC777: burn from the zero address");
address operator = _msgSender(); address operator = _msgSender();
_beforeTokenTransfer(operator, from, address(0), amount);
_callTokensToSend(operator, from, address(0), amount, data, operatorData); _callTokensToSend(operator, from, address(0), amount, data, operatorData);
_beforeTokenTransfer(operator, from, address(0), amount);
// Update state variables // Update state variables
_balances[from] = _balances[from].sub(amount, "ERC777: burn amount exceeds balance"); _balances[from] = _balances[from].sub(amount, "ERC777: burn amount exceeds balance");
_totalSupply = _totalSupply.sub(amount); _totalSupply = _totalSupply.sub(amount);
......
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
/*
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with GSN meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address payable) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes memory) {
this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
return msg.data;
}
}
...@@ -54,6 +54,6 @@ library Create2 { ...@@ -54,6 +54,6 @@ library Create2 {
bytes32 _data = keccak256( bytes32 _data = keccak256(
abi.encodePacked(bytes1(0xff), deployer, salt, bytecodeHash) abi.encodePacked(bytes1(0xff), deployer, salt, bytecodeHash)
); );
return address(uint256(_data)); return address(uint160(uint256(_data)));
} }
} }
...@@ -144,6 +144,16 @@ library EnumerableMap { ...@@ -144,6 +144,16 @@ library EnumerableMap {
} }
/** /**
* @dev Tries to returns the value associated with `key`. O(1).
* Does not revert if `key` is not in the map.
*/
function _tryGet(Map storage map, bytes32 key) private view returns (bool, bytes32) {
uint256 keyIndex = map._indexes[key];
if (keyIndex == 0) return (false, 0); // Equivalent to contains(map, key)
return (true, map._entries[keyIndex - 1]._value); // All indexes are 1-based
}
/**
* @dev Returns the value associated with `key`. O(1). * @dev Returns the value associated with `key`. O(1).
* *
* Requirements: * Requirements:
...@@ -151,11 +161,16 @@ library EnumerableMap { ...@@ -151,11 +161,16 @@ library EnumerableMap {
* - `key` must be in the map. * - `key` must be in the map.
*/ */
function _get(Map storage map, bytes32 key) private view returns (bytes32) { function _get(Map storage map, bytes32 key) private view returns (bytes32) {
return _get(map, key, "EnumerableMap: nonexistent key"); uint256 keyIndex = map._indexes[key];
require(keyIndex != 0, "EnumerableMap: nonexistent key"); // Equivalent to contains(map, key)
return map._entries[keyIndex - 1]._value; // All indexes are 1-based
} }
/** /**
* @dev Same as {_get}, with a custom error message when `key` is not in the map. * @dev Same as {_get}, with a custom error message when `key` is not in the map.
*
* CAUTION: This function is deprecated because it requires allocating memory for the error
* message unnecessarily. For custom revert reasons use {_tryGet}.
*/ */
function _get(Map storage map, bytes32 key, string memory errorMessage) private view returns (bytes32) { function _get(Map storage map, bytes32 key, string memory errorMessage) private view returns (bytes32) {
uint256 keyIndex = map._indexes[key]; uint256 keyIndex = map._indexes[key];
...@@ -177,7 +192,7 @@ library EnumerableMap { ...@@ -177,7 +192,7 @@ library EnumerableMap {
* already present. * already present.
*/ */
function set(UintToAddressMap storage map, uint256 key, address value) internal returns (bool) { function set(UintToAddressMap storage map, uint256 key, address value) internal returns (bool) {
return _set(map._inner, bytes32(key), bytes32(uint256(value))); return _set(map._inner, bytes32(key), bytes32(uint256(uint160(value))));
} }
/** /**
...@@ -214,7 +229,16 @@ library EnumerableMap { ...@@ -214,7 +229,16 @@ library EnumerableMap {
*/ */
function at(UintToAddressMap storage map, uint256 index) internal view returns (uint256, address) { function at(UintToAddressMap storage map, uint256 index) internal view returns (uint256, address) {
(bytes32 key, bytes32 value) = _at(map._inner, index); (bytes32 key, bytes32 value) = _at(map._inner, index);
return (uint256(key), address(uint256(value))); return (uint256(key), address(uint160(uint256(value))));
}
/**
* @dev Tries to returns the value associated with `key`. O(1).
* Does not revert if `key` is not in the map.
*/
function tryGet(UintToAddressMap storage map, uint256 key) internal view returns (bool, address) {
(bool success, bytes32 value) = _tryGet(map._inner, bytes32(key));
return (success, address(uint160(uint256(value))));
} }
/** /**
...@@ -225,13 +249,16 @@ library EnumerableMap { ...@@ -225,13 +249,16 @@ library EnumerableMap {
* - `key` must be in the map. * - `key` must be in the map.
*/ */
function get(UintToAddressMap storage map, uint256 key) internal view returns (address) { function get(UintToAddressMap storage map, uint256 key) internal view returns (address) {
return address(uint256(_get(map._inner, bytes32(key)))); return address(uint160(uint256(_get(map._inner, bytes32(key)))));
} }
/** /**
* @dev Same as {get}, with a custom error message when `key` is not in the map. * @dev Same as {get}, with a custom error message when `key` is not in the map.
*
* CAUTION: This function is deprecated because it requires allocating memory for the error
* message unnecessarily. For custom revert reasons use {tryGet}.
*/ */
function get(UintToAddressMap storage map, uint256 key, string memory errorMessage) internal view returns (address) { function get(UintToAddressMap storage map, uint256 key, string memory errorMessage) internal view returns (address) {
return address(uint256(_get(map._inner, bytes32(key), errorMessage))); return address(uint160(uint256(_get(map._inner, bytes32(key), errorMessage))));
} }
} }
...@@ -199,7 +199,7 @@ library EnumerableSet { ...@@ -199,7 +199,7 @@ library EnumerableSet {
* already present. * already present.
*/ */
function add(AddressSet storage set, address value) internal returns (bool) { function add(AddressSet storage set, address value) internal returns (bool) {
return _add(set._inner, bytes32(uint256(value))); return _add(set._inner, bytes32(uint256(uint160(value))));
} }
/** /**
...@@ -209,14 +209,14 @@ library EnumerableSet { ...@@ -209,14 +209,14 @@ library EnumerableSet {
* present. * present.
*/ */
function remove(AddressSet storage set, address value) internal returns (bool) { function remove(AddressSet storage set, address value) internal returns (bool) {
return _remove(set._inner, bytes32(uint256(value))); return _remove(set._inner, bytes32(uint256(uint160(value))));
} }
/** /**
* @dev Returns true if the value is in the set. O(1). * @dev Returns true if the value is in the set. O(1).
*/ */
function contains(AddressSet storage set, address value) internal view returns (bool) { function contains(AddressSet storage set, address value) internal view returns (bool) {
return _contains(set._inner, bytes32(uint256(value))); return _contains(set._inner, bytes32(uint256(uint160(value))));
} }
/** /**
...@@ -237,7 +237,7 @@ library EnumerableSet { ...@@ -237,7 +237,7 @@ library EnumerableSet {
* - `index` must be strictly less than {length}. * - `index` must be strictly less than {length}.
*/ */
function at(AddressSet storage set, uint256 index) internal view returns (address) { function at(AddressSet storage set, uint256 index) internal view returns (address) {
return address(uint256(_at(set._inner, index))); return address(uint160(uint256(_at(set._inner, index))));
} }
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
pragma solidity ^0.7.0; pragma solidity ^0.7.0;
import "../GSN/Context.sol"; import "./Context.sol";
/** /**
* @dev Contract module which allows children to implement an emergency stop * @dev Contract module which allows children to implement an emergency stop
...@@ -36,7 +36,7 @@ abstract contract Pausable is Context { ...@@ -36,7 +36,7 @@ abstract contract Pausable is Context {
/** /**
* @dev Returns true if the contract is paused, and false otherwise. * @dev Returns true if the contract is paused, and false otherwise.
*/ */
function paused() public view returns (bool) { function paused() public view virtual returns (bool) {
return _paused; return _paused;
} }
...@@ -48,7 +48,7 @@ abstract contract Pausable is Context { ...@@ -48,7 +48,7 @@ abstract contract Pausable is Context {
* - The contract must not be paused. * - The contract must not be paused.
*/ */
modifier whenNotPaused() { modifier whenNotPaused() {
require(!_paused, "Pausable: paused"); require(!paused(), "Pausable: paused");
_; _;
} }
...@@ -60,7 +60,7 @@ abstract contract Pausable is Context { ...@@ -60,7 +60,7 @@ abstract contract Pausable is Context {
* - The contract must be paused. * - The contract must be paused.
*/ */
modifier whenPaused() { modifier whenPaused() {
require(_paused, "Pausable: not paused"); require(paused(), "Pausable: not paused");
_; _;
} }
......
...@@ -26,7 +26,7 @@ library Strings { ...@@ -26,7 +26,7 @@ library Strings {
uint256 index = digits - 1; uint256 index = digits - 1;
temp = value; temp = value;
while (temp != 0) { while (temp != 0) {
buffer[index--] = byte(uint8(48 + temp % 10)); buffer[index--] = bytes1(uint8(48 + temp % 10));
temp /= 10; temp /= 10;
} }
return string(buffer); return string(buffer);
......
{{~#*inline "typed-variable-array"~}} {{~#*inline "typed-variable-array"~}}
{{#each .}}++[.var-type]#++{{typeName}}++#++{{#if name}} ++[.var-name]#++{{name}}++#++{{/if}}{{#unless @last}}, {{/unless}}{{/each}} {{#each .}}{{typeName}}{{#if name}} {{name}}{{/if}}{{#unless @last}}, {{/unless}}{{/each}}
{{~/inline~}} {{~/inline~}}
{{#each linkable}} {{#each linkable}}
...@@ -12,20 +12,13 @@ ...@@ -12,20 +12,13 @@
{{natspec.devdoc}} {{natspec.devdoc}}
{{#if modifiers}} {{#if ownModifiers}}
[.contract-index] [.contract-index]
.Modifiers .Modifiers
-- --
{{#each inheritedItems}} {{#each ownModifiers}}
{{#unless @first}}
[.contract-subindex-inherited]
.{{contract.name}}
{{/unless}}
{{#each modifiers}}
* {xref-{{slug anchor~}} }[`++{{name}}({{args.names}})++`] * {xref-{{slug anchor~}} }[`++{{name}}({{args.names}})++`]
{{/each}} {{/each}}
{{/each}}
-- --
{{/if}} {{/if}}
...@@ -34,6 +27,7 @@ ...@@ -34,6 +27,7 @@
.Functions .Functions
-- --
{{#each inheritedItems}} {{#each inheritedItems}}
{{#if (or @first (ne contract.name "Context"))}}
{{#unless @first}} {{#unless @first}}
[.contract-subindex-inherited] [.contract-subindex-inherited]
.{{contract.name}} .{{contract.name}}
...@@ -42,6 +36,7 @@ ...@@ -42,6 +36,7 @@
* {xref-{{slug anchor~}} }[`++{{name}}({{args.names}})++`] * {xref-{{slug anchor~}} }[`++{{name}}({{args.names}})++`]
{{/each}} {{/each}}
{{/if}}
{{/each}} {{/each}}
-- --
{{/if}} {{/if}}
...@@ -66,7 +61,7 @@ ...@@ -66,7 +61,7 @@
{{#each ownModifiers}} {{#each ownModifiers}}
[.contract-item] [.contract-item]
[[{{anchor}}]] [[{{anchor}}]]
==== `++{{name}}({{> typed-variable-array args}})++` [.item-kind]#modifier# ==== `[.contract-item-name]#++{{name}}++#++({{> typed-variable-array args}})++` [.item-kind]#modifier#
{{natspec.devdoc}} {{natspec.devdoc}}
...@@ -75,7 +70,7 @@ ...@@ -75,7 +70,7 @@
{{#each ownFunctions}} {{#each ownFunctions}}
[.contract-item] [.contract-item]
[[{{anchor}}]] [[{{anchor}}]]
==== `++{{name}}({{> typed-variable-array args}}){{#if outputs}}{{> typed-variable-array outputs}}{{/if}}++` [.item-kind]#{{visibility}}# ==== `[.contract-item-name]#++{{name}}++#++({{> typed-variable-array args}}){{#if outputs}}{{> typed-variable-array outputs}}{{/if}}++` [.item-kind]#{{visibility}}#
{{natspec.devdoc}} {{natspec.devdoc}}
...@@ -84,7 +79,7 @@ ...@@ -84,7 +79,7 @@
{{#each ownEvents}} {{#each ownEvents}}
[.contract-item] [.contract-item]
[[{{anchor}}]] [[{{anchor}}]]
==== `++{{name}}({{> typed-variable-array args}})++` [.item-kind]#event# ==== `[.contract-item-name]#++{{name}}++#++({{> typed-variable-array args}})++` [.item-kind]#event#
{{natspec.devdoc}} {{natspec.devdoc}}
......
This source diff could not be displayed because it is too large. You can view the blob instead.
{ {
"name": "openzeppelin-solidity", "name": "openzeppelin-solidity",
"description": "Secure Smart Contract library for Solidity", "description": "Secure Smart Contract library for Solidity",
"version": "3.2.2-solc-0.7", "version": "3.3.0-solc-0.7",
"files": [ "files": [
"/contracts/**/*.sol", "/contracts/**/*.sol",
"/build/contracts/*.json", "/build/contracts/*.json",
...@@ -60,7 +60,9 @@ ...@@ -60,7 +60,9 @@
"eslint-plugin-node": "^10.0.0", "eslint-plugin-node": "^10.0.0",
"eslint-plugin-promise": "^4.2.1", "eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.1", "eslint-plugin-standard": "^4.0.1",
"eth-sig-util": "^3.0.0",
"ethereumjs-util": "^7.0.7", "ethereumjs-util": "^7.0.7",
"ethereumjs-wallet": "^1.0.1",
"lodash.startcase": "^4.4.0", "lodash.startcase": "^4.4.0",
"lodash.zip": "^4.2.0", "lodash.zip": "^4.2.0",
"micromatch": "^4.0.2", "micromatch": "^4.0.2",
...@@ -68,7 +70,8 @@ ...@@ -68,7 +70,8 @@
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"solhint": "^3.2.0", "solhint": "^3.2.0",
"solidity-coverage": "^0.7.11", "solidity-coverage": "^0.7.11",
"solidity-docgen": "^0.5.3" "solidity-docgen": "^0.5.3",
"web3": "^1.3.0"
}, },
"dependencies": {} "dependencies": {}
} }
...@@ -15,6 +15,6 @@ solidity-docgen \ ...@@ -15,6 +15,6 @@ solidity-docgen \
-o "$OUTDIR" \ -o "$OUTDIR" \
-e contracts/mocks,contracts/examples \ -e contracts/mocks,contracts/examples \
--output-structure readmes \ --output-structure readmes \
--solc-module scripts/prepare-docs-solc.js --solc-module ./scripts/prepare-docs-solc.js
node scripts/gen-nav.js "$OUTDIR" > "$OUTDIR/../nav.adoc" node scripts/gen-nav.js "$OUTDIR" > "$OUTDIR/../nav.adoc"
...@@ -16,8 +16,10 @@ current_version() { ...@@ -16,8 +16,10 @@ current_version() {
} }
current_release_branch() { current_release_branch() {
v="$(current_version)" v="$(current_version)" # 3.3.0-rc.0
echo "release-${v%.*-"$PRERELEASE_SUFFIX".*}" vf="${v%-"$PRERELEASE_SUFFIX".*}" # 3.3.0
r="${vf%.*}" # 3.3
echo "release-$r"
} }
assert_current_branch() { assert_current_branch() {
......
const ethSigUtil = require('eth-sig-util');
const Wallet = require('ethereumjs-wallet').default;
const { EIP712Domain, domainSeparator } = require('../helpers/eip712');
const EIP712 = artifacts.require('EIP712External');
contract('EIP712', function (accounts) {
const [mailTo] = accounts;
const name = 'A Name';
const version = '1';
beforeEach('deploying', async function () {
this.eip712 = await EIP712.new(name, version);
// We get the chain id from the contract because Ganache (used for coverage) does not return the same chain id
// from within the EVM as from the JSON RPC interface.
// See https://github.com/trufflesuite/ganache-core/issues/515
this.chainId = await this.eip712.getChainId();
});
it('domain separator', async function () {
expect(
await this.eip712.domainSeparator(),
).to.equal(
await domainSeparator(name, version, this.chainId, this.eip712.address),
);
});
it('digest', async function () {
const chainId = this.chainId;
const verifyingContract = this.eip712.address;
const message = {
to: mailTo,
contents: 'very interesting',
};
const data = {
types: {
EIP712Domain,
Mail: [
{ name: 'to', type: 'address' },
{ name: 'contents', type: 'string' },
],
},
domain: { name, version, chainId, verifyingContract },
primaryType: 'Mail',
message,
};
const wallet = Wallet.generate();
const signature = ethSigUtil.signTypedMessage(wallet.getPrivateKey(), { data });
await this.eip712.verify(signature, wallet.getAddressString(), message.to, message.contents);
});
});
/* eslint-disable */
const { BN, constants, expectEvent, expectRevert, time } = require('@openzeppelin/test-helpers');
const { expect } = require('chai');
const { MAX_UINT256, ZERO_ADDRESS, ZERO_BYTES32 } = constants;
const { fromRpcSig } = require('ethereumjs-util');
const ethSigUtil = require('eth-sig-util');
const Wallet = require('ethereumjs-wallet').default;
const ERC20PermitMock = artifacts.require('ERC20PermitMock');
const { EIP712Domain, domainSeparator } = require('../helpers/eip712');
const Permit = [
{ name: 'owner', type: 'address' },
{ name: 'spender', type: 'address' },
{ name: 'value', type: 'uint256' },
{ name: 'nonce', type: 'uint256' },
{ name: 'deadline', type: 'uint256' },
];
contract('ERC20Permit', function (accounts) {
const [ initialHolder, spender, recipient, other ] = accounts;
const name = 'My Token';
const symbol = 'MTKN';
const version = '1';
const initialSupply = new BN(100);
beforeEach(async function () {
this.token = await ERC20PermitMock.new(name, symbol, initialHolder, initialSupply);
// We get the chain id from the contract because Ganache (used for coverage) does not return the same chain id
// from within the EVM as from the JSON RPC interface.
// See https://github.com/trufflesuite/ganache-core/issues/515
this.chainId = await this.token.getChainId();
});
it('initial nonce is 0', async function () {
expect(await this.token.nonces(initialHolder)).to.be.bignumber.equal('0');
});
it('domain separator', async function () {
expect(
await this.token.DOMAIN_SEPARATOR(),
).to.equal(
await domainSeparator(name, version, this.chainId, this.token.address),
);
});
describe('permit', function () {
const wallet = Wallet.generate();
const owner = wallet.getAddressString();
const value = new BN(42);
const nonce = 0;
const maxDeadline = MAX_UINT256;
const buildData = (chainId, verifyingContract, deadline = maxDeadline) => ({
primaryType: 'Permit',
types: { EIP712Domain, Permit },
domain: { name, version, chainId, verifyingContract },
message: { owner, spender, value, nonce, deadline },
});
it('accepts owner signature', async function () {
const data = buildData(this.chainId, this.token.address);
const signature = ethSigUtil.signTypedMessage(wallet.getPrivateKey(), { data });
const { v, r, s } = fromRpcSig(signature);
const receipt = await this.token.permit(owner, spender, value, maxDeadline, v, r, s);
expect(await this.token.nonces(owner)).to.be.bignumber.equal('1');
expect(await this.token.allowance(owner, spender)).to.be.bignumber.equal(value);
});
it('rejects reused signature', async function () {
const data = buildData(this.chainId, this.token.address);
const signature = ethSigUtil.signTypedMessage(wallet.getPrivateKey(), { data });
const { v, r, s } = fromRpcSig(signature);
await this.token.permit(owner, spender, value, maxDeadline, v, r, s);
await expectRevert(
this.token.permit(owner, spender, value, maxDeadline, v, r, s),
'ERC20Permit: invalid signature',
);
});
it('rejects other signature', async function () {
const otherWallet = Wallet.generate();
const data = buildData(this.chainId, this.token.address);
const signature = ethSigUtil.signTypedMessage(otherWallet.getPrivateKey(), { data });
const { v, r, s } = fromRpcSig(signature);
await expectRevert(
this.token.permit(owner, spender, value, maxDeadline, v, r, s),
'ERC20Permit: invalid signature',
);
});
it('rejects expired permit', async function () {
const deadline = (await time.latest()) - time.duration.weeks(1);
const data = buildData(this.chainId, this.token.address, deadline);
const signature = ethSigUtil.signTypedMessage(wallet.getPrivateKey(), { data });
const { v, r, s } = fromRpcSig(signature);
await expectRevert(
this.token.permit(owner, spender, value, deadline, v, r, s),
'ERC20Permit: expired deadline',
);
});
});
});
const ethSigUtil = require('eth-sig-util');
const EIP712Domain = [
{ name: 'name', type: 'string' },
{ name: 'version', type: 'string' },
{ name: 'chainId', type: 'uint256' },
{ name: 'verifyingContract', type: 'address' },
];
async function domainSeparator (name, version, chainId, verifyingContract) {
return '0x' + ethSigUtil.TypedDataUtils.hashStruct(
'EIP712Domain',
{ name, version, chainId, verifyingContract },
{ EIP712Domain },
).toString('hex');
}
module.exports = {
EIP712Domain,
domainSeparator,
};
...@@ -37,6 +37,12 @@ contract('ERC165Checker', function (accounts) { ...@@ -37,6 +37,12 @@ contract('ERC165Checker', function (accounts) {
const supported = await this.mock.supportsAllInterfaces(this.target.address, [DUMMY_ID]); const supported = await this.mock.supportsAllInterfaces(this.target.address, [DUMMY_ID]);
expect(supported).to.equal(false); expect(supported).to.equal(false);
}); });
it('does not support mock interface via getSupportedInterfaces', async function () {
const supported = await this.mock.getSupportedInterfaces(this.target.address, [DUMMY_ID]);
expect(supported.length).to.equal(1);
expect(supported[0]).to.equal(false);
});
}); });
context('ERC165 supported', function () { context('ERC165 supported', function () {
...@@ -58,6 +64,12 @@ contract('ERC165Checker', function (accounts) { ...@@ -58,6 +64,12 @@ contract('ERC165Checker', function (accounts) {
const supported = await this.mock.supportsAllInterfaces(this.target.address, [DUMMY_ID]); const supported = await this.mock.supportsAllInterfaces(this.target.address, [DUMMY_ID]);
expect(supported).to.equal(false); expect(supported).to.equal(false);
}); });
it('does not support mock interface via getSupportedInterfaces', async function () {
const supported = await this.mock.getSupportedInterfaces(this.target.address, [DUMMY_ID]);
expect(supported.length).to.equal(1);
expect(supported[0]).to.equal(false);
});
}); });
context('ERC165 and single interface supported', function () { context('ERC165 and single interface supported', function () {
...@@ -79,6 +91,12 @@ contract('ERC165Checker', function (accounts) { ...@@ -79,6 +91,12 @@ contract('ERC165Checker', function (accounts) {
const supported = await this.mock.supportsAllInterfaces(this.target.address, [DUMMY_ID]); const supported = await this.mock.supportsAllInterfaces(this.target.address, [DUMMY_ID]);
expect(supported).to.equal(true); expect(supported).to.equal(true);
}); });
it('supports mock interface via getSupportedInterfaces', async function () {
const supported = await this.mock.getSupportedInterfaces(this.target.address, [DUMMY_ID]);
expect(supported.length).to.equal(1);
expect(supported[0]).to.equal(true);
});
}); });
context('ERC165 and many interfaces supported', function () { context('ERC165 and many interfaces supported', function () {
...@@ -117,6 +135,34 @@ contract('ERC165Checker', function (accounts) { ...@@ -117,6 +135,34 @@ contract('ERC165Checker', function (accounts) {
const supported = await this.mock.supportsAllInterfaces(this.target.address, interfaceIdsToTest); const supported = await this.mock.supportsAllInterfaces(this.target.address, interfaceIdsToTest);
expect(supported).to.equal(false); expect(supported).to.equal(false);
}); });
it('supports all interfaceIds via getSupportedInterfaces', async function () {
const supported = await this.mock.getSupportedInterfaces(this.target.address, this.supportedInterfaces);
expect(supported.length).to.equal(3);
expect(supported[0]).to.equal(true);
expect(supported[1]).to.equal(true);
expect(supported[2]).to.equal(true);
});
it('supports none of the interfaces queried via getSupportedInterfaces', async function () {
const interfaceIdsToTest = [DUMMY_UNSUPPORTED_ID, DUMMY_UNSUPPORTED_ID_2];
const supported = await this.mock.getSupportedInterfaces(this.target.address, interfaceIdsToTest);
expect(supported.length).to.equal(2);
expect(supported[0]).to.equal(false);
expect(supported[1]).to.equal(false);
});
it('supports not all of the interfaces queried via getSupportedInterfaces', async function () {
const interfaceIdsToTest = [...this.supportedInterfaces, DUMMY_UNSUPPORTED_ID];
const supported = await this.mock.getSupportedInterfaces(this.target.address, interfaceIdsToTest);
expect(supported.length).to.equal(4);
expect(supported[0]).to.equal(true);
expect(supported[1]).to.equal(true);
expect(supported[2]).to.equal(true);
expect(supported[3]).to.equal(false);
});
}); });
context('account address does not support ERC165', function () { context('account address does not support ERC165', function () {
...@@ -134,5 +180,11 @@ contract('ERC165Checker', function (accounts) { ...@@ -134,5 +180,11 @@ contract('ERC165Checker', function (accounts) {
const supported = await this.mock.supportsAllInterfaces(DUMMY_ACCOUNT, [DUMMY_ID]); const supported = await this.mock.supportsAllInterfaces(DUMMY_ACCOUNT, [DUMMY_ID]);
expect(supported).to.equal(false); expect(supported).to.equal(false);
}); });
it('does not support mock interface via getSupportedInterfaces', async function () {
const supported = await this.mock.getSupportedInterfaces(DUMMY_ACCOUNT, [DUMMY_ID]);
expect(supported.length).to.equal(1);
expect(supported[0]).to.equal(false);
});
}); });
}); });
...@@ -61,7 +61,7 @@ function shouldSupportInterfaces (interfaces = []) { ...@@ -61,7 +61,7 @@ function shouldSupportInterfaces (interfaces = []) {
const interfaceId = INTERFACE_IDS[k]; const interfaceId = INTERFACE_IDS[k];
describe(k, function () { describe(k, function () {
describe('ERC165\'s supportsInterface(bytes4)', function () { describe('ERC165\'s supportsInterface(bytes4)', function () {
it('uses less than 30k gas', async function () { it('uses less than 30k gas [skip-on-coverage]', async function () {
expect(await this.contractUnderTest.supportsInterface.estimateGas(interfaceId)).to.be.lte(30000); expect(await this.contractUnderTest.supportsInterface.estimateGas(interfaceId)).to.be.lte(30000);
}); });
......
...@@ -5,142 +5,394 @@ const { expect } = require('chai'); ...@@ -5,142 +5,394 @@ const { expect } = require('chai');
const SafeMathMock = artifacts.require('SafeMathMock'); const SafeMathMock = artifacts.require('SafeMathMock');
function expectStruct (value, expected) {
for (const key in expected) {
if (BN.isBN(value[key])) {
expect(value[key]).to.be.bignumber.equal(expected[key]);
} else {
expect(value[key]).to.be.equal(expected[key]);
}
}
}
contract('SafeMath', function (accounts) { contract('SafeMath', function (accounts) {
beforeEach(async function () { beforeEach(async function () {
this.safeMath = await SafeMathMock.new(); this.safeMath = await SafeMathMock.new();
}); });
async function testCommutative (fn, lhs, rhs, expected) { async function testCommutative (fn, lhs, rhs, expected, ...extra) {
expect(await fn(lhs, rhs)).to.be.bignumber.equal(expected); expect(await fn(lhs, rhs, ...extra)).to.be.bignumber.equal(expected);
expect(await fn(rhs, lhs)).to.be.bignumber.equal(expected); expect(await fn(rhs, lhs, ...extra)).to.be.bignumber.equal(expected);
} }
async function testFailsCommutative (fn, lhs, rhs, reason) { async function testFailsCommutative (fn, lhs, rhs, reason, ...extra) {
await expectRevert(fn(lhs, rhs), reason); await expectRevert(fn(lhs, rhs, ...extra), reason);
await expectRevert(fn(rhs, lhs), reason); await expectRevert(fn(rhs, lhs, ...extra), reason);
} }
describe('add', function () { async function testCommutativeIterable (fn, lhs, rhs, expected, ...extra) {
it('adds correctly', async function () { expectStruct(await fn(lhs, rhs, ...extra), expected);
const a = new BN('5678'); expectStruct(await fn(rhs, lhs, ...extra), expected);
const b = new BN('1234'); }
await testCommutative(this.safeMath.add, a, b, a.add(b)); describe('with flag', function () {
}); describe('add', function () {
it('adds correctly', async function () {
const a = new BN('5678');
const b = new BN('1234');
it('reverts on addition overflow', async function () { testCommutativeIterable(this.safeMath.tryAdd, a, b, { flag: true, value: a.add(b) });
const a = MAX_UINT256; });
const b = new BN('1');
it('reverts on addition overflow', async function () {
const a = MAX_UINT256;
const b = new BN('1');
await testFailsCommutative(this.safeMath.add, a, b, 'SafeMath: addition overflow'); testCommutativeIterable(this.safeMath.tryAdd, a, b, { flag: false, value: '0' });
});
}); });
});
describe('sub', function () { describe('sub', function () {
it('subtracts correctly', async function () { it('subtracts correctly', async function () {
const a = new BN('5678'); const a = new BN('5678');
const b = new BN('1234'); const b = new BN('1234');
expect(await this.safeMath.sub(a, b)).to.be.bignumber.equal(a.sub(b)); expectStruct(await this.safeMath.trySub(a, b), { flag: true, value: a.sub(b) });
}); });
it('reverts if subtraction result would be negative', async function () { it('reverts if subtraction result would be negative', async function () {
const a = new BN('1234'); const a = new BN('1234');
const b = new BN('5678'); const b = new BN('5678');
await expectRevert(this.safeMath.sub(a, b), 'SafeMath: subtraction overflow'); expectStruct(await this.safeMath.trySub(a, b), { flag: false, value: '0' });
});
}); });
});
describe('mul', function () { describe('mul', function () {
it('multiplies correctly', async function () { it('multiplies correctly', async function () {
const a = new BN('1234'); const a = new BN('1234');
const b = new BN('5678'); const b = new BN('5678');
testCommutativeIterable(this.safeMath.tryMul, a, b, { flag: true, value: a.mul(b) });
});
it('multiplies by zero correctly', async function () {
const a = new BN('0');
const b = new BN('5678');
testCommutativeIterable(this.safeMath.tryMul, a, b, { flag: true, value: a.mul(b) });
});
await testCommutative(this.safeMath.mul, a, b, a.mul(b)); it('reverts on multiplication overflow', async function () {
const a = MAX_UINT256;
const b = new BN('2');
testCommutativeIterable(this.safeMath.tryMul, a, b, { flag: false, value: '0' });
});
}); });
it('multiplies by zero correctly', async function () { describe('div', function () {
const a = new BN('0'); it('divides correctly', async function () {
const b = new BN('5678'); const a = new BN('5678');
const b = new BN('5678');
await testCommutative(this.safeMath.mul, a, b, '0'); expectStruct(await this.safeMath.tryDiv(a, b), { flag: true, value: a.div(b) });
});
it('divides zero correctly', async function () {
const a = new BN('0');
const b = new BN('5678');
expectStruct(await this.safeMath.tryDiv(a, b), { flag: true, value: a.div(b) });
});
it('returns complete number result on non-even division', async function () {
const a = new BN('7000');
const b = new BN('5678');
expectStruct(await this.safeMath.tryDiv(a, b), { flag: true, value: a.div(b) });
});
it('reverts on division by zero', async function () {
const a = new BN('5678');
const b = new BN('0');
expectStruct(await this.safeMath.tryDiv(a, b), { flag: false, value: '0' });
});
}); });
it('reverts on multiplication overflow', async function () { describe('mod', function () {
const a = MAX_UINT256; describe('modulos correctly', async function () {
const b = new BN('2'); it('when the dividend is smaller than the divisor', async function () {
const a = new BN('284');
const b = new BN('5678');
expectStruct(await this.safeMath.tryMod(a, b), { flag: true, value: a.mod(b) });
});
it('when the dividend is equal to the divisor', async function () {
const a = new BN('5678');
const b = new BN('5678');
expectStruct(await this.safeMath.tryMod(a, b), { flag: true, value: a.mod(b) });
});
await testFailsCommutative(this.safeMath.mul, a, b, 'SafeMath: multiplication overflow'); it('when the dividend is larger than the divisor', async function () {
const a = new BN('7000');
const b = new BN('5678');
expectStruct(await this.safeMath.tryMod(a, b), { flag: true, value: a.mod(b) });
});
it('when the dividend is a multiple of the divisor', async function () {
const a = new BN('17034'); // 17034 == 5678 * 3
const b = new BN('5678');
expectStruct(await this.safeMath.tryMod(a, b), { flag: true, value: a.mod(b) });
});
});
it('reverts with a 0 divisor', async function () {
const a = new BN('5678');
const b = new BN('0');
expectStruct(await this.safeMath.tryMod(a, b), { flag: false, value: '0' });
});
}); });
}); });
describe('div', function () { describe('with default revert message', function () {
it('divides correctly', async function () { describe('add', function () {
const a = new BN('5678'); it('adds correctly', async function () {
const b = new BN('5678'); const a = new BN('5678');
const b = new BN('1234');
await testCommutative(this.safeMath.add, a, b, a.add(b));
});
it('reverts on addition overflow', async function () {
const a = MAX_UINT256;
const b = new BN('1');
await testFailsCommutative(this.safeMath.add, a, b, 'SafeMath: addition overflow');
});
});
describe('sub', function () {
it('subtracts correctly', async function () {
const a = new BN('5678');
const b = new BN('1234');
expect(await this.safeMath.sub(a, b)).to.be.bignumber.equal(a.sub(b));
});
it('reverts if subtraction result would be negative', async function () {
const a = new BN('1234');
const b = new BN('5678');
expect(await this.safeMath.div(a, b)).to.be.bignumber.equal(a.div(b)); await expectRevert(this.safeMath.sub(a, b), 'SafeMath: subtraction overflow');
});
}); });
it('divides zero correctly', async function () { describe('mul', function () {
const a = new BN('0'); it('multiplies correctly', async function () {
const b = new BN('5678'); const a = new BN('1234');
const b = new BN('5678');
await testCommutative(this.safeMath.mul, a, b, a.mul(b));
});
it('multiplies by zero correctly', async function () {
const a = new BN('0');
const b = new BN('5678');
await testCommutative(this.safeMath.mul, a, b, '0');
});
expect(await this.safeMath.div(a, b)).to.be.bignumber.equal('0'); it('reverts on multiplication overflow', async function () {
const a = MAX_UINT256;
const b = new BN('2');
await testFailsCommutative(this.safeMath.mul, a, b, 'SafeMath: multiplication overflow');
});
}); });
it('returns complete number result on non-even division', async function () { describe('div', function () {
const a = new BN('7000'); it('divides correctly', async function () {
const b = new BN('5678'); const a = new BN('5678');
const b = new BN('5678');
expect(await this.safeMath.div(a, b)).to.be.bignumber.equal(a.div(b));
});
it('divides zero correctly', async function () {
const a = new BN('0');
const b = new BN('5678');
expect(await this.safeMath.div(a, b)).to.be.bignumber.equal('0');
});
it('returns complete number result on non-even division', async function () {
const a = new BN('7000');
const b = new BN('5678');
expect(await this.safeMath.div(a, b)).to.be.bignumber.equal('1');
});
it('reverts on division by zero', async function () {
const a = new BN('5678');
const b = new BN('0');
expect(await this.safeMath.div(a, b)).to.be.bignumber.equal('1'); await expectRevert(this.safeMath.div(a, b), 'SafeMath: division by zero');
});
}); });
it('reverts on division by zero', async function () { describe('mod', function () {
const a = new BN('5678'); describe('modulos correctly', async function () {
const b = new BN('0'); it('when the dividend is smaller than the divisor', async function () {
const a = new BN('284');
const b = new BN('5678');
expect(await this.safeMath.mod(a, b)).to.be.bignumber.equal(a.mod(b));
});
it('when the dividend is equal to the divisor', async function () {
const a = new BN('5678');
const b = new BN('5678');
expect(await this.safeMath.mod(a, b)).to.be.bignumber.equal(a.mod(b));
});
await expectRevert(this.safeMath.div(a, b), 'SafeMath: division by zero'); it('when the dividend is larger than the divisor', async function () {
const a = new BN('7000');
const b = new BN('5678');
expect(await this.safeMath.mod(a, b)).to.be.bignumber.equal(a.mod(b));
});
it('when the dividend is a multiple of the divisor', async function () {
const a = new BN('17034'); // 17034 == 5678 * 3
const b = new BN('5678');
expect(await this.safeMath.mod(a, b)).to.be.bignumber.equal(a.mod(b));
});
});
it('reverts with a 0 divisor', async function () {
const a = new BN('5678');
const b = new BN('0');
await expectRevert(this.safeMath.mod(a, b), 'SafeMath: modulo by zero');
});
}); });
}); });
describe('mod', function () { describe('with custom revert message', function () {
describe('modulos correctly', async function () { describe('sub', function () {
it('when the dividend is smaller than the divisor', async function () { it('subtracts correctly', async function () {
const a = new BN('284'); const a = new BN('5678');
const b = new BN('1234');
expect(await this.safeMath.subWithMessage(a, b, 'MyErrorMessage')).to.be.bignumber.equal(a.sub(b));
});
it('reverts if subtraction result would be negative', async function () {
const a = new BN('1234');
const b = new BN('5678'); const b = new BN('5678');
expect(await this.safeMath.mod(a, b)).to.be.bignumber.equal(a.mod(b)); await expectRevert(this.safeMath.subWithMessage(a, b, 'MyErrorMessage'), 'MyErrorMessage');
}); });
});
it('when the dividend is equal to the divisor', async function () { describe('div', function () {
it('divides correctly', async function () {
const a = new BN('5678'); const a = new BN('5678');
const b = new BN('5678'); const b = new BN('5678');
expect(await this.safeMath.mod(a, b)).to.be.bignumber.equal(a.mod(b)); expect(await this.safeMath.divWithMessage(a, b, 'MyErrorMessage')).to.be.bignumber.equal(a.div(b));
}); });
it('when the dividend is larger than the divisor', async function () { it('divides zero correctly', async function () {
const a = new BN('7000'); const a = new BN('0');
const b = new BN('5678'); const b = new BN('5678');
expect(await this.safeMath.mod(a, b)).to.be.bignumber.equal(a.mod(b)); expect(await this.safeMath.divWithMessage(a, b, 'MyErrorMessage')).to.be.bignumber.equal('0');
}); });
it('when the dividend is a multiple of the divisor', async function () { it('returns complete number result on non-even division', async function () {
const a = new BN('17034'); // 17034 == 5678 * 3 const a = new BN('7000');
const b = new BN('5678'); const b = new BN('5678');
expect(await this.safeMath.mod(a, b)).to.be.bignumber.equal(a.mod(b)); expect(await this.safeMath.divWithMessage(a, b, 'MyErrorMessage')).to.be.bignumber.equal('1');
});
it('reverts on division by zero', async function () {
const a = new BN('5678');
const b = new BN('0');
await expectRevert(this.safeMath.divWithMessage(a, b, 'MyErrorMessage'), 'MyErrorMessage');
}); });
}); });
it('reverts with a 0 divisor', async function () { describe('mod', function () {
const a = new BN('5678'); describe('modulos correctly', async function () {
const b = new BN('0'); it('when the dividend is smaller than the divisor', async function () {
const a = new BN('284');
const b = new BN('5678');
expect(await this.safeMath.modWithMessage(a, b, 'MyErrorMessage')).to.be.bignumber.equal(a.mod(b));
});
it('when the dividend is equal to the divisor', async function () {
const a = new BN('5678');
const b = new BN('5678');
expect(await this.safeMath.modWithMessage(a, b, 'MyErrorMessage')).to.be.bignumber.equal(a.mod(b));
});
it('when the dividend is larger than the divisor', async function () {
const a = new BN('7000');
const b = new BN('5678');
expect(await this.safeMath.modWithMessage(a, b, 'MyErrorMessage')).to.be.bignumber.equal(a.mod(b));
});
it('when the dividend is a multiple of the divisor', async function () {
const a = new BN('17034'); // 17034 == 5678 * 3
const b = new BN('5678');
expect(await this.safeMath.modWithMessage(a, b, 'MyErrorMessage')).to.be.bignumber.equal(a.mod(b));
});
});
it('reverts with a 0 divisor', async function () {
const a = new BN('5678');
const b = new BN('0');
await expectRevert(this.safeMath.modWithMessage(a, b, 'MyErrorMessage'), 'MyErrorMessage');
});
});
});
describe('memory leakage', function () {
it('add', async function () {
expect(await this.safeMath.addMemoryCheck()).to.be.bignumber.equal('0');
});
it('sub', async function () {
expect(await this.safeMath.subMemoryCheck()).to.be.bignumber.equal('0');
});
it('mul', async function () {
expect(await this.safeMath.mulMemoryCheck()).to.be.bignumber.equal('0');
});
it('div', async function () {
expect(await this.safeMath.divMemoryCheck()).to.be.bignumber.equal('0');
});
await expectRevert(this.safeMath.mod(a, b), 'SafeMath: modulo by zero'); it('mod', async function () {
expect(await this.safeMath.modMemoryCheck()).to.be.bignumber.equal('0');
}); });
}); });
}); });
const { BN, constants, expectEvent } = require('@openzeppelin/test-helpers');
const { ZERO_ADDRESS } = constants;
const { expect } = require('chai');
const ERC20PresetFixedSupply = artifacts.require('ERC20PresetFixedSupply');
contract('ERC20PresetFixedSupply', function (accounts) {
const [deployer, owner] = accounts;
const name = 'PresetFixedSupply';
const symbol = 'PFS';
const initialSupply = new BN('50000');
const amount = new BN('10000');
before(async function () {
this.token = await ERC20PresetFixedSupply.new(name, symbol, initialSupply, owner, { from: deployer });
});
it('deployer has the balance equal to initial supply', async function () {
expect(await this.token.balanceOf(owner)).to.be.bignumber.equal(initialSupply);
});
it('total supply is equal to initial supply', async function () {
expect(await this.token.totalSupply()).to.be.bignumber.equal(initialSupply);
});
describe('burning', function () {
it('holders can burn their tokens', async function () {
const remainingBalance = initialSupply.sub(amount);
const receipt = await this.token.burn(amount, { from: owner });
expectEvent(receipt, 'Transfer', { from: owner, to: ZERO_ADDRESS, value: amount });
expect(await this.token.balanceOf(owner)).to.be.bignumber.equal(remainingBalance);
});
it('decrements totalSupply', async function () {
const expectedSupply = initialSupply.sub(amount);
expect(await this.token.totalSupply()).to.be.bignumber.equal(expectedSupply);
});
});
});
const { BN, singletons } = require('@openzeppelin/test-helpers');
const { expect } = require('chai');
const ERC777PresetFixedSupply = artifacts.require('ERC777PresetFixedSupply');
contract('ERC777PresetFixedSupply', function (accounts) {
const [registryFunder, owner, defaultOperatorA, defaultOperatorB, anyone] = accounts;
const initialSupply = new BN('10000');
const name = 'ERC777Preset';
const symbol = '777P';
const defaultOperators = [defaultOperatorA, defaultOperatorB];
before(async function () {
await singletons.ERC1820Registry(registryFunder);
});
beforeEach(async function () {
this.token = await ERC777PresetFixedSupply.new(name, symbol, defaultOperators, initialSupply, owner);
});
it('returns the name', async function () {
expect(await this.token.name()).to.equal(name);
});
it('returns the symbol', async function () {
expect(await this.token.symbol()).to.equal(symbol);
});
it('returns the default operators', async function () {
expect(await this.token.defaultOperators()).to.deep.equal(defaultOperators);
});
it('default operators are operators for all accounts', async function () {
for (const operator of defaultOperators) {
expect(await this.token.isOperatorFor(operator, anyone)).to.equal(true);
}
});
it('returns the total supply equal to initial supply', async function () {
expect(await this.token.totalSupply()).to.be.bignumber.equal(initialSupply);
});
it('returns the balance of owner equal to initial supply', async function () {
expect(await this.token.balanceOf(owner)).to.be.bignumber.equal(initialSupply);
});
});
const { BN, expectRevert } = require('@openzeppelin/test-helpers');
const ethereumjsUtil = require('ethereumjs-util');
const { keccak256 } = ethereumjsUtil;
const { expect } = require('chai');
const UpgradeableBeacon = artifacts.require('UpgradeableBeacon');
const BeaconProxy = artifacts.require('BeaconProxy');
const DummyImplementation = artifacts.require('DummyImplementation');
const DummyImplementationV2 = artifacts.require('DummyImplementationV2');
const BadBeaconNoImpl = artifacts.require('BadBeaconNoImpl');
const BadBeaconNotContract = artifacts.require('BadBeaconNotContract');
function toChecksumAddress (address) {
return ethereumjsUtil.toChecksumAddress('0x' + address.replace(/^0x/, '').padStart(40, '0'));
}
const BEACON_LABEL = 'eip1967.proxy.beacon';
const BEACON_SLOT = '0x' + new BN(keccak256(Buffer.from(BEACON_LABEL))).subn(1).toString(16);
contract('BeaconProxy', function (accounts) {
const [anotherAccount] = accounts;
describe('bad beacon is not accepted', async function () {
it('non-contract beacon', async function () {
await expectRevert(
BeaconProxy.new(anotherAccount, '0x'),
'BeaconProxy: beacon is not a contract',
);
});
it('non-compliant beacon', async function () {
const beacon = await BadBeaconNoImpl.new();
await expectRevert.unspecified(
BeaconProxy.new(beacon.address, '0x'),
);
});
it('non-contract implementation', async function () {
const beacon = await BadBeaconNotContract.new();
await expectRevert(
BeaconProxy.new(beacon.address, '0x'),
'BeaconProxy: beacon implementation is not a contract',
);
});
});
before('deploy implementation', async function () {
this.implementationV0 = await DummyImplementation.new();
this.implementationV1 = await DummyImplementationV2.new();
});
describe('initialization', function () {
before(function () {
this.assertInitialized = async ({ value, balance }) => {
const beaconAddress = toChecksumAddress(await web3.eth.getStorageAt(this.proxy.address, BEACON_SLOT));
expect(beaconAddress).to.equal(this.beacon.address);
const dummy = new DummyImplementation(this.proxy.address);
expect(await dummy.value()).to.bignumber.eq(value);
expect(await web3.eth.getBalance(this.proxy.address)).to.bignumber.eq(balance);
};
});
beforeEach('deploy beacon', async function () {
this.beacon = await UpgradeableBeacon.new(this.implementationV0.address);
});
it('no initialization', async function () {
const data = Buffer.from('');
const balance = '10';
this.proxy = await BeaconProxy.new(this.beacon.address, data, { value: balance });
await this.assertInitialized({ value: '0', balance });
});
it('non-payable initialization', async function () {
const value = '55';
const data = this.implementationV0.contract.methods
.initializeNonPayableWithValue(value)
.encodeABI();
this.proxy = await BeaconProxy.new(this.beacon.address, data);
await this.assertInitialized({ value, balance: '0' });
});
it('payable initialization', async function () {
const value = '55';
const data = this.implementationV0.contract.methods
.initializePayableWithValue(value)
.encodeABI();
const balance = '100';
this.proxy = await BeaconProxy.new(this.beacon.address, data, { value: balance });
await this.assertInitialized({ value, balance });
});
it('reverting initialization', async function () {
const data = this.implementationV0.contract.methods.reverts().encodeABI();
await expectRevert(
BeaconProxy.new(this.beacon.address, data),
'DummyImplementation reverted',
);
});
});
it('upgrade a proxy by upgrading its beacon', async function () {
const beacon = await UpgradeableBeacon.new(this.implementationV0.address);
const value = '10';
const data = this.implementationV0.contract.methods
.initializeNonPayableWithValue(value)
.encodeABI();
const proxy = await BeaconProxy.new(beacon.address, data);
const dummy = new DummyImplementation(proxy.address);
// test initial values
expect(await dummy.value()).to.bignumber.eq(value);
// test initial version
expect(await dummy.version()).to.eq('V1');
// upgrade beacon
await beacon.upgradeTo(this.implementationV1.address);
// test upgraded version
expect(await dummy.version()).to.eq('V2');
});
it('upgrade 2 proxies by upgrading shared beacon', async function () {
const value1 = '10';
const value2 = '42';
const beacon = await UpgradeableBeacon.new(this.implementationV0.address);
const proxy1InitializeData = this.implementationV0.contract.methods
.initializeNonPayableWithValue(value1)
.encodeABI();
const proxy1 = await BeaconProxy.new(beacon.address, proxy1InitializeData);
const proxy2InitializeData = this.implementationV0.contract.methods
.initializeNonPayableWithValue(value2)
.encodeABI();
const proxy2 = await BeaconProxy.new(beacon.address, proxy2InitializeData);
const dummy1 = new DummyImplementation(proxy1.address);
const dummy2 = new DummyImplementation(proxy2.address);
// test initial values
expect(await dummy1.value()).to.bignumber.eq(value1);
expect(await dummy2.value()).to.bignumber.eq(value2);
// test initial version
expect(await dummy1.version()).to.eq('V1');
expect(await dummy2.version()).to.eq('V1');
// upgrade beacon
await beacon.upgradeTo(this.implementationV1.address);
// test upgraded version
expect(await dummy1.version()).to.eq('V2');
expect(await dummy2.version()).to.eq('V2');
});
});
const { expectRevert } = require('@openzeppelin/test-helpers');
const { expect } = require('chai');
const DummyImplementation = artifacts.require('DummyImplementation');
module.exports = function shouldBehaveLikeClone (createClone) {
before('deploy implementation', async function () {
this.implementation = web3.utils.toChecksumAddress((await DummyImplementation.new()).address);
});
const assertProxyInitialization = function ({ value, balance }) {
it('initializes the proxy', async function () {
const dummy = new DummyImplementation(this.proxy);
expect(await dummy.value()).to.be.bignumber.equal(value.toString());
});
it('has expected balance', async function () {
expect(await web3.eth.getBalance(this.proxy)).to.be.bignumber.equal(balance.toString());
});
};
describe('initialization without parameters', function () {
describe('non payable', function () {
const expectedInitializedValue = 10;
const initializeData = new DummyImplementation('').contract.methods['initializeNonPayable()']().encodeABI();
describe('when not sending balance', function () {
beforeEach('creating proxy', async function () {
this.proxy = (
await createClone(this.implementation, initializeData)
).address;
});
assertProxyInitialization({
value: expectedInitializedValue,
balance: 0,
});
});
describe('when sending some balance', function () {
const value = 10e5;
it('reverts', async function () {
await expectRevert.unspecified(
createClone(this.implementation, initializeData, { value }),
);
});
});
});
describe('payable', function () {
const expectedInitializedValue = 100;
const initializeData = new DummyImplementation('').contract.methods['initializePayable()']().encodeABI();
describe('when not sending balance', function () {
beforeEach('creating proxy', async function () {
this.proxy = (
await createClone(this.implementation, initializeData)
).address;
});
assertProxyInitialization({
value: expectedInitializedValue,
balance: 0,
});
});
describe('when sending some balance', function () {
const value = 10e5;
beforeEach('creating proxy', async function () {
this.proxy = (
await createClone(this.implementation, initializeData, { value })
).address;
});
assertProxyInitialization({
value: expectedInitializedValue,
balance: value,
});
});
});
});
describe('initialization with parameters', function () {
describe('non payable', function () {
const expectedInitializedValue = 10;
const initializeData = new DummyImplementation('').contract
.methods.initializeNonPayableWithValue(expectedInitializedValue).encodeABI();
describe('when not sending balance', function () {
beforeEach('creating proxy', async function () {
this.proxy = (
await createClone(this.implementation, initializeData)
).address;
});
assertProxyInitialization({
value: expectedInitializedValue,
balance: 0,
});
});
describe('when sending some balance', function () {
const value = 10e5;
it('reverts', async function () {
await expectRevert.unspecified(
createClone(this.implementation, initializeData, { value }),
);
});
});
});
describe('payable', function () {
const expectedInitializedValue = 42;
const initializeData = new DummyImplementation('').contract
.methods.initializePayableWithValue(expectedInitializedValue).encodeABI();
describe('when not sending balance', function () {
beforeEach('creating proxy', async function () {
this.proxy = (
await createClone(this.implementation, initializeData)
).address;
});
assertProxyInitialization({
value: expectedInitializedValue,
balance: 0,
});
});
describe('when sending some balance', function () {
const value = 10e5;
beforeEach('creating proxy', async function () {
this.proxy = (
await createClone(this.implementation, initializeData, { value })
).address;
});
assertProxyInitialization({
value: expectedInitializedValue,
balance: value,
});
});
});
});
};
const { expectEvent, expectRevert } = require('@openzeppelin/test-helpers');
const shouldBehaveLikeClone = require('./Clones.behaviour');
const ClonesMock = artifacts.require('ClonesMock');
contract('Clones', function (accounts) {
describe('clone', function () {
shouldBehaveLikeClone(async (implementation, initData, opts = {}) => {
const factory = await ClonesMock.new();
const receipt = await factory.clone(implementation, initData, { value: opts.value });
const address = receipt.logs.find(({ event }) => event === 'NewInstance').args.instance;
return { address };
});
});
describe('cloneDeterministic', function () {
shouldBehaveLikeClone(async (implementation, initData, opts = {}) => {
const salt = web3.utils.randomHex(32);
const factory = await ClonesMock.new();
const receipt = await factory.cloneDeterministic(implementation, salt, initData, { value: opts.value });
const address = receipt.logs.find(({ event }) => event === 'NewInstance').args.instance;
return { address };
});
it('address already used', async function () {
const implementation = web3.utils.randomHex(20);
const salt = web3.utils.randomHex(32);
const factory = await ClonesMock.new();
// deploy once
expectEvent(
await factory.cloneDeterministic(implementation, salt, '0x'),
'NewInstance',
);
// deploy twice
await expectRevert(
factory.cloneDeterministic(implementation, salt, '0x'),
'ERC1167: create2 failed',
);
});
it('address prediction', async function () {
const implementation = web3.utils.randomHex(20);
const salt = web3.utils.randomHex(32);
const factory = await ClonesMock.new();
const predicted = await factory.predictDeterministicAddress(implementation, salt);
expectEvent(
await factory.cloneDeterministic(implementation, salt, '0x'),
'NewInstance',
{ instance: predicted },
);
});
});
});
...@@ -80,7 +80,7 @@ contract('ProxyAdmin', function (accounts) { ...@@ -80,7 +80,7 @@ contract('ProxyAdmin', function (accounts) {
describe('#upgradeAndCall', function () { describe('#upgradeAndCall', function () {
context('with unauthorized account', function () { context('with unauthorized account', function () {
it('fails to upgrade', async function () { it('fails to upgrade', async function () {
const callData = new ImplV1('').contract.methods['initializeNonPayable(uint256)'](1337).encodeABI(); const callData = new ImplV1('').contract.methods.initializeNonPayableWithValue(1337).encodeABI();
await expectRevert( await expectRevert(
this.proxyAdmin.upgradeAndCall(this.proxy.address, this.implementationV2.address, callData, this.proxyAdmin.upgradeAndCall(this.proxy.address, this.implementationV2.address, callData,
{ from: anotherAccount }, { from: anotherAccount },
...@@ -104,7 +104,7 @@ contract('ProxyAdmin', function (accounts) { ...@@ -104,7 +104,7 @@ contract('ProxyAdmin', function (accounts) {
context('with valid callData', function () { context('with valid callData', function () {
it('upgrades implementation', async function () { it('upgrades implementation', async function () {
const callData = new ImplV1('').contract.methods['initializeNonPayable(uint256)'](1337).encodeABI(); const callData = new ImplV1('').contract.methods.initializeNonPayableWithValue(1337).encodeABI();
await this.proxyAdmin.upgradeAndCall(this.proxy.address, this.implementationV2.address, callData, await this.proxyAdmin.upgradeAndCall(this.proxy.address, this.implementationV2.address, callData,
{ from: proxyAdminOwner }, { from: proxyAdminOwner },
); );
......
const { BN, expectRevert, expectEvent, constants } = require('@openzeppelin/test-helpers'); const { BN, expectRevert, expectEvent, constants } = require('@openzeppelin/test-helpers');
const { ZERO_ADDRESS } = constants; const { ZERO_ADDRESS } = constants;
const { toChecksumAddress, keccak256 } = require('ethereumjs-util'); const ethereumjsUtil = require('ethereumjs-util');
const { expect } = require('chai'); const { expect } = require('chai');
...@@ -19,6 +19,10 @@ const ClashingImplementation = artifacts.require('ClashingImplementation'); ...@@ -19,6 +19,10 @@ const ClashingImplementation = artifacts.require('ClashingImplementation');
const IMPLEMENTATION_LABEL = 'eip1967.proxy.implementation'; const IMPLEMENTATION_LABEL = 'eip1967.proxy.implementation';
const ADMIN_LABEL = 'eip1967.proxy.admin'; const ADMIN_LABEL = 'eip1967.proxy.admin';
function toChecksumAddress (address) {
return ethereumjsUtil.toChecksumAddress('0x' + address.replace(/^0x/, '').padStart(40, '0'));
}
module.exports = function shouldBehaveLikeTransparentUpgradeableProxy (createProxy, accounts) { module.exports = function shouldBehaveLikeTransparentUpgradeableProxy (createProxy, accounts) {
const [proxyAdminAddress, proxyAdminOwner, anotherAccount] = accounts; const [proxyAdminAddress, proxyAdminOwner, anotherAccount] = accounts;
...@@ -308,13 +312,13 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy (createPro ...@@ -308,13 +312,13 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy (createPro
describe('storage', function () { describe('storage', function () {
it('should store the implementation address in specified location', async function () { it('should store the implementation address in specified location', async function () {
const slot = '0x' + new BN(keccak256(Buffer.from(IMPLEMENTATION_LABEL))).subn(1).toString(16); const slot = '0x' + new BN(ethereumjsUtil.keccak256(Buffer.from(IMPLEMENTATION_LABEL))).subn(1).toString(16);
const implementation = toChecksumAddress(await web3.eth.getStorageAt(this.proxyAddress, slot)); const implementation = toChecksumAddress(await web3.eth.getStorageAt(this.proxyAddress, slot));
expect(implementation).to.be.equal(this.implementationV0); expect(implementation).to.be.equal(this.implementationV0);
}); });
it('should store the admin proxy in specified location', async function () { it('should store the admin proxy in specified location', async function () {
const slot = '0x' + new BN(keccak256(Buffer.from(ADMIN_LABEL))).subn(1).toString(16); const slot = '0x' + new BN(ethereumjsUtil.keccak256(Buffer.from(ADMIN_LABEL))).subn(1).toString(16);
const proxyAdmin = toChecksumAddress(await web3.eth.getStorageAt(this.proxyAddress, slot)); const proxyAdmin = toChecksumAddress(await web3.eth.getStorageAt(this.proxyAddress, slot));
expect(proxyAdmin).to.be.equal(proxyAdminAddress); expect(proxyAdmin).to.be.equal(proxyAdminAddress);
}); });
......
const { expectRevert, expectEvent } = require('@openzeppelin/test-helpers');
const { expect } = require('chai');
const UpgradeableBeacon = artifacts.require('UpgradeableBeacon');
const Implementation1 = artifacts.require('Implementation1');
const Implementation2 = artifacts.require('Implementation2');
contract('UpgradeableBeacon', function (accounts) {
const [owner, other] = accounts;
it('cannot be created with non-contract implementation', async function () {
await expectRevert(
UpgradeableBeacon.new(accounts[0]),
'UpgradeableBeacon: implementation is not a contract',
);
});
context('once deployed', async function () {
beforeEach('deploying beacon', async function () {
this.v1 = await Implementation1.new();
this.beacon = await UpgradeableBeacon.new(this.v1.address, { from: owner });
});
it('returns implementation', async function () {
expect(await this.beacon.implementation()).to.equal(this.v1.address);
});
it('can be upgraded by the owner', async function () {
const v2 = await Implementation2.new();
const receipt = await this.beacon.upgradeTo(v2.address, { from: owner });
expectEvent(receipt, 'Upgraded', { implementation: v2.address });
expect(await this.beacon.implementation()).to.equal(v2.address);
});
it('cannot be upgraded to a non-contract', async function () {
await expectRevert(
this.beacon.upgradeTo(other, { from: owner }),
'UpgradeableBeacon: implementation is not a contract',
);
});
it('cannot be upgraded by other account', async function () {
const v2 = await Implementation2.new();
await expectRevert(
this.beacon.upgradeTo(v2.address, { from: other }),
'Ownable: caller is not the owner',
);
});
});
});
const { BN, expectRevert } = require('@openzeppelin/test-helpers'); const { BN, expectRevert } = require('@openzeppelin/test-helpers');
const { toChecksumAddress, keccak256 } = require('ethereumjs-util'); const ethereumjsUtil = require('ethereumjs-util');
const { expect } = require('chai'); const { expect } = require('chai');
...@@ -7,6 +7,10 @@ const DummyImplementation = artifacts.require('DummyImplementation'); ...@@ -7,6 +7,10 @@ const DummyImplementation = artifacts.require('DummyImplementation');
const IMPLEMENTATION_LABEL = 'eip1967.proxy.implementation'; const IMPLEMENTATION_LABEL = 'eip1967.proxy.implementation';
function toChecksumAddress (address) {
return ethereumjsUtil.toChecksumAddress('0x' + address.replace(/^0x/, '').padStart(40, '0'));
}
module.exports = function shouldBehaveLikeUpgradeableProxy (createProxy, proxyAdminAddress, proxyCreator) { module.exports = function shouldBehaveLikeUpgradeableProxy (createProxy, proxyAdminAddress, proxyCreator) {
it('cannot be initialized with a non-contract address', async function () { it('cannot be initialized with a non-contract address', async function () {
const nonContractAddress = proxyCreator; const nonContractAddress = proxyCreator;
...@@ -24,7 +28,7 @@ module.exports = function shouldBehaveLikeUpgradeableProxy (createProxy, proxyAd ...@@ -24,7 +28,7 @@ module.exports = function shouldBehaveLikeUpgradeableProxy (createProxy, proxyAd
const assertProxyInitialization = function ({ value, balance }) { const assertProxyInitialization = function ({ value, balance }) {
it('sets the implementation address', async function () { it('sets the implementation address', async function () {
const slot = '0x' + new BN(keccak256(Buffer.from(IMPLEMENTATION_LABEL))).subn(1).toString(16); const slot = '0x' + new BN(ethereumjsUtil.keccak256(Buffer.from(IMPLEMENTATION_LABEL))).subn(1).toString(16);
const implementation = toChecksumAddress(await web3.eth.getStorageAt(this.proxy, slot)); const implementation = toChecksumAddress(await web3.eth.getStorageAt(this.proxy, slot));
expect(implementation).to.be.equal(this.implementation); expect(implementation).to.be.equal(this.implementation);
}); });
...@@ -144,7 +148,7 @@ module.exports = function shouldBehaveLikeUpgradeableProxy (createProxy, proxyAd ...@@ -144,7 +148,7 @@ module.exports = function shouldBehaveLikeUpgradeableProxy (createProxy, proxyAd
describe('non payable', function () { describe('non payable', function () {
const expectedInitializedValue = 10; const expectedInitializedValue = 10;
const initializeData = new DummyImplementation('').contract const initializeData = new DummyImplementation('').contract
.methods['initializeNonPayable(uint256)'](expectedInitializedValue).encodeABI(); .methods.initializeNonPayableWithValue(expectedInitializedValue).encodeABI();
describe('when not sending balance', function () { describe('when not sending balance', function () {
beforeEach('creating proxy', async function () { beforeEach('creating proxy', async function () {
...@@ -175,7 +179,7 @@ module.exports = function shouldBehaveLikeUpgradeableProxy (createProxy, proxyAd ...@@ -175,7 +179,7 @@ module.exports = function shouldBehaveLikeUpgradeableProxy (createProxy, proxyAd
describe('payable', function () { describe('payable', function () {
const expectedInitializedValue = 42; const expectedInitializedValue = 42;
const initializeData = new DummyImplementation('').contract const initializeData = new DummyImplementation('').contract
.methods['initializePayable(uint256)'](expectedInitializedValue).encodeABI(); .methods.initializePayableWithValue(expectedInitializedValue).encodeABI();
describe('when not sending balance', function () { describe('when not sending balance', function () {
beforeEach('creating proxy', async function () { beforeEach('creating proxy', async function () {
...@@ -210,5 +214,17 @@ module.exports = function shouldBehaveLikeUpgradeableProxy (createProxy, proxyAd ...@@ -210,5 +214,17 @@ module.exports = function shouldBehaveLikeUpgradeableProxy (createProxy, proxyAd
}); });
}); });
}); });
describe('reverting initialization', function () {
const initializeData = new DummyImplementation('').contract
.methods.reverts().encodeABI();
it('reverts', async function () {
await expectRevert(
createProxy(this.implementation, proxyAdminAddress, initializeData, { from: proxyCreator }),
'DummyImplementation reverted',
);
});
});
}); });
}; };
...@@ -106,7 +106,7 @@ function shouldBehaveLikeERC1155 ([minter, firstTokenHolder, secondTokenHolder, ...@@ -106,7 +106,7 @@ function shouldBehaveLikeERC1155 ([minter, firstTokenHolder, secondTokenHolder,
[firstTokenHolder, secondTokenHolder, ZERO_ADDRESS], [firstTokenHolder, secondTokenHolder, ZERO_ADDRESS],
[firstTokenId, secondTokenId, unknownTokenId], [firstTokenId, secondTokenId, unknownTokenId],
), ),
'ERC1155: batch balance query for the zero address', 'ERC1155: balance query for the zero address',
); );
}); });
......
...@@ -80,7 +80,7 @@ contract('ERC1155Pausable', function (accounts) { ...@@ -80,7 +80,7 @@ contract('ERC1155Pausable', function (accounts) {
it('reverts when trying to burnBatch', async function () { it('reverts when trying to burnBatch', async function () {
await expectRevert( await expectRevert(
this.token.burn(holder, [firstTokenId], [firstTokenAmount]), this.token.burnBatch(holder, [firstTokenId], [firstTokenAmount]),
'ERC1155Pausable: token transfer while paused', 'ERC1155Pausable: token transfer while paused',
); );
}); });
......
...@@ -447,4 +447,37 @@ contract('ERC777', function (accounts) { ...@@ -447,4 +447,37 @@ contract('ERC777', function (accounts) {
expect(await this.token.defaultOperators()).to.deep.equal([]); expect(await this.token.defaultOperators()).to.deep.equal([]);
}); });
}); });
describe('relative order of hooks', function () {
beforeEach(async function () {
await singletons.ERC1820Registry(registryFunder);
this.sender = await ERC777SenderRecipientMock.new();
await this.sender.registerRecipient(this.sender.address);
await this.sender.registerSender(this.sender.address);
this.token = await ERC777.new(holder, initialSupply, name, symbol, []);
await this.token.send(this.sender.address, 1, '0x', { from: holder });
});
it('send', async function () {
const { receipt } = await this.sender.send(this.token.address, anyone, 1, '0x');
const internalBeforeHook = receipt.logs.findIndex(l => l.event === 'BeforeTokenTransfer');
expect(internalBeforeHook).to.be.gte(0);
const externalSendHook = receipt.logs.findIndex(l => l.event === 'TokensToSendCalled');
expect(externalSendHook).to.be.gte(0);
expect(externalSendHook).to.be.lt(internalBeforeHook);
});
it('burn', async function () {
const { receipt } = await this.sender.burn(this.token.address, 1, '0x');
const internalBeforeHook = receipt.logs.findIndex(l => l.event === 'BeforeTokenTransfer');
expect(internalBeforeHook).to.be.gte(0);
const externalSendHook = receipt.logs.findIndex(l => l.event === 'TokensToSendCalled');
expect(externalSendHook).to.be.gte(0);
expect(externalSendHook).to.be.lt(internalBeforeHook);
});
});
}); });
const { BN, expectEvent } = require('@openzeppelin/test-helpers'); const { BN, constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers');
const { expect } = require('chai'); const { expect } = require('chai');
const zip = require('lodash.zip'); const zip = require('lodash.zip');
...@@ -139,4 +139,43 @@ contract('EnumerableMap', function (accounts) { ...@@ -139,4 +139,43 @@ contract('EnumerableMap', function (accounts) {
expect(await this.map.contains(keyB)).to.equal(false); expect(await this.map.contains(keyB)).to.equal(false);
}); });
}); });
describe('read', function () {
beforeEach(async function () {
await this.map.set(keyA, accountA);
});
describe('get', function () {
it('existing value', async function () {
expect(await this.map.get(keyA)).to.be.equal(accountA);
});
it('missing value', async function () {
await expectRevert(this.map.get(keyB), 'EnumerableMap: nonexistent key');
});
});
describe('get with message', function () {
it('existing value', async function () {
expect(await this.map.getWithMessage(keyA, 'custom error string')).to.be.equal(accountA);
});
it('missing value', async function () {
await expectRevert(this.map.getWithMessage(keyB, 'custom error string'), 'custom error string');
});
});
describe('tryGet', function () {
it('existing value', async function () {
expect(await this.map.tryGet(keyA)).to.be.deep.equal({
0: true,
1: accountA,
});
});
it('missing value', async function () {
expect(await this.map.tryGet(keyB)).to.be.deep.equal({
0: false,
1: constants.ZERO_ADDRESS,
});
});
});
});
}); });
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