commit
aaa5a5abab
@ -0,0 +1,5 @@ |
||||
--- |
||||
'openzeppelin-solidity': patch |
||||
--- |
||||
|
||||
`AccessControlDefaultAdminRules`: Clean up pending admin schedule on renounce. |
Binary file not shown.
@ -1,3 +1,10 @@ |
||||
// environment |
||||
definition nonpayable(env e) returns bool = e.msg.value == 0; |
||||
definition nonzerosender(env e) returns bool = e.msg.sender != 0; |
||||
|
||||
definition max_uint48() returns uint48 = 0xffffffffffff; |
||||
// constants |
||||
definition max_uint48() returns mathint = (1 << 48) - 1; |
||||
|
||||
// math |
||||
definition min(mathint a, mathint b) returns mathint = a < b ? a : b; |
||||
definition max(mathint a, mathint b) returns mathint = a > b ? a : b; |
||||
|
@ -0,0 +1,24 @@ |
||||
const { HardhatError } = require('hardhat/internal/core/errors'); |
||||
|
||||
// Modifies `artifacts.require(X)` so that instead of X it loads the XUpgradeable contract.
|
||||
// This allows us to run the same test suite on both the original and the transpiled and renamed Upgradeable contracts.
|
||||
|
||||
extendEnvironment(env => { |
||||
const artifactsRequire = env.artifacts.require; |
||||
|
||||
env.artifacts.require = name => { |
||||
for (const suffix of ['UpgradeableWithInit', 'Upgradeable', '']) { |
||||
try { |
||||
return artifactsRequire(name + suffix); |
||||
} catch (e) { |
||||
// HH700: Artifact not found - from https://hardhat.org/hardhat-runner/docs/errors#HH700
|
||||
if (HardhatError.isHardhatError(e) && e.number === 700 && suffix !== '') { |
||||
continue; |
||||
} else { |
||||
throw e; |
||||
} |
||||
} |
||||
} |
||||
throw new Error('Unreachable'); |
||||
}; |
||||
}); |
@ -0,0 +1,25 @@ |
||||
const { internalTask } = require('hardhat/config'); |
||||
const { TASK_TEST_GET_TEST_FILES } = require('hardhat/builtin-tasks/task-names'); |
||||
|
||||
// Modifies `hardhat test` to skip the proxy tests after proxies are removed by the transpiler for upgradeability.
|
||||
|
||||
internalTask(TASK_TEST_GET_TEST_FILES).setAction(async (args, hre, runSuper) => { |
||||
const path = require('path'); |
||||
const { promises: fs } = require('fs'); |
||||
|
||||
const hasProxies = await fs |
||||
.access(path.join(hre.config.paths.sources, 'proxy/Proxy.sol')) |
||||
.then(() => true) |
||||
.catch(() => false); |
||||
|
||||
const ignoredIfProxy = [ |
||||
'proxy/beacon/BeaconProxy.test.js', |
||||
'proxy/beacon/UpgradeableBeacon.test.js', |
||||
'proxy/ERC1967/ERC1967Proxy.test.js', |
||||
'proxy/transparent/ProxyAdmin.test.js', |
||||
'proxy/transparent/TransparentUpgradeableProxy.test.js', |
||||
'proxy/utils/UUPSUpgradeable.test.js', |
||||
].map(p => path.join(hre.config.paths.tests, p)); |
||||
|
||||
return (await runSuper(args)).filter(file => hasProxies || !ignoredIfProxy.includes(file)); |
||||
}); |
@ -0,0 +1,21 @@ |
||||
The upgradeable variant of OpenZeppelin Contracts is automatically generated from the original Solidity code. We call this process "transpilation" and it is implemented by our [Upgradeability Transpiler](https://github.com/OpenZeppelin/openzeppelin-transpiler/). |
||||
|
||||
When the `master` branch or `release-v*` branches are updated, the code is transpiled and pushed to [OpenZeppelin/openzeppelin-contracts-upgradeable](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable) by the `upgradeable.yml` workflow. |
||||
|
||||
## `transpile.sh` |
||||
|
||||
Applies patches and invokes the transpiler with the command line flags we need for our requirements (for example, excluding certain files). |
||||
|
||||
## `transpile-onto.sh` |
||||
|
||||
``` |
||||
bash scripts/upgradeable/transpile-onto.sh <target> [<base>] |
||||
``` |
||||
|
||||
Transpiles the contents of the current git branch and commits the result as a new commit on branch `<target>`. If branch `<target>` doesn't exist, it will copy the commit history of `[<base>]` (this is used in GitHub Actions, but is usually not necessary locally). |
||||
|
||||
## `patch-apply.sh` & `patch-save.sh` |
||||
|
||||
Some of the upgradeable contract variants require ad-hoc changes that are not implemented by the transpiler. These changes are implemented by patches stored in `upgradeable.patch` in this directory. `patch-apply.sh` applies these patches. |
||||
|
||||
If the patches fail to apply due to changes in the repo, the conflicts have to be resolved manually. Once fixed, `patch-save.sh` will take the changes staged in Git and update `upgradeable.patch` to match. |
@ -0,0 +1,19 @@ |
||||
#!/usr/bin/env bash |
||||
|
||||
set -euo pipefail |
||||
|
||||
DIRNAME="$(dirname -- "${BASH_SOURCE[0]}")" |
||||
PATCH="$DIRNAME/upgradeable.patch" |
||||
|
||||
error() { |
||||
echo Error: "$*" >&2 |
||||
exit 1 |
||||
} |
||||
|
||||
if ! git diff-files --quiet ":!$PATCH" || ! git diff-index --quiet HEAD ":!$PATCH"; then |
||||
error "Repository must have no staged or unstaged changes" |
||||
fi |
||||
|
||||
if ! git apply -3 "$PATCH"; then |
||||
error "Fix conflicts and run $DIRNAME/patch-save.sh" |
||||
fi |
@ -0,0 +1,18 @@ |
||||
#!/usr/bin/env bash |
||||
|
||||
set -euo pipefail |
||||
|
||||
DIRNAME="$(dirname -- "${BASH_SOURCE[0]}")" |
||||
PATCH="$DIRNAME/upgradeable.patch" |
||||
|
||||
error() { |
||||
echo Error: "$*" >&2 |
||||
exit 1 |
||||
} |
||||
|
||||
if ! git diff-files --quiet ":!$PATCH"; then |
||||
error "Unstaged changes. Stage to include in patch or temporarily stash." |
||||
fi |
||||
|
||||
git diff-index --cached --patch --output="$PATCH" HEAD |
||||
git restore --staged --worktree ":!$PATCH" |
@ -0,0 +1,44 @@ |
||||
#!/usr/bin/env bash |
||||
|
||||
set -euo pipefail |
||||
|
||||
if [ $# -lt 1 ]; then |
||||
echo "usage: bash $0 <target> [<base>]" >&2 |
||||
exit 1 |
||||
fi |
||||
|
||||
set -x |
||||
|
||||
target="$1" |
||||
base="${2-}" |
||||
|
||||
bash scripts/upgradeable/transpile.sh |
||||
|
||||
commit="$(git rev-parse --short HEAD)" |
||||
branch="$(git rev-parse --abbrev-ref HEAD)" |
||||
|
||||
git add contracts |
||||
|
||||
# detach from the current branch to avoid making changes to it |
||||
git checkout --quiet --detach |
||||
|
||||
# switch to the target branch, creating it if necessary |
||||
if git rev-parse -q --verify "$target"; then |
||||
# if the branch exists, make it the current HEAD without checking out its contents |
||||
git reset --soft "$target" |
||||
git checkout "$target" |
||||
else |
||||
# if the branch doesn't exist, create it as an orphan and check it out |
||||
git checkout --orphan "$target" |
||||
if [ -n "$base" ] && git rev-parse -q --verify "$base"; then |
||||
# if base was specified and it exists, set it as the branch history |
||||
git reset --soft "$base" |
||||
fi |
||||
fi |
||||
|
||||
# commit if there are changes to commit |
||||
if ! git diff --quiet --cached; then |
||||
git commit -m "Transpile $commit" |
||||
fi |
||||
|
||||
git checkout "$branch" |
@ -0,0 +1,35 @@ |
||||
#!/usr/bin/env bash |
||||
|
||||
set -euo pipefail -x |
||||
|
||||
DIRNAME="$(dirname -- "${BASH_SOURCE[0]}")" |
||||
|
||||
bash "$DIRNAME/patch-apply.sh" |
||||
|
||||
npm run clean |
||||
npm run compile |
||||
|
||||
build_info=($(jq -r '.input.sources | keys | if any(test("^contracts/mocks/.*\\bunreachable\\b")) then empty else input_filename end' artifacts/build-info/*)) |
||||
build_info_num=${#build_info[@]} |
||||
|
||||
if [ $build_info_num -ne 1 ]; then |
||||
echo "found $build_info_num relevant build info files but expected just 1" |
||||
exit 1 |
||||
fi |
||||
|
||||
# -D: delete original and excluded files |
||||
# -b: use this build info file |
||||
# -i: use included Initializable |
||||
# -x: exclude proxy-related contracts with a few exceptions |
||||
# -p: emit public initializer |
||||
npx @openzeppelin/upgrade-safe-transpiler@latest -D \ |
||||
-b "$build_info" \ |
||||
-i contracts/proxy/utils/Initializable.sol \ |
||||
-x 'contracts-exposed/**/*' \ |
||||
-x 'contracts/proxy/**/*' \ |
||||
-x '!contracts/proxy/Clones.sol' \ |
||||
-x '!contracts/proxy/ERC1967/ERC1967Storage.sol' \ |
||||
-x '!contracts/proxy/ERC1967/ERC1967Upgrade.sol' \ |
||||
-x '!contracts/proxy/utils/UUPSUpgradeable.sol' \ |
||||
-x '!contracts/proxy/beacon/IBeacon.sol' \ |
||||
-p 'contracts/**/presets/**/*' |
@ -0,0 +1,481 @@ |
||||
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
|
||||
deleted file mode 100644
|
||||
index 2797a088..00000000
|
||||
--- a/.github/ISSUE_TEMPLATE/bug_report.md
|
||||
+++ /dev/null
|
||||
@@ -1,21 +0,0 @@
|
||||
----
|
||||
-name: Bug report
|
||||
-about: Report a bug in OpenZeppelin Contracts
|
||||
-
|
||||
----
|
||||
-
|
||||
-<!-- Briefly describe the issue you're experiencing. Tell us what you were trying to do and what happened instead. -->
|
||||
-
|
||||
-<!-- Remember, this is not a place to ask for help debugging code. For that, we welcome you in the OpenZeppelin Community Forum: https://forum.openzeppelin.com/. -->
|
||||
-
|
||||
-**💻 Environment**
|
||||
-
|
||||
-<!-- Tell us what version of OpenZeppelin Contracts you're using, and how you're using it: Truffle, Remix, etc. -->
|
||||
-
|
||||
-**📝 Details**
|
||||
-
|
||||
-<!-- Describe the problem you have been experiencing in more detail. Include as much information as you think is relevant. Keep in mind that transactions can fail for many reasons; context is key here. -->
|
||||
-
|
||||
-**🔢 Code to reproduce bug**
|
||||
-
|
||||
-<!-- We will be able to better help if you provide a minimal example that triggers the bug. -->
|
||||
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
|
||||
index 4018cef2..d343a53d 100644
|
||||
--- a/.github/ISSUE_TEMPLATE/config.yml
|
||||
+++ b/.github/ISSUE_TEMPLATE/config.yml
|
||||
@@ -1,4 +1,8 @@
|
||||
+blank_issues_enabled: false
|
||||
contact_links:
|
||||
+ - name: Bug Reports & Feature Requests
|
||||
+ url: https://github.com/OpenZeppelin/openzeppelin-contracts/issues/new/choose
|
||||
+ about: Visit the OpenZeppelin Contracts repository
|
||||
- name: Questions & Support Requests
|
||||
url: https://forum.openzeppelin.com/c/support/contracts/18
|
||||
about: Ask in the OpenZeppelin Forum
|
||||
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
|
||||
deleted file mode 100644
|
||||
index ff596b0c..00000000
|
||||
--- a/.github/ISSUE_TEMPLATE/feature_request.md
|
||||
+++ /dev/null
|
||||
@@ -1,14 +0,0 @@
|
||||
----
|
||||
-name: Feature request
|
||||
-about: Suggest an idea for OpenZeppelin Contracts
|
||||
-
|
||||
----
|
||||
-
|
||||
-**🧐 Motivation**
|
||||
-<!-- Is your feature request related to a specific problem? Is it just a crazy idea? Tell us about it! -->
|
||||
-
|
||||
-**📝 Details**
|
||||
-<!-- Please describe your feature request in detail. -->
|
||||
-
|
||||
-<!-- Make sure that you have reviewed the OpenZeppelin Contracts Contributor Guidelines. -->
|
||||
-<!-- https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/CONTRIBUTING.md -->
|
||||
diff --git a/README.md b/README.md
|
||||
index 9fc95518..53130e3c 100644
|
||||
--- a/README.md
|
||||
+++ b/README.md
|
||||
@@ -16,17 +16,20 @@
|
||||
|
||||
:building_construction: **Want to scale your decentralized application?** Check out [OpenZeppelin Defender](https://openzeppelin.com/defender) — a secure platform for automating and monitoring your operations.
|
||||
|
||||
+> **Note**
|
||||
+> You are looking at the upgradeable variant of OpenZeppelin Contracts. Be sure to review the documentation on [Using OpenZeppelin Contracts with Upgrades](https://docs.openzeppelin.com/contracts/4.x/upgradeable).
|
||||
+
|
||||
## Overview
|
||||
|
||||
### Installation
|
||||
|
||||
```
|
||||
-$ npm install @openzeppelin/contracts
|
||||
+$ npm install @openzeppelin/contracts-upgradeable
|
||||
```
|
||||
|
||||
OpenZeppelin Contracts features a [stable API](https://docs.openzeppelin.com/contracts/releases-stability#api-stability), which means that your contracts won't break unexpectedly when upgrading to a newer minor version.
|
||||
|
||||
-An alternative to npm is to use the GitHub repository (`openzeppelin/openzeppelin-contracts`) to retrieve the contracts. When doing this, make sure to specify the tag for a release such as `v4.5.0`, instead of using the `master` branch.
|
||||
+An alternative to npm is to use the GitHub repository (`openzeppelin/openzeppelin-contracts-upgradeable`) to retrieve the contracts. When doing this, make sure to specify the tag for a release such as `v4.5.0`, instead of using the `master` branch.
|
||||
|
||||
### Usage
|
||||
|
||||
@@ -35,10 +38,11 @@ Once installed, you can use the contracts in the library by importing them:
|
||||
```solidity
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
-import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
|
||||
+import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
|
||||
|
||||
-contract MyCollectible is ERC721 {
|
||||
- constructor() ERC721("MyCollectible", "MCO") {
|
||||
+contract MyCollectible is ERC721Upgradeable {
|
||||
+ function initialize() initializer public {
|
||||
+ __ERC721_init("MyCollectible", "MCO");
|
||||
}
|
||||
}
|
||||
```
|
||||
diff --git a/contracts/finance/VestingWallet.sol b/contracts/finance/VestingWallet.sol
|
||||
index fe67eb54..d26ea4e1 100644
|
||||
--- a/contracts/finance/VestingWallet.sol
|
||||
+++ b/contracts/finance/VestingWallet.sol
|
||||
@@ -15,6 +15,8 @@ import "../utils/Context.sol";
|
||||
* Any token transferred to this contract will follow the vesting schedule as if they were locked from the beginning.
|
||||
* Consequently, if the vesting has already started, any amount of tokens sent to this contract will (at least partly)
|
||||
* be immediately releasable.
|
||||
+ *
|
||||
+ * @custom:storage-size 52
|
||||
*/
|
||||
contract VestingWallet is Context {
|
||||
event EtherReleased(uint256 amount);
|
||||
diff --git a/contracts/governance/TimelockControllerWith46Migration.sol b/contracts/governance/TimelockControllerWith46Migration.sol
|
||||
new file mode 100644
|
||||
index 00000000..3315e7bd
|
||||
--- /dev/null
|
||||
+++ b/contracts/governance/TimelockControllerWith46Migration.sol
|
||||
@@ -0,0 +1,39 @@
|
||||
+// SPDX-License-Identifier: MIT
|
||||
+// OpenZeppelin Contracts v4.6.0 (governance/TimelockControllerWith46Migration.sol)
|
||||
+
|
||||
+pragma solidity ^0.8.0;
|
||||
+
|
||||
+import "./TimelockController.sol";
|
||||
+
|
||||
+/**
|
||||
+ * @dev Extension of the TimelockController that includes an additional
|
||||
+ * function to migrate from OpenZeppelin Upgradeable Contracts <4.6 to >=4.6.
|
||||
+ *
|
||||
+ * This migration is necessary to setup administration rights over the new
|
||||
+ * `CANCELLER_ROLE`.
|
||||
+ *
|
||||
+ * The migration is trustless and can be performed by anyone.
|
||||
+ *
|
||||
+ * _Available since v4.6._
|
||||
+ */
|
||||
+contract TimelockControllerWith46Migration is TimelockController {
|
||||
+ constructor(
|
||||
+ uint256 minDelay,
|
||||
+ address[] memory proposers,
|
||||
+ address[] memory executors,
|
||||
+ address admin
|
||||
+ ) TimelockController(minDelay, proposers, executors, admin) {}
|
||||
+
|
||||
+ /**
|
||||
+ * @dev Migration function. Running it is necessary for upgradeable
|
||||
+ * instances that were initially setup with code <4.6 and that upgraded
|
||||
+ * to >=4.6.
|
||||
+ */
|
||||
+ function migrateTo46() public virtual {
|
||||
+ require(
|
||||
+ getRoleAdmin(PROPOSER_ROLE) == TIMELOCK_ADMIN_ROLE && getRoleAdmin(CANCELLER_ROLE) == DEFAULT_ADMIN_ROLE,
|
||||
+ "TimelockController: already migrated"
|
||||
+ );
|
||||
+ _setRoleAdmin(CANCELLER_ROLE, TIMELOCK_ADMIN_ROLE);
|
||||
+ }
|
||||
+}
|
||||
diff --git a/contracts/governance/extensions/GovernorVotes.sol b/contracts/governance/extensions/GovernorVotes.sol
|
||||
index 64431711..885f0e42 100644
|
||||
--- a/contracts/governance/extensions/GovernorVotes.sol
|
||||
+++ b/contracts/governance/extensions/GovernorVotes.sol
|
||||
@@ -10,6 +10,8 @@ import "../../interfaces/IERC5805.sol";
|
||||
* @dev Extension of {Governor} for voting weight extraction from an {ERC20Votes} token, or since v4.5 an {ERC721Votes} token.
|
||||
*
|
||||
* _Available since v4.3._
|
||||
+ *
|
||||
+ * @custom:storage-size 51
|
||||
*/
|
||||
abstract contract GovernorVotes is Governor {
|
||||
IERC5805 public immutable token;
|
||||
diff --git a/contracts/governance/extensions/GovernorVotesComp.sol b/contracts/governance/extensions/GovernorVotesComp.sol
|
||||
index 17250ad7..1d26b72e 100644
|
||||
--- a/contracts/governance/extensions/GovernorVotesComp.sol
|
||||
+++ b/contracts/governance/extensions/GovernorVotesComp.sol
|
||||
@@ -10,6 +10,8 @@ import "../../token/ERC20/extensions/ERC20VotesComp.sol";
|
||||
* @dev Extension of {Governor} for voting weight extraction from a Comp token.
|
||||
*
|
||||
* _Available since v4.3._
|
||||
+ *
|
||||
+ * @custom:storage-size 51
|
||||
*/
|
||||
abstract contract GovernorVotesComp is Governor {
|
||||
ERC20VotesComp public immutable token;
|
||||
diff --git a/contracts/package.json b/contracts/package.json
|
||||
index 55e70b17..ceefb984 100644
|
||||
--- a/contracts/package.json
|
||||
+++ b/contracts/package.json
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
- "name": "@openzeppelin/contracts",
|
||||
+ "name": "@openzeppelin/contracts-upgradeable",
|
||||
"description": "Secure Smart Contract library for Solidity",
|
||||
"version": "4.8.2",
|
||||
"files": [
|
||||
@@ -13,7 +13,7 @@
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
- "url": "https://github.com/OpenZeppelin/openzeppelin-contracts.git"
|
||||
+ "url": "https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable.git"
|
||||
},
|
||||
"keywords": [
|
||||
"solidity",
|
||||
diff --git a/contracts/security/PullPayment.sol b/contracts/security/PullPayment.sol
|
||||
index 65b4980f..f336592e 100644
|
||||
--- a/contracts/security/PullPayment.sol
|
||||
+++ b/contracts/security/PullPayment.sol
|
||||
@@ -22,6 +22,8 @@ import "../utils/escrow/Escrow.sol";
|
||||
* To use, derive from the `PullPayment` contract, and use {_asyncTransfer}
|
||||
* instead of Solidity's `transfer` function. Payees can query their due
|
||||
* payments with {payments}, and retrieve them with {withdrawPayments}.
|
||||
+ *
|
||||
+ * @custom:storage-size 51
|
||||
*/
|
||||
abstract contract PullPayment {
|
||||
Escrow private immutable _escrow;
|
||||
diff --git a/contracts/token/ERC20/extensions/ERC20Capped.sol b/contracts/token/ERC20/extensions/ERC20Capped.sol
|
||||
index 16f830d1..9ef98148 100644
|
||||
--- a/contracts/token/ERC20/extensions/ERC20Capped.sol
|
||||
+++ b/contracts/token/ERC20/extensions/ERC20Capped.sol
|
||||
@@ -7,6 +7,8 @@ import "../ERC20.sol";
|
||||
|
||||
/**
|
||||
* @dev Extension of {ERC20} that adds a cap to the supply of tokens.
|
||||
+ *
|
||||
+ * @custom:storage-size 51
|
||||
*/
|
||||
abstract contract ERC20Capped is ERC20 {
|
||||
uint256 private immutable _cap;
|
||||
diff --git a/contracts/token/ERC20/extensions/ERC20Permit.sol b/contracts/token/ERC20/extensions/ERC20Permit.sol
|
||||
index a357199b..9dc8e894 100644
|
||||
--- a/contracts/token/ERC20/extensions/ERC20Permit.sol
|
||||
+++ b/contracts/token/ERC20/extensions/ERC20Permit.sol
|
||||
@@ -18,6 +18,8 @@ import "../../../utils/Counters.sol";
|
||||
* need to send a transaction, and thus is not required to hold Ether at all.
|
||||
*
|
||||
* _Available since v3.4._
|
||||
+ *
|
||||
+ * @custom:storage-size 51
|
||||
*/
|
||||
abstract contract ERC20Permit is ERC20, IERC20Permit, EIP712 {
|
||||
using Counters for Counters.Counter;
|
||||
diff --git a/contracts/token/ERC20/extensions/ERC20Wrapper.sol b/contracts/token/ERC20/extensions/ERC20Wrapper.sol
|
||||
index bfe782e4..7264fe32 100644
|
||||
--- a/contracts/token/ERC20/extensions/ERC20Wrapper.sol
|
||||
+++ b/contracts/token/ERC20/extensions/ERC20Wrapper.sol
|
||||
@@ -14,6 +14,8 @@ import "../utils/SafeERC20.sol";
|
||||
* wrapping of an existing "basic" ERC20 into a governance token.
|
||||
*
|
||||
* _Available since v4.2._
|
||||
+ *
|
||||
+ * @custom:storage-size 51
|
||||
*/
|
||||
abstract contract ERC20Wrapper is ERC20 {
|
||||
IERC20 private immutable _underlying;
|
||||
diff --git a/contracts/token/ERC20/utils/TokenTimelock.sol b/contracts/token/ERC20/utils/TokenTimelock.sol
|
||||
index ed855b7b..3d30f59d 100644
|
||||
--- a/contracts/token/ERC20/utils/TokenTimelock.sol
|
||||
+++ b/contracts/token/ERC20/utils/TokenTimelock.sol
|
||||
@@ -11,6 +11,8 @@ import "./SafeERC20.sol";
|
||||
*
|
||||
* Useful for simple vesting schedules like "advisors get all of their tokens
|
||||
* after 1 year".
|
||||
+ *
|
||||
+ * @custom:storage-size 53
|
||||
*/
|
||||
contract TokenTimelock {
|
||||
using SafeERC20 for IERC20;
|
||||
diff --git a/contracts/utils/cryptography/EIP712.sol b/contracts/utils/cryptography/EIP712.sol
|
||||
index 6a4e1cad..55d8eced 100644
|
||||
--- a/contracts/utils/cryptography/EIP712.sol
|
||||
+++ b/contracts/utils/cryptography/EIP712.sol
|
||||
@@ -4,7 +4,6 @@
|
||||
pragma solidity ^0.8.8;
|
||||
|
||||
import "./ECDSA.sol";
|
||||
-import "../ShortStrings.sol";
|
||||
import "../../interfaces/IERC5267.sol";
|
||||
|
||||
/**
|
||||
@@ -30,27 +29,19 @@ import "../../interfaces/IERC5267.sol";
|
||||
*
|
||||
* _Available since v3.4._
|
||||
*
|
||||
- * @custom:oz-upgrades-unsafe-allow state-variable-immutable state-variable-assignment
|
||||
+ * @custom:storage-size 52
|
||||
*/
|
||||
abstract contract EIP712 is IERC5267 {
|
||||
- using ShortStrings for *;
|
||||
-
|
||||
bytes32 private constant _TYPE_HASH =
|
||||
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
|
||||
|
||||
- // 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 _cachedDomainSeparator;
|
||||
- uint256 private immutable _cachedChainId;
|
||||
- address private immutable _cachedThis;
|
||||
-
|
||||
+ /// @custom:oz-renamed-from _HASHED_NAME
|
||||
bytes32 private immutable _hashedName;
|
||||
+ /// @custom:oz-renamed-from _HASHED_VERSION
|
||||
bytes32 private immutable _hashedVersion;
|
||||
|
||||
- ShortString private immutable _name;
|
||||
- ShortString private immutable _version;
|
||||
- string private _nameFallback;
|
||||
- string private _versionFallback;
|
||||
+ string private _name;
|
||||
+ string private _version;
|
||||
|
||||
/**
|
||||
* @dev Initializes the domain separator and parameter caches.
|
||||
@@ -65,29 +56,23 @@ abstract contract EIP712 is IERC5267 {
|
||||
* contract upgrade].
|
||||
*/
|
||||
constructor(string memory name, string memory version) {
|
||||
- _name = name.toShortStringWithFallback(_nameFallback);
|
||||
- _version = version.toShortStringWithFallback(_versionFallback);
|
||||
- _hashedName = keccak256(bytes(name));
|
||||
- _hashedVersion = keccak256(bytes(version));
|
||||
-
|
||||
- _cachedChainId = block.chainid;
|
||||
- _cachedDomainSeparator = _buildDomainSeparator();
|
||||
- _cachedThis = address(this);
|
||||
+ _name = name;
|
||||
+ _version = version;
|
||||
+
|
||||
+ // Reset prior values in storage if upgrading
|
||||
+ _hashedName = 0;
|
||||
+ _hashedVersion = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Returns the domain separator for the current chain.
|
||||
*/
|
||||
function _domainSeparatorV4() internal view returns (bytes32) {
|
||||
- if (address(this) == _cachedThis && block.chainid == _cachedChainId) {
|
||||
- return _cachedDomainSeparator;
|
||||
- } else {
|
||||
- return _buildDomainSeparator();
|
||||
- }
|
||||
+ return _buildDomainSeparator();
|
||||
}
|
||||
|
||||
function _buildDomainSeparator() private view returns (bytes32) {
|
||||
- return keccak256(abi.encode(_TYPE_HASH, _hashedName, _hashedVersion, block.chainid, address(this)));
|
||||
+ return keccak256(abi.encode(_TYPE_HASH, _EIP712NameHash(), _EIP712VersionHash(), block.chainid, address(this)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -129,14 +114,80 @@ abstract contract EIP712 is IERC5267 {
|
||||
uint256[] memory extensions
|
||||
)
|
||||
{
|
||||
+ // If the hashed name and version in storage are non-zero, the contract hasn't been properly initialized
|
||||
+ // and the EIP712 domain is not reliable, as it will be missing name and version.
|
||||
+ require(_hashedName == 0 && _hashedVersion == 0, "EIP712: Uninitialized");
|
||||
+
|
||||
return (
|
||||
hex"0f", // 01111
|
||||
- _name.toStringWithFallback(_nameFallback),
|
||||
- _version.toStringWithFallback(_versionFallback),
|
||||
+ _EIP712Name(),
|
||||
+ _EIP712Version(),
|
||||
block.chainid,
|
||||
address(this),
|
||||
bytes32(0),
|
||||
new uint256[](0)
|
||||
);
|
||||
}
|
||||
+
|
||||
+ /**
|
||||
+ * @dev The name parameter for the EIP712 domain.
|
||||
+ *
|
||||
+ * NOTE: This function reads from storage by default, but can be redefined to return a constant value if gas costs
|
||||
+ * are a concern.
|
||||
+ */
|
||||
+ function _EIP712Name() internal virtual view returns (string memory) {
|
||||
+ return _name;
|
||||
+ }
|
||||
+
|
||||
+ /**
|
||||
+ * @dev The version parameter for the EIP712 domain.
|
||||
+ *
|
||||
+ * NOTE: This function reads from storage by default, but can be redefined to return a constant value if gas costs
|
||||
+ * are a concern.
|
||||
+ */
|
||||
+ function _EIP712Version() internal virtual view returns (string memory) {
|
||||
+ return _version;
|
||||
+ }
|
||||
+
|
||||
+ /**
|
||||
+ * @dev The hash of the name parameter for the EIP712 domain.
|
||||
+ *
|
||||
+ * NOTE: In previous versions this function was virtual. In this version you should override `_EIP712Name` instead.
|
||||
+ */
|
||||
+ function _EIP712NameHash() internal view returns (bytes32) {
|
||||
+ string memory name = _EIP712Name();
|
||||
+ if (bytes(name).length > 0) {
|
||||
+ return keccak256(bytes(name));
|
||||
+ } else {
|
||||
+ // If the name is empty, the contract may have been upgraded without initializing the new storage.
|
||||
+ // We return the name hash in storage if non-zero, otherwise we assume the name is empty by design.
|
||||
+ bytes32 hashedName = _hashedName;
|
||||
+ if (hashedName != 0) {
|
||||
+ return hashedName;
|
||||
+ } else {
|
||||
+ return keccak256("");
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ /**
|
||||
+ * @dev The hash of the version parameter for the EIP712 domain.
|
||||
+ *
|
||||
+ * NOTE: In previous versions this function was virtual. In this version you should override `_EIP712Version` instead.
|
||||
+ */
|
||||
+ function _EIP712VersionHash() internal view returns (bytes32) {
|
||||
+ string memory version = _EIP712Version();
|
||||
+ if (bytes(version).length > 0) {
|
||||
+ return keccak256(bytes(version));
|
||||
+ } else {
|
||||
+ // If the version is empty, the contract may have been upgraded without initializing the new storage.
|
||||
+ // We return the version hash in storage if non-zero, otherwise we assume the version is empty by design.
|
||||
+ bytes32 hashedVersion = _hashedVersion;
|
||||
+ if (hashedVersion != 0) {
|
||||
+ return hashedVersion;
|
||||
+ } else {
|
||||
+ return keccak256("");
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
diff --git a/package.json b/package.json
|
||||
index 8458dd61..b4672240 100644
|
||||
--- a/package.json
|
||||
+++ b/package.json
|
||||
@@ -36,7 +36,7 @@
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
- "url": "https://github.com/OpenZeppelin/openzeppelin-contracts.git"
|
||||
+ "url": "https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable.git"
|
||||
},
|
||||
"keywords": [
|
||||
"solidity",
|
||||
diff --git a/test/utils/cryptography/EIP712.test.js b/test/utils/cryptography/EIP712.test.js
|
||||
index 54a4e772..ba4602ed 100644
|
||||
--- a/test/utils/cryptography/EIP712.test.js
|
||||
+++ b/test/utils/cryptography/EIP712.test.js
|
||||
@@ -47,26 +47,6 @@ contract('EIP712', function (accounts) {
|
||||
const rebuildDomain = await getDomain(this.eip712);
|
||||
expect(mapValues(rebuildDomain, String)).to.be.deep.equal(mapValues(this.domain, String));
|
||||
});
|
||||
-
|
||||
- if (shortOrLong === 'short') {
|
||||
- // Long strings are in storage, and the proxy will not be properly initialized unless
|
||||
- // the upgradeable contract variant is used and the initializer is invoked.
|
||||
-
|
||||
- it('adjusts when behind proxy', async function () {
|
||||
- const factory = await Clones.new();
|
||||
- const cloneReceipt = await factory.$clone(this.eip712.address);
|
||||
- const cloneAddress = cloneReceipt.logs.find(({ event }) => event === 'return$clone').args.instance;
|
||||
- const clone = new EIP712Verifier(cloneAddress);
|
||||
-
|
||||
- const cloneDomain = { ...this.domain, verifyingContract: clone.address };
|
||||
-
|
||||
- const reportedDomain = await getDomain(clone);
|
||||
- expect(mapValues(reportedDomain, String)).to.be.deep.equal(mapValues(cloneDomain, String));
|
||||
-
|
||||
- const expectedSeparator = await domainSeparator(cloneDomain);
|
||||
- expect(await clone.$_domainSeparatorV4()).to.equal(expectedSeparator);
|
||||
- });
|
||||
- }
|
||||
});
|
||||
|
||||
it('hash digest', async function () {
|
Loading…
Reference in new issue