upgradeable.patch 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  1. diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
  2. deleted file mode 100644
  3. index 35ad097ff..000000000
  4. --- a/.github/ISSUE_TEMPLATE/bug_report.md
  5. +++ /dev/null
  6. @@ -1,21 +0,0 @@
  7. ----
  8. -name: Bug report
  9. -about: Report a bug in OpenZeppelin Contracts
  10. -
  11. ----
  12. -
  13. -<!-- Briefly describe the issue you're experiencing. Tell us what you were trying to do and what happened instead. -->
  14. -
  15. -<!-- 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/. -->
  16. -
  17. -**💻 Environment**
  18. -
  19. -<!-- Tell us what version of OpenZeppelin Contracts you're using, and how you're using it: Hardhat, Remix, etc. -->
  20. -
  21. -**📝 Details**
  22. -
  23. -<!-- 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. -->
  24. -
  25. -**🔢 Code to reproduce bug**
  26. -
  27. -<!-- We will be able to better help if you provide a minimal example that triggers the bug. -->
  28. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
  29. index 4018cef29..d343a53d8 100644
  30. --- a/.github/ISSUE_TEMPLATE/config.yml
  31. +++ b/.github/ISSUE_TEMPLATE/config.yml
  32. @@ -1,4 +1,8 @@
  33. +blank_issues_enabled: false
  34. contact_links:
  35. + - name: Bug Reports & Feature Requests
  36. + url: https://github.com/OpenZeppelin/openzeppelin-contracts/issues/new/choose
  37. + about: Visit the OpenZeppelin Contracts repository
  38. - name: Questions & Support Requests
  39. url: https://forum.openzeppelin.com/c/support/contracts/18
  40. about: Ask in the OpenZeppelin Forum
  41. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
  42. deleted file mode 100644
  43. index ff596b0c3..000000000
  44. --- a/.github/ISSUE_TEMPLATE/feature_request.md
  45. +++ /dev/null
  46. @@ -1,14 +0,0 @@
  47. ----
  48. -name: Feature request
  49. -about: Suggest an idea for OpenZeppelin Contracts
  50. -
  51. ----
  52. -
  53. -**🧐 Motivation**
  54. -<!-- Is your feature request related to a specific problem? Is it just a crazy idea? Tell us about it! -->
  55. -
  56. -**📝 Details**
  57. -<!-- Please describe your feature request in detail. -->
  58. -
  59. -<!-- Make sure that you have reviewed the OpenZeppelin Contracts Contributor Guidelines. -->
  60. -<!-- https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/CONTRIBUTING.md -->
  61. diff --git a/README.md b/README.md
  62. index 60d0a430a..0e4f91a6d 100644
  63. --- a/README.md
  64. +++ b/README.md
  65. @@ -19,6 +19,9 @@
  66. > [!IMPORTANT]
  67. > OpenZeppelin Contracts uses semantic versioning to communicate backwards compatibility of its API and storage layout. For upgradeable contracts, the storage layout of different major versions should be assumed incompatible, for example, it is unsafe to upgrade from 4.9.3 to 5.0.0. Learn more at [Backwards Compatibility](https://docs.openzeppelin.com/contracts/backwards-compatibility).
  68. ++> [!NOTE]
  69. ++> 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/upgradeable).
  70. ++
  71. ## Overview
  72. ### Installation
  73. @@ -26,7 +29,7 @@
  74. #### Hardhat (npm)
  75. ```
  76. -$ npm install @openzeppelin/contracts
  77. +$ npm install @openzeppelin/contracts-upgradeable
  78. ```
  79. #### Foundry (git)
  80. @@ -38,10 +41,10 @@ $ npm install @openzeppelin/contracts
  81. > Foundry installs the latest version initially, but subsequent `forge update` commands will use the `master` branch.
  82. ```
  83. -$ forge install OpenZeppelin/openzeppelin-contracts
  84. +$ forge install OpenZeppelin/openzeppelin-contracts-upgradeable
  85. ```
  86. -Add `@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/` in `remappings.txt.`
  87. +Add `@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/` in `remappings.txt.`
  88. ### Usage
  89. @@ -50,10 +53,11 @@ Once installed, you can use the contracts in the library by importing them:
  90. ```solidity
  91. pragma solidity ^0.8.20;
  92. -import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
  93. +import {ERC721Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
  94. -contract MyCollectible is ERC721 {
  95. - constructor() ERC721("MyCollectible", "MCO") {
  96. +contract MyCollectible is ERC721Upgradeable {
  97. + function initialize() initializer public {
  98. + __ERC721_init("MyCollectible", "MCO");
  99. }
  100. }
  101. ```
  102. diff --git a/contracts/package.json b/contracts/package.json
  103. index 70ae73bc2..ef659873f 100644
  104. --- a/contracts/package.json
  105. +++ b/contracts/package.json
  106. @@ -1,5 +1,5 @@
  107. {
  108. - "name": "@openzeppelin/contracts",
  109. + "name": "@openzeppelin/contracts-upgradeable",
  110. "description": "Secure Smart Contract library for Solidity",
  111. "version": "5.3.0",
  112. "files": [
  113. @@ -13,7 +13,7 @@
  114. },
  115. "repository": {
  116. "type": "git",
  117. - "url": "https://github.com/OpenZeppelin/openzeppelin-contracts.git"
  118. + "url": "https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable.git"
  119. },
  120. "keywords": [
  121. "solidity",
  122. @@ -28,5 +28,8 @@
  123. "bugs": {
  124. "url": "https://github.com/OpenZeppelin/openzeppelin-contracts/issues"
  125. },
  126. - "homepage": "https://openzeppelin.com/contracts/"
  127. + "homepage": "https://openzeppelin.com/contracts/",
  128. + "peerDependencies": {
  129. + "@openzeppelin/contracts": "<package-version>"
  130. + }
  131. }
  132. diff --git a/contracts/utils/cryptography/EIP712.sol b/contracts/utils/cryptography/EIP712.sol
  133. index c39954e35..fe681f87a 100644
  134. --- a/contracts/utils/cryptography/EIP712.sol
  135. +++ b/contracts/utils/cryptography/EIP712.sol
  136. @@ -4,7 +4,6 @@
  137. pragma solidity ^0.8.20;
  138. import {MessageHashUtils} from "./MessageHashUtils.sol";
  139. -import {ShortStrings, ShortString} from "../ShortStrings.sol";
  140. import {IERC5267} from "../../interfaces/IERC5267.sol";
  141. /**
  142. @@ -25,33 +24,20 @@ import {IERC5267} from "../../interfaces/IERC5267.sol";
  143. * NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method
  144. * https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask].
  145. *
  146. - * NOTE: In the upgradeable version of this contract, the cached values will correspond to the address, and the domain
  147. - * separator of the implementation contract. This will cause the {_domainSeparatorV4} function to always rebuild the
  148. - * separator from the immutable values, which is cheaper than accessing a cached version in cold storage.
  149. - *
  150. - * @custom:oz-upgrades-unsafe-allow state-variable-immutable
  151. + * NOTE: The upgradeable version of this contract does not use an immutable cache and recomputes the domain separator
  152. + * each time {_domainSeparatorV4} is called. That is cheaper than accessing a cached version in cold storage.
  153. */
  154. abstract contract EIP712 is IERC5267 {
  155. - using ShortStrings for *;
  156. -
  157. bytes32 private constant TYPE_HASH =
  158. keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
  159. - // Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to
  160. - // invalidate the cached domain separator if the chain id changes.
  161. - bytes32 private immutable _cachedDomainSeparator;
  162. - uint256 private immutable _cachedChainId;
  163. - address private immutable _cachedThis;
  164. -
  165. + /// @custom:oz-renamed-from _HASHED_NAME
  166. bytes32 private immutable _hashedName;
  167. + /// @custom:oz-renamed-from _HASHED_VERSION
  168. bytes32 private immutable _hashedVersion;
  169. - ShortString private immutable _name;
  170. - ShortString private immutable _version;
  171. - // slither-disable-next-line constable-states
  172. - string private _nameFallback;
  173. - // slither-disable-next-line constable-states
  174. - string private _versionFallback;
  175. + string private _name;
  176. + string private _version;
  177. /**
  178. * @dev Initializes the domain separator and parameter caches.
  179. @@ -66,29 +52,23 @@ abstract contract EIP712 is IERC5267 {
  180. * contract upgrade].
  181. */
  182. constructor(string memory name, string memory version) {
  183. - _name = name.toShortStringWithFallback(_nameFallback);
  184. - _version = version.toShortStringWithFallback(_versionFallback);
  185. - _hashedName = keccak256(bytes(name));
  186. - _hashedVersion = keccak256(bytes(version));
  187. -
  188. - _cachedChainId = block.chainid;
  189. - _cachedDomainSeparator = _buildDomainSeparator();
  190. - _cachedThis = address(this);
  191. + _name = name;
  192. + _version = version;
  193. +
  194. + // Reset prior values in storage if upgrading
  195. + _hashedName = 0;
  196. + _hashedVersion = 0;
  197. }
  198. /**
  199. * @dev Returns the domain separator for the current chain.
  200. */
  201. function _domainSeparatorV4() internal view returns (bytes32) {
  202. - if (address(this) == _cachedThis && block.chainid == _cachedChainId) {
  203. - return _cachedDomainSeparator;
  204. - } else {
  205. - return _buildDomainSeparator();
  206. - }
  207. + return _buildDomainSeparator();
  208. }
  209. function _buildDomainSeparator() private view returns (bytes32) {
  210. - return keccak256(abi.encode(TYPE_HASH, _hashedName, _hashedVersion, block.chainid, address(this)));
  211. + return keccak256(abi.encode(TYPE_HASH, _EIP712NameHash(), _EIP712VersionHash(), block.chainid, address(this)));
  212. }
  213. /**
  214. @@ -125,6 +105,10 @@ abstract contract EIP712 is IERC5267 {
  215. uint256[] memory extensions
  216. )
  217. {
  218. + // If the hashed name and version in storage are non-zero, the contract hasn't been properly initialized
  219. + // and the EIP712 domain is not reliable, as it will be missing name and version.
  220. + require(_hashedName == 0 && _hashedVersion == 0, "EIP712: Uninitialized");
  221. +
  222. return (
  223. hex"0f", // 01111
  224. _EIP712Name(),
  225. @@ -139,22 +123,62 @@ abstract contract EIP712 is IERC5267 {
  226. /**
  227. * @dev The name parameter for the EIP712 domain.
  228. *
  229. - * NOTE: By default this function reads _name which is an immutable value.
  230. - * It only reads from storage if necessary (in case the value is too large to fit in a ShortString).
  231. + * NOTE: This function reads from storage by default, but can be redefined to return a constant value if gas costs
  232. + * are a concern.
  233. */
  234. - // solhint-disable-next-line func-name-mixedcase
  235. - function _EIP712Name() internal view returns (string memory) {
  236. - return _name.toStringWithFallback(_nameFallback);
  237. + function _EIP712Name() internal view virtual returns (string memory) {
  238. + return _name;
  239. }
  240. /**
  241. * @dev The version parameter for the EIP712 domain.
  242. *
  243. - * NOTE: By default this function reads _version which is an immutable value.
  244. - * It only reads from storage if necessary (in case the value is too large to fit in a ShortString).
  245. + * NOTE: This function reads from storage by default, but can be redefined to return a constant value if gas costs
  246. + * are a concern.
  247. */
  248. - // solhint-disable-next-line func-name-mixedcase
  249. - function _EIP712Version() internal view returns (string memory) {
  250. - return _version.toStringWithFallback(_versionFallback);
  251. + function _EIP712Version() internal view virtual returns (string memory) {
  252. + return _version;
  253. + }
  254. +
  255. + /**
  256. + * @dev The hash of the name parameter for the EIP712 domain.
  257. + *
  258. + * NOTE: In previous versions this function was virtual. In this version you should override `_EIP712Name` instead.
  259. + */
  260. + function _EIP712NameHash() internal view returns (bytes32) {
  261. + string memory name = _EIP712Name();
  262. + if (bytes(name).length > 0) {
  263. + return keccak256(bytes(name));
  264. + } else {
  265. + // If the name is empty, the contract may have been upgraded without initializing the new storage.
  266. + // We return the name hash in storage if non-zero, otherwise we assume the name is empty by design.
  267. + bytes32 hashedName = _hashedName;
  268. + if (hashedName != 0) {
  269. + return hashedName;
  270. + } else {
  271. + return keccak256("");
  272. + }
  273. + }
  274. + }
  275. +
  276. + /**
  277. + * @dev The hash of the version parameter for the EIP712 domain.
  278. + *
  279. + * NOTE: In previous versions this function was virtual. In this version you should override `_EIP712Version` instead.
  280. + */
  281. + function _EIP712VersionHash() internal view returns (bytes32) {
  282. + string memory version = _EIP712Version();
  283. + if (bytes(version).length > 0) {
  284. + return keccak256(bytes(version));
  285. + } else {
  286. + // If the version is empty, the contract may have been upgraded without initializing the new storage.
  287. + // We return the version hash in storage if non-zero, otherwise we assume the version is empty by design.
  288. + bytes32 hashedVersion = _hashedVersion;
  289. + if (hashedVersion != 0) {
  290. + return hashedVersion;
  291. + } else {
  292. + return keccak256("");
  293. + }
  294. + }
  295. }
  296. }
  297. diff --git a/package.json b/package.json
  298. index eeeaf0bcd..65581c544 100644
  299. --- a/package.json
  300. +++ b/package.json
  301. @@ -34,7 +34,7 @@
  302. },
  303. "repository": {
  304. "type": "git",
  305. - "url": "https://github.com/OpenZeppelin/openzeppelin-contracts.git"
  306. + "url": "https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable.git"
  307. },
  308. "keywords": [
  309. "solidity",
  310. diff --git a/remappings.txt b/remappings.txt
  311. index 304d1386a..a1cd63bee 100644
  312. --- a/remappings.txt
  313. +++ b/remappings.txt
  314. @@ -1 +1,2 @@
  315. -@openzeppelin/contracts/=contracts/
  316. +@openzeppelin/contracts-upgradeable/=contracts/
  317. +@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/
  318. diff --git a/test/account/AccountERC7702.test.js b/test/account/AccountERC7702.test.js
  319. index d08a52209..7a44bccfe 100644
  320. --- a/test/account/AccountERC7702.test.js
  321. +++ b/test/account/AccountERC7702.test.js
  322. @@ -26,8 +26,8 @@ async function fixture() {
  323. // domain cannot be fetched using getDomain(mock) before the mock is deployed
  324. const domain = {
  325. - name: 'AccountERC7702Mock',
  326. - version: '1',
  327. + name: '', // Not initialized in the context of signer
  328. + version: '', // Not initialized in the context of signer
  329. chainId: entrypointDomain.chainId,
  330. verifyingContract: mock.address,
  331. };
  332. diff --git a/test/account/examples/AccountERC7702WithModulesMock.test.js b/test/account/examples/AccountERC7702WithModulesMock.test.js
  333. index 9ee5f9177..f6106bcc7 100644
  334. --- a/test/account/examples/AccountERC7702WithModulesMock.test.js
  335. +++ b/test/account/examples/AccountERC7702WithModulesMock.test.js
  336. @@ -36,8 +36,8 @@ async function fixture() {
  337. // domain cannot be fetched using getDomain(mock) before the mock is deployed
  338. const domain = {
  339. - name: 'AccountERC7702WithModulesMock',
  340. - version: '1',
  341. + name: '', // Not initialized in the context of signer
  342. + version: '', // Not initialized in the context of signer
  343. chainId: entrypointDomain.chainId,
  344. verifyingContract: mock.address,
  345. };
  346. diff --git a/test/utils/cryptography/EIP712.test.js b/test/utils/cryptography/EIP712.test.js
  347. index 2b6e7fa97..268e0d29d 100644
  348. --- a/test/utils/cryptography/EIP712.test.js
  349. +++ b/test/utils/cryptography/EIP712.test.js
  350. @@ -47,27 +47,6 @@ describe('EIP712', function () {
  351. const rebuildDomain = await getDomain(this.eip712);
  352. expect(rebuildDomain).to.be.deep.equal(this.domain);
  353. });
  354. -
  355. - if (shortOrLong === 'short') {
  356. - // Long strings are in storage, and the proxy will not be properly initialized unless
  357. - // the upgradeable contract variant is used and the initializer is invoked.
  358. -
  359. - it('adjusts when behind proxy', async function () {
  360. - const factory = await ethers.deployContract('$Clones');
  361. -
  362. - const clone = await factory
  363. - .$clone(this.eip712)
  364. - .then(tx => tx.wait())
  365. - .then(receipt => receipt.logs.find(ev => ev.fragment.name == 'return$clone_address').args.instance)
  366. - .then(address => ethers.getContractAt('$EIP712Verifier', address));
  367. -
  368. - const expectedDomain = { ...this.domain, verifyingContract: clone.target };
  369. - expect(await getDomain(clone)).to.be.deep.equal(expectedDomain);
  370. -
  371. - const expectedSeparator = await domainSeparator(expectedDomain);
  372. - expect(await clone.$_domainSeparatorV4()).to.equal(expectedSeparator);
  373. - });
  374. - }
  375. });
  376. it('hash digest', async function () {