diff --git a/.changeset/grumpy-poets-rush.md b/.changeset/grumpy-poets-rush.md new file mode 100644 index 00000000000..e566a10fecf --- /dev/null +++ b/.changeset/grumpy-poets-rush.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': major +--- + +Upgradeable Contracts: No longer transpile interfaces, libraries, and stateless contracts. diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index 84ebe823ab8..3154e9931af 100644 --- a/.github/actions/setup/action.yml +++ b/.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 diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 3d9b199fb70..3e7cba1201e 100644 --- a/.github/workflows/checks.yml +++ b/.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 diff --git a/contracts/mocks/Stateless.sol b/contracts/mocks/Stateless.sol new file mode 100644 index 00000000000..56f5b4c6610 --- /dev/null +++ b/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 {} diff --git a/contracts/package.json b/contracts/package.json index df141192d3d..9017953ca3c 100644 --- a/contracts/package.json +++ b/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": { diff --git a/contracts/proxy/utils/UUPSUpgradeable.sol b/contracts/proxy/utils/UUPSUpgradeable.sol index f08e61c1e8b..045d6a326a6 100644 --- a/contracts/proxy/utils/UUPSUpgradeable.sol +++ b/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 diff --git a/contracts/token/ERC1155/utils/ERC1155Holder.sol b/contracts/token/ERC1155/utils/ERC1155Holder.sol index 7c8d470e03a..6f353aa5953 100644 --- a/contracts/token/ERC1155/utils/ERC1155Holder.sol +++ b/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 { /** diff --git a/contracts/token/ERC721/utils/ERC721Holder.sol b/contracts/token/ERC721/utils/ERC721Holder.sol index 4ffd16146b1..a7c4b03b555 100644 --- a/contracts/token/ERC721/utils/ERC721Holder.sol +++ b/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 { /** diff --git a/contracts/utils/Context.sol b/contracts/utils/Context.sol index 25e1159256e..8a74f248750 100644 --- a/contracts/utils/Context.sol +++ b/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) { diff --git a/contracts/utils/Multicall.sol b/contracts/utils/Multicall.sol index a9a3d3acf06..aa23e635bda 100644 --- a/contracts/utils/Multicall.sol +++ b/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 { /** diff --git a/contracts/utils/introspection/ERC165.sol b/contracts/utils/introspection/ERC165.sol index 71c8e4a4f6d..1963a257dad 100644 --- a/contracts/utils/introspection/ERC165.sol +++ b/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 { /** diff --git a/hardhat.config.js b/hardhat.config.js index 1c87aac94b5..aca9d7273ab 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -93,6 +93,7 @@ module.exports = { }, }, exposed: { + imports: true, initializers: true, exclude: ['vendor/**/*'], }, diff --git a/package-lock.json b/package-lock.json index f668e9d2825..b1b91181550 100644 --- a/package-lock.json +++ b/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 } } diff --git a/package.json b/package.json index e6804c4cd96..3a1617c097d 100644 --- a/package.json +++ b/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", diff --git a/scripts/prepack.sh b/scripts/prepack.sh index 6f1bd4c32f1..6af10329f66 100755 --- a/scripts/prepack.sh +++ b/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/ diff --git a/scripts/prepare-contracts-package.sh b/scripts/prepare-contracts-package.sh deleted file mode 100755 index 3f62fd419dd..00000000000 --- a/scripts/prepare-contracts-package.sh +++ /dev/null @@ -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 diff --git a/scripts/prepare.sh b/scripts/prepare.sh deleted file mode 100755 index 7014a7076ff..00000000000 --- a/scripts/prepare.sh +++ /dev/null @@ -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 diff --git a/scripts/remove-ignored-artifacts.js b/scripts/remove-ignored-artifacts.js index f3e45b8d1b2..e156032b17c 100644 --- a/scripts/remove-ignored-artifacts.js +++ b/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); diff --git a/scripts/upgradeable/transpile.sh b/scripts/upgradeable/transpile.sh index 05de96d3436..f2126936ca7 100644 --- a/scripts/upgradeable/transpile.sh +++ b/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//$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 diff --git a/scripts/upgradeable/upgradeable.patch b/scripts/upgradeable/upgradeable.patch index 18384b94a70..f25710cdec7 100644 --- a/scripts/upgradeable/upgradeable.patch +++ b/scripts/upgradeable/upgradeable.patch @@ -59,7 +59,7 @@ index ff596b0c3..000000000 - - 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": "" ++ } + } 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",