Răsfoiți Sursa

Enable partial transpilation for upgradeable package (#4628)

Co-authored-by: Hadrien Croubois <hadrien.croubois@gmail.com>
Francisco 2 ani în urmă
părinte
comite
58463a9823

+ 5 - 0
.changeset/grumpy-poets-rush.md

@@ -0,0 +1,5 @@
+---
+'openzeppelin-solidity': major
+---
+
+Upgradeable Contracts: No longer transpile interfaces, libraries, and stateless contracts.

+ 0 - 2
.github/actions/setup/action.yml

@@ -15,5 +15,3 @@ runs:
       run: npm ci
       shell: bash
       if: steps.cache.outputs.cache-hit != 'true'
-      env:
-        SKIP_COMPILE: true

+ 3 - 0
.github/workflows/checks.yml

@@ -56,6 +56,9 @@ jobs:
           fetch-depth: 0 # Include history so patch conflicts are resolved automatically
       - name: Set up environment
         uses: ./.github/actions/setup
+      - name: Copy non-upgradeable contracts as dependency
+        run:
+          cp -rnT contracts node_modules/@openzeppelin/contracts
       - name: Transpile to upgradeable
         run: bash scripts/upgradeable/transpile.sh
       - name: Run tests

+ 36 - 0
contracts/mocks/Stateless.sol

@@ -0,0 +1,36 @@
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.8.20;
+
+// We keep these imports and a dummy contract just to we can run the test suite after transpilation.
+
+import {Address} from "../utils/Address.sol";
+import {Arrays} from "../utils/Arrays.sol";
+import {AuthorityUtils} from "../access/manager/AuthorityUtils.sol";
+import {Base64} from "../utils/Base64.sol";
+import {BitMaps} from "../utils/structs/BitMaps.sol";
+import {Checkpoints} from "../utils/structs/Checkpoints.sol";
+import {Clones} from "../proxy/Clones.sol";
+import {Create2} from "../utils/Create2.sol";
+import {DoubleEndedQueue} from "../utils/structs/DoubleEndedQueue.sol";
+import {ECDSA} from "../utils/cryptography/ECDSA.sol";
+import {EnumerableMap} from "../utils/structs/EnumerableMap.sol";
+import {EnumerableSet} from "../utils/structs/EnumerableSet.sol";
+import {ERC1155Holder} from "../token/ERC1155/utils/ERC1155Holder.sol";
+import {ERC165} from "../utils/introspection/ERC165.sol";
+import {ERC165Checker} from "../utils/introspection/ERC165Checker.sol";
+import {ERC1967Utils} from "../proxy/ERC1967/ERC1967Utils.sol";
+import {ERC721Holder} from "../token/ERC721/utils/ERC721Holder.sol";
+import {Math} from "../utils/math/Math.sol";
+import {MerkleProof} from "../utils/cryptography/MerkleProof.sol";
+import {MessageHashUtils} from "../utils/cryptography/MessageHashUtils.sol";
+import {SafeCast} from "../utils/math/SafeCast.sol";
+import {SafeERC20} from "../token/ERC20/utils/SafeERC20.sol";
+import {ShortStrings} from "../utils/ShortStrings.sol";
+import {SignatureChecker} from "../utils/cryptography/SignatureChecker.sol";
+import {SignedMath} from "../utils/math/SignedMath.sol";
+import {StorageSlot} from "../utils/StorageSlot.sol";
+import {Strings} from "../utils/Strings.sol";
+import {Time} from "../utils/types/Time.sol";
+
+contract Dummy1234 {}

+ 1 - 1
contracts/package.json

@@ -8,7 +8,7 @@
     "!/mocks/**/*"
   ],
   "scripts": {
-    "prepare": "bash ../scripts/prepare-contracts-package.sh",
+    "prepack": "bash ../scripts/prepack.sh",
     "prepare-docs": "cd ..; npm run prepare-docs"
   },
   "repository": {

+ 2 - 0
contracts/proxy/utils/UUPSUpgradeable.sol

@@ -15,6 +15,8 @@ import {ERC1967Utils} from "../ERC1967/ERC1967Utils.sol";
  * `UUPSUpgradeable` with a custom implementation of upgrades.
  *
  * The {_authorizeUpgrade} function must be overridden to include access restriction to the upgrade mechanism.
+ *
+ * @custom:stateless
  */
 abstract contract UUPSUpgradeable is IERC1822Proxiable {
     /// @custom:oz-upgrades-unsafe-allow state-variable-immutable

+ 2 - 0
contracts/token/ERC1155/utils/ERC1155Holder.sol

@@ -11,6 +11,8 @@ import {IERC1155Receiver} from "../IERC1155Receiver.sol";
  *
  * IMPORTANT: When inheriting this contract, you must include a way to use the received tokens, otherwise they will be
  * stuck.
+ *
+ * @custom:stateless
  */
 abstract contract ERC1155Holder is ERC165, IERC1155Receiver {
     /**

+ 2 - 0
contracts/token/ERC721/utils/ERC721Holder.sol

@@ -11,6 +11,8 @@ import {IERC721Receiver} from "../IERC721Receiver.sol";
  * Accepts all token transfers.
  * Make sure the contract is able to use its token with {IERC721-safeTransferFrom}, {IERC721-approve} or
  * {IERC721-setApprovalForAll}.
+ *
+ * @custom:stateless
  */
 abstract contract ERC721Holder is IERC721Receiver {
     /**

+ 2 - 0
contracts/utils/Context.sol

@@ -12,6 +12,8 @@ pragma solidity ^0.8.20;
  * is concerned).
  *
  * This contract is only required for intermediate, library-like contracts.
+ *
+ * @custom:stateless
  */
 abstract contract Context {
     function _msgSender() internal view virtual returns (address) {

+ 2 - 0
contracts/utils/Multicall.sol

@@ -7,6 +7,8 @@ import {Address} from "./Address.sol";
 
 /**
  * @dev Provides a function to batch together multiple calls in a single external call.
+ *
+ * @custom:stateless
  */
 abstract contract Multicall {
     /**

+ 2 - 0
contracts/utils/introspection/ERC165.sol

@@ -16,6 +16,8 @@ import {IERC165} from "./IERC165.sol";
  *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
  * }
  * ```
+ *
+ * @custom:stateless
  */
 abstract contract ERC165 is IERC165 {
     /**

+ 1 - 0
hardhat.config.js

@@ -93,6 +93,7 @@ module.exports = {
     },
   },
   exposed: {
+    imports: true,
     initializers: true,
     exclude: ['vendor/**/*'],
   },

+ 92 - 5
package-lock.json

@@ -18,6 +18,7 @@
         "@nomiclabs/hardhat-web3": "^2.0.0",
         "@openzeppelin/docs-utils": "^0.1.4",
         "@openzeppelin/test-helpers": "^0.5.13",
+        "@openzeppelin/upgrade-safe-transpiler": "^0.3.30",
         "@openzeppelin/upgrades-core": "^1.20.6",
         "array.prototype.at": "^1.1.1",
         "chai": "^4.2.0",
@@ -29,7 +30,7 @@
         "glob": "^10.3.5",
         "graphlib": "^2.1.8",
         "hardhat": "^2.9.1",
-        "hardhat-exposed": "^0.3.11",
+        "hardhat-exposed": "^0.3.12-1",
         "hardhat-gas-reporter": "^1.0.4",
         "hardhat-ignore-warnings": "^0.2.0",
         "keccak256": "^1.0.2",
@@ -2407,6 +2408,91 @@
         "semver": "bin/semver"
       }
     },
+    "node_modules/@openzeppelin/upgrade-safe-transpiler": {
+      "version": "0.3.30",
+      "resolved": "https://registry.npmjs.org/@openzeppelin/upgrade-safe-transpiler/-/upgrade-safe-transpiler-0.3.30.tgz",
+      "integrity": "sha512-nkJ4r+W+FUp0eAvE18uHh/smwD1NS3KLAGJ59+Vgmx3VlCvCDNaS0rTJ1FpwxDYD3J0Whx0ZVtHz2ySq4YsnNQ==",
+      "dev": true,
+      "dependencies": {
+        "ajv": "^8.0.0",
+        "compare-versions": "^6.0.0",
+        "ethereum-cryptography": "^2.0.0",
+        "lodash": "^4.17.20",
+        "minimatch": "^9.0.0",
+        "minimist": "^1.2.5",
+        "solidity-ast": "^0.4.51"
+      },
+      "bin": {
+        "upgrade-safe-transpiler": "dist/cli.js"
+      }
+    },
+    "node_modules/@openzeppelin/upgrade-safe-transpiler/node_modules/ajv": {
+      "version": "8.12.0",
+      "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
+      "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
+      "dev": true,
+      "dependencies": {
+        "fast-deep-equal": "^3.1.1",
+        "json-schema-traverse": "^1.0.0",
+        "require-from-string": "^2.0.2",
+        "uri-js": "^4.2.2"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/epoberezkin"
+      }
+    },
+    "node_modules/@openzeppelin/upgrade-safe-transpiler/node_modules/brace-expansion": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+      "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+      "dev": true,
+      "dependencies": {
+        "balanced-match": "^1.0.0"
+      }
+    },
+    "node_modules/@openzeppelin/upgrade-safe-transpiler/node_modules/ethereum-cryptography": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.1.2.tgz",
+      "integrity": "sha512-Z5Ba0T0ImZ8fqXrJbpHcbpAvIswRte2wGNR/KePnu8GbbvgJ47lMxT/ZZPG6i9Jaht4azPDop4HaM00J0J59ug==",
+      "dev": true,
+      "dependencies": {
+        "@noble/curves": "1.1.0",
+        "@noble/hashes": "1.3.1",
+        "@scure/bip32": "1.3.1",
+        "@scure/bip39": "1.2.1"
+      }
+    },
+    "node_modules/@openzeppelin/upgrade-safe-transpiler/node_modules/json-schema-traverse": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+      "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+      "dev": true
+    },
+    "node_modules/@openzeppelin/upgrade-safe-transpiler/node_modules/minimatch": {
+      "version": "9.0.3",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
+      "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
+      "dev": true,
+      "dependencies": {
+        "brace-expansion": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=16 || 14 >=14.17"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
+    "node_modules/@openzeppelin/upgrade-safe-transpiler/node_modules/require-from-string": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+      "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/@openzeppelin/upgrades-core": {
       "version": "1.29.0",
       "resolved": "https://registry.npmjs.org/@openzeppelin/upgrades-core/-/upgrades-core-1.29.0.tgz",
@@ -8564,13 +8650,13 @@
       }
     },
     "node_modules/hardhat-exposed": {
-      "version": "0.3.12",
-      "resolved": "https://registry.npmjs.org/hardhat-exposed/-/hardhat-exposed-0.3.12.tgz",
-      "integrity": "sha512-op/shZ6YQcQzPzxT4h0oD3x7M6fBna2rM/YUuhZLzJOtsu/DF9xK2o2thPSR1LAz8enx3wbjJZxl7b2+QXyDYw==",
+      "version": "0.3.12-1",
+      "resolved": "https://registry.npmjs.org/hardhat-exposed/-/hardhat-exposed-0.3.12-1.tgz",
+      "integrity": "sha512-hDhh+wC6usu/OPT4v6hi+JdBxZJDgi6gVAw/45ApY7rgODCqpan7+8GuVwOtu0YK/9wPN9Y065MzAVFqJtylgA==",
       "dev": true,
       "dependencies": {
         "micromatch": "^4.0.4",
-        "solidity-ast": "^0.4.25"
+        "solidity-ast": "^0.4.52"
       },
       "peerDependencies": {
         "hardhat": "^2.3.0"
@@ -16919,6 +17005,7 @@
     },
     "scripts/solhint-custom": {
       "name": "solhint-plugin-openzeppelin",
+      "version": "0.0.0",
       "dev": true
     }
   }

+ 3 - 3
package.json

@@ -2,9 +2,9 @@
   "name": "openzeppelin-solidity",
   "description": "Secure Smart Contract library for Solidity",
   "version": "4.9.2",
+  "private": true,
   "files": [
     "/contracts/**/*.sol",
-    "/build/contracts/*.json",
     "!/contracts/mocks/**/*"
   ],
   "scripts": {
@@ -20,7 +20,6 @@
     "lint:sol": "prettier --log-level warn --ignore-path .gitignore '{contracts,test}/**/*.sol' --check && solhint '{contracts,test}/**/*.sol'",
     "lint:sol:fix": "prettier --log-level warn --ignore-path .gitignore '{contracts,test}/**/*.sol' --write",
     "clean": "hardhat clean && rimraf build contracts/build",
-    "prepare": "scripts/prepare.sh",
     "prepack": "scripts/prepack.sh",
     "generate": "scripts/generate/run.js",
     "release": "scripts/release/release.sh",
@@ -59,6 +58,7 @@
     "@nomiclabs/hardhat-web3": "^2.0.0",
     "@openzeppelin/docs-utils": "^0.1.4",
     "@openzeppelin/test-helpers": "^0.5.13",
+    "@openzeppelin/upgrade-safe-transpiler": "^0.3.30",
     "@openzeppelin/upgrades-core": "^1.20.6",
     "array.prototype.at": "^1.1.1",
     "chai": "^4.2.0",
@@ -70,7 +70,7 @@
     "glob": "^10.3.5",
     "graphlib": "^2.1.8",
     "hardhat": "^2.9.1",
-    "hardhat-exposed": "^0.3.11",
+    "hardhat-exposed": "^0.3.12-1",
     "hardhat-gas-reporter": "^1.0.4",
     "hardhat-ignore-warnings": "^0.2.0",
     "keccak256": "^1.0.2",

+ 14 - 3
scripts/prepack.sh

@@ -4,9 +4,20 @@ set -euo pipefail
 shopt -s globstar
 
 # cross platform `mkdir -p`
-node -e 'fs.mkdirSync("build/contracts", { recursive: true })'
+mkdirp() {
+  node -e "fs.mkdirSync('$1', { recursive: true })"
+}
 
-cp artifacts/contracts/**/*.json build/contracts
-rm build/contracts/*.dbg.json
+# cd to the root of the repo
+cd "$(git rev-parse --show-toplevel)"
 
+npm run clean
+
+env COMPILE_MODE=production npm run compile
+
+mkdirp contracts/build/contracts
+cp artifacts/contracts/**/*.json contracts/build/contracts
+rm contracts/build/contracts/*.dbg.json
 node scripts/remove-ignored-artifacts.js
+
+cp README.md contracts/

+ 0 - 15
scripts/prepare-contracts-package.sh

@@ -1,15 +0,0 @@
-#!/usr/bin/env bash
-
-# cd to the root of the repo
-cd "$(git rev-parse --show-toplevel)"
-
-# avoids re-compilation during publishing of both packages
-if [[ ! -v ALREADY_COMPILED ]]; then
-  npm run clean
-  npm run prepare
-  npm run prepack
-fi
-
-cp README.md contracts/
-mkdir contracts/build contracts/build/contracts
-cp -r build/contracts/*.json contracts/build/contracts

+ 0 - 10
scripts/prepare.sh

@@ -1,10 +0,0 @@
-#!/usr/bin/env bash
-
-set -euo pipefail
-
-if [ "${SKIP_COMPILE:-}" == true ]; then
-  exit
-fi
-
-npm run clean
-env COMPILE_MODE=production npm run compile

+ 1 - 1
scripts/remove-ignored-artifacts.js

@@ -23,7 +23,7 @@ const ignorePatternsSubtrees = ignorePatterns
   .concat(ignorePatterns.map(pat => path.join(pat, '**/*')))
   .map(p => p.replace(/^\//, ''));
 
-const artifactsDir = 'build/contracts';
+const artifactsDir = 'contracts/build/contracts';
 const buildinfo = 'artifacts/build-info';
 const filenames = fs.readdirSync(buildinfo);
 

+ 7 - 2
scripts/upgradeable/transpile.sh

@@ -2,9 +2,12 @@
 
 set -euo pipefail -x
 
+VERSION="$(jq -r .version contracts/package.json)"
 DIRNAME="$(dirname -- "${BASH_SOURCE[0]}")"
 
 bash "$DIRNAME/patch-apply.sh"
+sed -i "s/<package-version>/$VERSION/g" contracts/package.json
+git add contracts/package.json
 
 npm run clean
 npm run compile
@@ -24,7 +27,8 @@ fi
 # -p: emit public initializer
 # -n: use namespaces
 # -N: exclude from namespaces transformation
-npx @openzeppelin/upgrade-safe-transpiler@latest -D \
+# -q: partial transpilation using @openzeppelin/contracts as peer project
+npx @openzeppelin/upgrade-safe-transpiler -D \
   -b "$build_info" \
   -i contracts/proxy/utils/Initializable.sol \
   -x 'contracts-exposed/**/*' \
@@ -36,7 +40,8 @@ npx @openzeppelin/upgrade-safe-transpiler@latest -D \
   -x '!contracts/proxy/beacon/IBeacon.sol' \
   -p 'contracts/**/presets/**/*' \
   -n \
-  -N 'contracts/mocks/**/*'
+  -N 'contracts/mocks/**/*' \
+  -q '@openzeppelin/'
 
 # delete compilation artifacts of vanilla code
 npm run clean

+ 17 - 7
scripts/upgradeable/upgradeable.patch

@@ -59,7 +59,7 @@ index ff596b0c3..000000000
 -<!-- 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 53c29e5f8..666a667d3 100644
+index 549891e3f..a6b24078e 100644
 --- a/README.md
 +++ b/README.md
 @@ -23,6 +23,9 @@
@@ -81,8 +81,8 @@ index 53c29e5f8..666a667d3 100644
  ```
  
  #### Foundry (git)
-@@ -40,10 +43,10 @@ $ npm install @openzeppelin/contracts
- > **Warning** Foundry installs the latest version initially, but subsequent `forge update` commands will use the `master` branch.
+@@ -42,10 +45,10 @@ $ npm install @openzeppelin/contracts
+ > Foundry installs the latest version initially, but subsequent `forge update` commands will use the `master` branch.
  
  ```
 -$ forge install OpenZeppelin/openzeppelin-contracts
@@ -94,7 +94,7 @@ index 53c29e5f8..666a667d3 100644
  
  ### Usage
  
-@@ -52,10 +55,11 @@ Once installed, you can use the contracts in the library by importing them:
+@@ -54,10 +57,11 @@ Once installed, you can use the contracts in the library by importing them:
  ```solidity
  pragma solidity ^0.8.20;
  
@@ -110,7 +110,7 @@ index 53c29e5f8..666a667d3 100644
  }
  ```
 diff --git a/contracts/package.json b/contracts/package.json
-index df141192d..1cf90ad14 100644
+index 9017953ca..f51c1d38b 100644
 --- a/contracts/package.json
 +++ b/contracts/package.json
 @@ -1,5 +1,5 @@
@@ -129,6 +129,16 @@ index df141192d..1cf90ad14 100644
    },
    "keywords": [
      "solidity",
+@@ -28,5 +28,8 @@
+   "bugs": {
+     "url": "https://github.com/OpenZeppelin/openzeppelin-contracts/issues"
+   },
+-  "homepage": "https://openzeppelin.com/contracts/"
++  "homepage": "https://openzeppelin.com/contracts/",
++  "peerDependencies": {
++    "@openzeppelin/contracts": "<package-version>"
++  }
+ }
 diff --git a/contracts/utils/cryptography/EIP712.sol b/contracts/utils/cryptography/EIP712.sol
 index 644f6f531..ab8ba05ff 100644
 --- a/contracts/utils/cryptography/EIP712.sol
@@ -297,10 +307,10 @@ index 644f6f531..ab8ba05ff 100644
      }
  }
 diff --git a/package.json b/package.json
-index e6804c4cd..612ec513e 100644
+index 3a1617c09..97e59c2d9 100644
 --- a/package.json
 +++ b/package.json
-@@ -33,7 +33,7 @@
+@@ -32,7 +32,7 @@
    },
    "repository": {
      "type": "git",